Skip to content

Redesign changelog page with feature highlights - #630

Merged
lawrencecchen merged 16 commits into
mainfrom
feat-rich-changelog
Feb 27, 2026
Merged

lawrencecchen merged 16 commits into
mainfrom
feat-rich-changelog

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Feb 27, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Major releases now show narrative summaries and feature highlight cards (2-column grid with image support for screenshots)
  • Colored section badges: green Added, blue Changed, amber Fixed, purple Contributors
  • Contributor avatars from GitHub inline as pills
  • Versions separated by horizontal rules with anchor links for deep-linking
  • Minor/patch releases stay compact as bullet lists
  • changelog-media.ts is a supplementary data layer alongside CHANGELOG.md for adding images, summaries, and feature descriptions to major releases

Screenshots needed

Image references are commented out in changelog-media.ts. To add screenshots, drop PNGs into web/public/changelog/ and uncomment the image fields. Planned images:

  • 0.61.0-open-with.png (command palette "Open Current Directory in..." results)
  • 0.61.0-command-palette.png (general command palette)
  • 0.61.0-tab-colors.png (workspace color picker)
  • 0.61.0-workspace-metadata.png (sidebar showing PR links, ports, branches)

Test plan

  • Check Vercel preview at /docs/changelog
  • Verify 0.61.0 and 0.60.0 show feature cards and summaries
  • Verify minor releases (0.59.0, 0.58.0, etc.) render as compact bullet lists
  • Check contributor avatars load on 0.61.0
  • Verify section badges render with correct colors
  • Check anchor links work (e.g. /docs/changelog#v0.61.0)
  • Light mode and dark mode both look good

Summary by CodeRabbit

  • New Features
    • Changelog now shows hero images and curated feature highlights per release, with image-aware layout and sizing.
    • Version entries include formatted release dates, visual section badges, and contributor avatars/links.
    • Added a "View Changelog" link beside the docs CTA for easier access.
  • Style
    • Refined documentation typography and spacing for more consistent reading across docs.
  • Chores
    • Enabled loading of external images used in changelog media.

Major releases now show narrative summaries, feature highlight cards
(with image support for screenshots), colored section badges, and
contributor avatars. Minor releases stay compact. Adds changelog-media.ts
as a supplementary data layer alongside CHANGELOG.md.
@vercel

vercel Bot commented Feb 27, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Feb 27, 2026 11:35am

@coderabbitai

coderabbitai Bot commented Feb 27, 2026 •

Copy link
Copy Markdown

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

Adds a changelog media data module and updates the changelog page to render optional hero/feature images, contributor avatars, and section badges; adds a footer "View Changelog" link, allows remote PNGs from GitHub, and moves documentation prose styles into an @layer base block.

Changes

Cohort / File(s) Summary
Changelog Media Data
web/app/docs/changelog/changelog-media.ts
New module: declares FeatureHighlight and VersionMedia interfaces and exports changelogMedia: Record<string, VersionMedia> with per-version titles, optional hero, features, and image placement docs.
Changelog Page UI
web/app/docs/changelog/page.tsx
Reworks changelog rendering to consume changelogMedia; adds helpers (pngDimensions, formatDate) and components (HeroImage, FeatureImage, FeatureList, ContributorList, SectionBadge); integrates Next/Image for hero/feature images, per-version anchors, and media-driven article layout.
Site CTA
web/app/page.tsx
Adds a "View Changelog" link beside "Read the Docs" and introduces horizontal spacing between CTA links.
Next.js Image config
web/next.config.ts
Adds images.remotePatterns to permit remote PNG loading from https://github.com paths.
Global CSS layering
web/app/globals.css
Re-scopes documentation prose and table styles into an @layer base block, converting margin-based spacing to padding-based within the layer; visual rules preserved.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I hopped through lines of code and light,
Placed heroes, badges, images bright,
Avatars peeking, features in view,
A gap of links and styles anew,
The changelog blooms — a rabbit's delight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: redesigning the changelog page with feature highlights, which aligns with the core modifications across changelog-media.ts, page.tsx, and supporting files.

✏️ 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
  • Commit unit tests in branch feat-rich-changelog

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

@greptile-apps

greptile-apps Bot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Redesigns the changelog page with visual hierarchy and feature highlights. Major releases now display narrative summaries, 2-column feature cards with optional images, and colored section badges (green for Added, blue for Changed, amber for Fixed, purple for Contributors). Minor releases remain compact bullet lists. Contributor avatars are fetched from GitHub and displayed inline.

  • Created changelog-media.ts as a supplementary data layer for rich content
  • Added deep-linking support with anchor IDs
  • Improved date formatting and visual spacing
  • Contributor section now shows GitHub avatars in pill format
  • Images are commented out and ready to be enabled when screenshots are added

The implementation is solid but has two cleanup items: a ternary operator bug in FeatureGrid where hasAnyImage computes but returns identical values in both branches, and an unused major variable.

Confidence Score: 4/5

  • Safe to merge with minor cleanup recommended
  • UI redesign is well-structured with proper TypeScript types and good separation of concerns. The bugs found are non-critical (unused variable and ineffective ternary) and don't break functionality. Code is readable and maintainable.
  • Fix the ternary bug in page.tsx line 135-137 before merge

Important Files Changed

Filename Overview
web/app/docs/changelog/changelog-media.ts New supplementary data layer for changelog media and feature highlights - clean TypeScript interfaces with no issues
web/app/docs/changelog/page.tsx Redesigned changelog with feature grids and badges - has unused variable logic and ternary bug returning identical values

Last reviewed commit: 04aa009

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
web/app/docs/changelog/page.tsx (2)

131-138: Redundant ternary with identical branches.

hasAnyImage is computed but both branches of the ternary produce the same class string:

hasAnyImage ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2"

Simplify by removing the condition or implement the intended layout variation.

♻️ Proposed simplification
-  const hasAnyImage = media.features.some((f) => f.image);
-
   return (
-    <div
-      className={`mt-4 mb-6 grid gap-3 ${
-        hasAnyImage ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2"
-      }`}
-    >
+    <div className="mt-4 mb-6 grid gap-3 grid-cols-1 sm:grid-cols-2">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 131 - 138, The ternary in the
className for the div after computing hasAnyImage (via media.features.some((f)
=> f.image)) uses identical branches, so remove the redundant conditional and
simplify the className to the single intended string (or, if different layouts
were intended, update the true/false branches to the correct class names);
update the JSX where the div's className is set (the element using hasAnyImage)
to either a plain string "grid-cols-1 sm:grid-cols-2" or to the correct
differing classes for each branch.

108-113: major variable is computed but never used for differentiation.

The isMajorRelease function returns a boolean that's stored in major (line 244), but the conditional at lines 250-252 applies identical empty strings for both cases:

className={`... ${major ? "" : ""}`}

Either remove the unused major computation or implement the intended styling difference.

♻️ Option 1: Remove unused code
-          const major = isMajorRelease(v, media);

           return (
             <article
               key={v.version}
               id={`v${v.version}`}
-              className={`relative border-t border-border py-8 first:border-t-0 ${
-                major ? "" : ""
-              }`}
+              className="relative border-t border-border py-8 first:border-t-0"
             >

Also applies to: 244-244, 250-252

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 108 - 113, The variable `major`
computed via the `isMajorRelease` function is unused in the className
conditional; either remove the `major` computation (and the `isMajorRelease`
call) to simplify the code, or update the className template that currently
reads `className={\`... ${major ? "" : ""}\`}` to apply distinct classes for
major vs non-major releases; locate the `isMajorRelease` function and the
`major` variable usage in the component and either delete the `major` variable
and related ternary or replace the empty-string branches with the intended CSS
class names to effect the visual differentiation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 131-138: The ternary in the className for the div after computing
hasAnyImage (via media.features.some((f) => f.image)) uses identical branches,
so remove the redundant conditional and simplify the className to the single
intended string (or, if different layouts were intended, update the true/false
branches to the correct class names); update the JSX where the div's className
is set (the element using hasAnyImage) to either a plain string "grid-cols-1
sm:grid-cols-2" or to the correct differing classes for each branch.
- Around line 108-113: The variable `major` computed via the `isMajorRelease`
function is unused in the className conditional; either remove the `major`
computation (and the `isMajorRelease` call) to simplify the code, or update the
className template that currently reads `className={\`... ${major ? "" : ""}\`}`
to apply distinct classes for major vs non-major releases; locate the
`isMajorRelease` function and the `major` variable usage in the component and
either delete the `major` variable and related ternary or replace the
empty-string branches with the intended CSS class names to effect the visual
differentiation.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c70ac25 and 04aa009.

📒 Files selected for processing (2)
  • web/app/docs/changelog/changelog-media.ts
  • web/app/docs/changelog/page.tsx

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread web/app/docs/changelog/page.tsx Outdated
Comment on lines +135 to +137
className={`mt-4 mb-6 grid gap-3 ${
hasAnyImage ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2"
}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ternary returns identical values in both branches - hasAnyImage is unused

Suggested change
className={`mt-4 mb-6 grid gap-3 ${
hasAnyImage ? "grid-cols-1 sm:grid-cols-2" : "grid-cols-1 sm:grid-cols-2"
}`}
className={`mt-4 mb-6 grid gap-3 grid-cols-1 sm:grid-cols-2`}

Comment thread web/app/docs/changelog/page.tsx Outdated
Comment on lines +244 to +252
const major = isMajorRelease(v, media);

return (
<article
key={v.version}
id={`v${v.version}`}
className={`relative border-t border-border py-8 first:border-t-0 ${
major ? "" : ""
}`}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

major variable is computed but never used (ternary on line 251 returns empty string for both branches)

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 7-14: In pngDimensions, the height is read from the wrong offset
(buf.readUInt32BE(24)); change the second read to buf.readUInt32BE(20) so the
function reads width at offset 16 and height at offset 20 (IHDR bytes 16–23).
Update the comment if needed to reflect the correct byte ranges and keep the
function name pngDimensions and the two buf.readUInt32BE calls as the locations
to modify.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 04aa009 and e842fd0.

⛔ Files ignored due to path filters (8)
  • web/public/changelog/0.60.0-cjk-input.png is excluded by !**/*.png
  • web/public/changelog/0.60.0-devtools.png is excluded by !**/*.png
  • web/public/changelog/0.60.0-tab-context-menu.png is excluded by !**/*.png
  • web/public/changelog/0.61.0-command-palette.png is excluded by !**/*.png
  • web/public/changelog/0.61.0-open-with.png is excluded by !**/*.png
  • web/public/changelog/0.61.0-pin-workspace.png is excluded by !**/*.png
  • web/public/changelog/0.61.0-tab-colors.png is excluded by !**/*.png
  • web/public/changelog/0.61.0-workspace-metadata.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • web/app/docs/changelog/changelog-media.ts
  • web/app/docs/changelog/page.tsx
  • web/app/page.tsx
  • web/next.config.ts

Comment on lines +7 to +14
/** Read PNG dimensions from the IHDR chunk (bytes 16-23). */
function pngDimensions(filePath: string): { width: number; height: number } {
const abs = path.join(process.cwd(), "public", filePath);
const buf = fs.readFileSync(abs);
return {
width: buf.readUInt32BE(16),
height: buf.readUInt32BE(24),
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix PNG height offset in pngDimensions (currently reading wrong bytes).

Line 13 reads height from byte offset 24, but the IHDR width/height block is bytes 16-23; this yields invalid dimensions and broken image aspect ratios.

🐛 Proposed fix
 /** Read PNG dimensions from the IHDR chunk (bytes 16-23). */
 function pngDimensions(filePath: string): { width: number; height: number } {
   const abs = path.join(process.cwd(), "public", filePath);
   const buf = fs.readFileSync(abs);
   return {
     width: buf.readUInt32BE(16),
-    height: buf.readUInt32BE(24),
+    height: buf.readUInt32BE(20),
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 7 - 14, In pngDimensions, the
height is read from the wrong offset (buf.readUInt32BE(24)); change the second
read to buf.readUInt32BE(20) so the function reads width at offset 16 and height
at offset 20 (IHDR bytes 16–23). Update the comment if needed to reflect the
correct byte ranges and keep the function name pngDimensions and the two
buf.readUInt32BE calls as the locations to modify.

Unlayered CSS beats @layer utilities in the cascade, so .docs-content
rules (margins, padding, list-style) were overriding Tailwind utilities
on ul, li, h2 elements in the changelog page. Moving them into
@layer base lets utilities win without needing !important hacks.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
web/app/docs/changelog/page.tsx (1)

7-14: ⚠️ Potential issue | 🔴 Critical

Fix PNG IHDR height offset in pngDimensions.

Line 13 reads height from byte offset 24; IHDR height is at offset 20, so dimensions are incorrect.

🐛 Proposed fix
   return {
     width: buf.readUInt32BE(16),
-    height: buf.readUInt32BE(24),
+    height: buf.readUInt32BE(20),
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 7 - 14, The pngDimensions
function reads the PNG IHDR chunk incorrectly: change the height offset from 24
to 20 so the IHDR height is read from byte 20; update the return in
pngDimensions (which uses buf.readUInt32BE(...)) to read width at 16 and height
at 20 to return correct dimensions for the given filePath.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 8-10: pngDimensions currently reads a file directly and will throw
for missing or non-local/remote image paths, which can 500 the /docs/changelog
page; update pngDimensions to guard against non-local or absent files by first
resolving the path against the public directory and checking existence (e.g.,
with fs.existsSync or a try/catch around fs.readFileSync) and return a safe
fallback (such as default width/height or undefined) when the file is not found
or not local instead of letting the exception propagate; keep the function name
pngDimensions and ensure callers handle the fallback result.

---

Duplicate comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 7-14: The pngDimensions function reads the PNG IHDR chunk
incorrectly: change the height offset from 24 to 20 so the IHDR height is read
from byte 20; update the return in pngDimensions (which uses
buf.readUInt32BE(...)) to read width at 16 and height at 20 to return correct
dimensions for the given filePath.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e842fd0 and a899423.

📒 Files selected for processing (2)
  • web/app/docs/changelog/page.tsx
  • web/app/globals.css

Comment on lines +8 to +10
function pngDimensions(filePath: string): { width: number; height: number } {
const abs = path.join(process.cwd(), "public", filePath);
const buf = fs.readFileSync(abs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid hard-failing /docs/changelog on missing or remote image sources.

Lines 9-10 throw if the file path is absent/non-local, which can 500 the whole page. Add a safe fallback path in pngDimensions.

🛡️ Proposed hardening
+const FALLBACK_IMAGE_DIMS = { width: 1200, height: 675 };
+
 function pngDimensions(filePath: string): { width: number; height: number } {
-  const abs = path.join(process.cwd(), "public", filePath);
-  const buf = fs.readFileSync(abs);
-  return {
-    width: buf.readUInt32BE(16),
-    height: buf.readUInt32BE(24),
-  };
+  if (/^https?:\/\//.test(filePath)) return FALLBACK_IMAGE_DIMS;
+  const rel = filePath.replace(/^\/+/, "");
+  const abs = path.join(process.cwd(), "public", rel);
+  try {
+    const buf = fs.readFileSync(abs);
+    return {
+      width: buf.readUInt32BE(16),
+      height: buf.readUInt32BE(24),
+    };
+  } catch {
+    return FALLBACK_IMAGE_DIMS;
+  }
 }
📝 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
function pngDimensions(filePath: string): { width: number; height: number } {
const abs = path.join(process.cwd(), "public", filePath);
const buf = fs.readFileSync(abs);
const FALLBACK_IMAGE_DIMS = { width: 1200, height: 675 };
function pngDimensions(filePath: string): { width: number; height: number } {
if (/^https?:\/\//.test(filePath)) return FALLBACK_IMAGE_DIMS;
const rel = filePath.replace(/^\/+/, "");
const abs = path.join(process.cwd(), "public", rel);
try {
const buf = fs.readFileSync(abs);
return {
width: buf.readUInt32BE(16),
height: buf.readUInt32BE(24),
};
} catch {
return FALLBACK_IMAGE_DIMS;
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 8 - 10, pngDimensions currently
reads a file directly and will throw for missing or non-local/remote image
paths, which can 500 the /docs/changelog page; update pngDimensions to guard
against non-local or absent files by first resolving the path against the public
directory and checking existence (e.g., with fs.existsSync or a try/catch around
fs.readFileSync) and return a safe fallback (such as default width/height or
undefined) when the file is not found or not local instead of letting the
exception propagate; keep the function name pngDimensions and ensure callers
handle the fallback result.

Margins were collapsing and conflicting with Tailwind layout utilities
in the changelog. Padding doesn't collapse and can't interfere with
external spacing set by parent containers.

@coderabbitai coderabbitai 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.

♻️ Duplicate comments (1)
web/app/docs/changelog/page.tsx (1)

7-15: ⚠️ Potential issue | 🔴 Critical

Harden and correct pngDimensions (wrong IHDR offset + unhandled file failures).

Line 13 reads PNG height from the wrong offset, and the current direct read can throw for missing/non-local assets, breaking /docs/changelog.

🐛 Proposed fix
+const FALLBACK_IMAGE_DIMS = { width: 1200, height: 675 };
+
 /** Read PNG dimensions from the IHDR chunk (bytes 16-23). */
 function pngDimensions(filePath: string): { width: number; height: number } {
-  const abs = path.join(process.cwd(), "public", filePath);
-  const buf = fs.readFileSync(abs);
-  return {
-    width: buf.readUInt32BE(16),
-    height: buf.readUInt32BE(24),
-  };
+  if (/^https?:\/\//.test(filePath)) return FALLBACK_IMAGE_DIMS;
+  const rel = filePath.replace(/^\/+/, "");
+  const abs = path.join(process.cwd(), "public", rel);
+  try {
+    const buf = fs.readFileSync(abs);
+    if (buf.length < 24) return FALLBACK_IMAGE_DIMS;
+    return {
+      width: buf.readUInt32BE(16),
+      height: buf.readUInt32BE(20),
+    };
+  } catch {
+    return FALLBACK_IMAGE_DIMS;
+  }
 }
#!/bin/bash
set -euo pipefail

PAGE="$(fd -p 'web/app/docs/changelog/page.tsx' | head -n1)"
MEDIA="$(fd -p 'web/app/docs/changelog/changelog-media.ts' | head -n1)"

echo "== pngDimensions byte offsets =="
rg -n 'readUInt32BE\(' "$PAGE"

echo "== pngDimensions hardening guards =="
rg -n 'FALLBACK_IMAGE_DIMS|https?:\\/\\/|try\\s*\\{|catch' "$PAGE"

echo "== Missing local changelog PNG assets =="
python - <<'PY'
import re
from pathlib import Path

media = Path("web/app/docs/changelog/changelog-media.ts")
text = media.read_text(encoding="utf-8")
pairs = re.findall(r'(?:image|hero):\s*"(/[^"]+\.png)"', text)
missing = [p for p in pairs if not Path("web/public" + p).exists()]
print("referenced:", len(pairs))
print("missing:", len(missing))
for p in missing:
    print("-", p)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 7 - 15, pngDimensions currently
reads the PNG IHDR height from the wrong offset and can throw for missing or
remote files; update pngDimensions to first guard against non-local paths (e.g.,
if filePath startsWith 'http' or similar) and missing files by returning a
fallback constant (FALLBACK_IMAGE_DIMS), then wrap the file read/parse in
try/catch to return the fallback on any error; correct the IHDR offsets to read
width = buf.readUInt32BE(16) and height = buf.readUInt32BE(20) (IHDR width at
bytes 16-19, height at 20-23), and ensure the function still returns
{width:number,height:number} using the fallback on failure.
🧹 Nitpick comments (1)
web/app/docs/changelog/page.tsx (1)

119-131: Avoid setting priority on every hero image.

priority is currently applied to all hero images, which can trigger unnecessary preloads. Limit it to the first/LCP entry.

♻️ Proposed refactor
-function HeroImage({ src, version }: { src: string; version: string }) {
+function HeroImage({
+  src,
+  version,
+  priority = false,
+}: {
+  src: string;
+  version: string;
+  priority?: boolean;
+}) {
   const { width, height } = pngDimensions(src);
   return (
     <div className="mt-4 mb-6 overflow-hidden rounded-lg border border-border">
       <Image
         src={src}
         alt={`cmux ${version}`}
         width={width}
         height={height}
         sizes="(max-width: 640px) 100vw, 640px"
         className="w-full h-auto"
-        priority
+        priority={priority}
       />
     </div>
   );
 }
-        {versions.map((v) => {
+        {versions.map((v, idx) => {
           const media = changelogMedia[v.version];
@@
               {media?.hero && (
-                <HeroImage src={media.hero} version={v.version} />
+                <HeroImage src={media.hero} version={v.version} priority={idx === 0} />
               )}

Also applies to: 246-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/app/docs/changelog/page.tsx` around lines 119 - 131, HeroImage currently
always sets the Image prop priority which preloads every hero image; change
HeroImage({ src, version }) to accept an optional priority boolean (e.g.,
HeroImage({ src, version, priority }: { src: string; version: string; priority?:
boolean }) ) and only pass priority to the next/image component when that prop
is true (use priority={priority}). Then update the callers that render the
changelog entries (the map that renders the list around lines where entries
246-280 are generated) to pass priority={true} only for the first/LCP entry (use
index === 0 or a flag), leaving other calls without the prop so they don’t
preload. Ensure you continue to use pngDimensions and Image unchanged except for
the conditional priority prop.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 7-15: pngDimensions currently reads the PNG IHDR height from the
wrong offset and can throw for missing or remote files; update pngDimensions to
first guard against non-local paths (e.g., if filePath startsWith 'http' or
similar) and missing files by returning a fallback constant
(FALLBACK_IMAGE_DIMS), then wrap the file read/parse in try/catch to return the
fallback on any error; correct the IHDR offsets to read width =
buf.readUInt32BE(16) and height = buf.readUInt32BE(20) (IHDR width at bytes
16-19, height at 20-23), and ensure the function still returns
{width:number,height:number} using the fallback on failure.

---

Nitpick comments:
In `@web/app/docs/changelog/page.tsx`:
- Around line 119-131: HeroImage currently always sets the Image prop priority
which preloads every hero image; change HeroImage({ src, version }) to accept an
optional priority boolean (e.g., HeroImage({ src, version, priority }: { src:
string; version: string; priority?: boolean }) ) and only pass priority to the
next/image component when that prop is true (use priority={priority}). Then
update the callers that render the changelog entries (the map that renders the
list around lines where entries 246-280 are generated) to pass priority={true}
only for the first/LCP entry (use index === 0 or a flag), leaving other calls
without the prop so they don’t preload. Ensure you continue to use pngDimensions
and Image unchanged except for the conditional priority prop.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a899423 and bd841f4.

📒 Files selected for processing (2)
  • web/app/docs/changelog/page.tsx
  • web/app/globals.css

Block layout was collapsing when articles had media content (h2, feature
divs, section divs all rendered at the same position). Switching to
display:flex + flex-direction:column on articles and using inline styles
for all spacing guarantees proper vertical stacking regardless of
docs-content CSS interference.
@lawrencecchen
lawrencecchen merged commit 851c706 into main Feb 27, 2026
6 of 7 checks passed
@lawrencecchen
lawrencecchen deleted the feat-rich-changelog branch February 27, 2026 11:41
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Redesign changelog page with feature highlights and visual hierarchy

Major releases now show narrative summaries, feature highlight cards
(with image support for screenshots), colored section badges, and
contributor avatars. Minor releases stay compact. Adds changelog-media.ts
as a supplementary data layer alongside CHANGELOG.md.

* Use Next.js Image for optimized loading and GitHub avatar remotes

* Add screenshots for Open With and Tab Colors features

* Add workspace metadata screenshot

* Switch to single-column inline layout, add command palette screenshot

* Conductor-style titles, narrower body, narrative descriptions, reorder features

* Add pin workspace and tab context menu screenshots, remove subtitle

* Add View Changelog link to front page, add DevTools to 0.60.0

* Add CJK input screenshot to 0.60.0

* Read real PNG dimensions at build time, add proper sizes attribute

* Fix image overflow: wrap in overflow-hidden container, add max-w-full

* Fix CSS cascade: move docs-content styles into @layer base

Unlayered CSS beats @layer utilities in the cascade, so .docs-content
rules (margins, padding, list-style) were overriding Tailwind utilities
on ul, li, h2 elements in the changelog page. Moving them into
@layer base lets utilities win without needing !important hacks.

* Switch docs-content spacing from margin to padding

Margins were collapsing and conflicting with Tailwind layout utilities
in the changelog. Padding doesn't collapse and can't interfere with
external spacing set by parent containers.

* Fix changelog layout: use flex column + inline styles for all spacing

Block layout was collapsing when articles had media content (h2, feature
divs, section divs all rendered at the same position). Switching to
display:flex + flex-direction:column on articles and using inline styles
for all spacing guarantees proper vertical stacking regardless of
docs-content CSS interference.

* Remove border from changelog images

* Replace devtools screenshot with cmux inspecting cmux.dev

This branch was successfully deployed

1 active deployment
Preview — b00e2bd7 Deployed Feb 27, 2026 by vercel[bot]
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