Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
a04f84f
feat: nuxt og image v6
harlan-zw Feb 26, 2026
0198696
chore: sync
harlan-zw Feb 26, 2026
b76f0e2
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Feb 26, 2026
fb61483
chore: sync
harlan-zw Feb 26, 2026
b4b7ffc
chore: sync
harlan-zw Feb 26, 2026
440bdb1
chore: sync
harlan-zw Feb 26, 2026
6e89cb9
chore: sync
harlan-zw Feb 26, 2026
b366eaa
chore: sync
harlan-zw Feb 26, 2026
17e7ef7
chore: sync
harlan-zw Feb 26, 2026
6c3add5
Merge remote-tracking branch 'origin/main' into feat/og-image-v6
danielroe Feb 26, 2026
b69a6df
fix: small fixes
danielroe Feb 26, 2026
25f3f96
Merge remote-tracking branch 'origin/main' into feat/og-image-v6
danielroe Feb 26, 2026
347cbcc
chore: reorder
danielroe Feb 26, 2026
8d8a3ee
ci: bump memory
danielroe Feb 26, 2026
8e1c597
Merge remote-tracking branch 'origin/main' into feat/og-image-v6
danielroe Feb 26, 2026
2cdcc35
chore: sync
harlan-zw Feb 26, 2026
114f2e3
Merge remote-tracking branch 'origin/feat/og-image-v6' into feat/og-i…
harlan-zw Feb 26, 2026
499668f
chore: opacity
harlan-zw Feb 26, 2026
4d5afa5
fix: use utc dates, handle division by zero, + add tanstack fixture
danielroe Feb 27, 2026
b196d5a
Merge remote-tracking branch 'origin/main' into feat/og-image-v6
danielroe Feb 27, 2026
a1259f7
chore: lint
danielroe Feb 27, 2026
49a9014
fix: rename og-image snapshot for home path
danielroe Feb 27, 2026
6e62678
fix: remove UnoCSS pipeline exclude that broke a11y tests
danielroe Feb 27, 2026
9d7060c
chore: opps
danielroe Feb 27, 2026
83fa423
fix: preserve UnoCSS default pipeline excludes alongside takumi exclude
danielroe Feb 27, 2026
449232c
ci: switch browser tests to x64 runner for Takumi WASM compat
danielroe Feb 27, 2026
9800fc1
revert: restore browser test ARM runner
danielroe Feb 27, 2026
b1cae8f
chore: bump og image
harlan-zw Feb 28, 2026
81c6227
chore: bump takumi
harlan-zw Feb 28, 2026
f65e30f
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Feb 28, 2026
2382633
chore: sync lock
harlan-zw Feb 28, 2026
42896a1
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Mar 2, 2026
1f12d07
chore: blog post
harlan-zw Mar 2, 2026
ff5c315
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Mar 2, 2026
f794422
chore: sync
harlan-zw Mar 2, 2026
44eb192
chore: sync
harlan-zw Mar 2, 2026
f2de5ad
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Mar 14, 2026
4d97d7f
Merge branch 'main' of github.com:harlan-zw/fork-npmx.dev into feat/o…
harlan-zw Mar 17, 2026
6a86fa3
feat: og images
harlan-zw Mar 17, 2026
533a466
chore: takumi v1 beta
harlan-zw Mar 17, 2026
00b80ec
fix: og image alts
harlan-zw Mar 17, 2026
a2e4207
Merge branch 'main' into feat/og-image-v6
harlan-zw Mar 17, 2026
3a762ab
chore: misc issues
harlan-zw Mar 17, 2026
52ed139
Merge upstream/main into feat/og-image-v7
harlan-zw Mar 27, 2026
c02a6a8
fix: convert remaining pages to defineOgImage API and sync with upstream
harlan-zw Mar 27, 2026
c3687a1
fix: remove unused i18n key and disable long-title validation
harlan-zw Mar 27, 2026
0908bb7
chore: bump nuxt-og-image to 6.2.6, takumi to 1.0.0-beta.20
harlan-zw Mar 27, 2026
295722d
chore: bump deps, fix test build prerender
harlan-zw Mar 27, 2026
a09e45c
chore: clean up PR scope, revert unrelated changes
harlan-zw Mar 27, 2026
95b7ff2
fix: blog OG avatar stacking, remove htmlValidator regexes, add ogIma…
harlan-zw Mar 27, 2026
327c7b8
fix: patch @nuxt/test-utils structuredClone crash with nuxt-og-image v6
harlan-zw Mar 27, 2026
ebe0a5b
fix: remove @nuxt/test-utils patch, fix upstream in nuxt-site-config
harlan-zw Mar 27, 2026
4d97433
fix: patch @nuxt/test-utils structuredClone for cross-context objects
harlan-zw Mar 27, 2026
fe4d826
Merge branch 'main' into feat/og-image-v7
danielroe Mar 27, 2026
8e5041e
[autofix.ci] apply automated fixes
autofix-ci[bot] Mar 27, 2026
0879add
Merge remote-tracking branch 'origin/main' into feat/og-image-v7
harlan-zw Mar 28, 2026
515f99b
chore: reduce PR scope, revert unrelated i18n and branding changes
harlan-zw Mar 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ globalThis['__NUXT_COLOR_MODE__'] ??= {
removeColorScheme: fn(),
}
// @ts-expect-error - dynamic global name
globalThis.defineOgImageComponent = fn()
globalThis.defineOgImage = fn()

// Subscribe to locale changes from storybook-i18n addon (once, outside decorator)
let currentI18nInstance: any = null
Expand Down
4 changes: 4 additions & 0 deletions app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ if (import.meta.client) {
useEventListener(document, 'click', handleModalLightDismiss)
}
}
// title and description will be inferred
// this will be overridden by upstream pages that use different templates
defineOgImage('Page.takumi', {}, { alt: 'npmx — a fast, modern browser for the npm registry' })
</script>

<template>
Expand Down
22 changes: 22 additions & 0 deletions app/components/OgBrand.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<script setup lang="ts">
const props = withDefaults(
defineProps<{
height?: number
}>(),
{
height: 60,
},
)

const width = computed(() => Math.round(props.height * (602 / 170)))
</script>

<template>
<img
src="/logo.svg"
alt="npmx"
:width="width"
:height="height"
:style="{ width: `${width}px`, height: `${height}px` }"
/>
</template>
13 changes: 0 additions & 13 deletions app/components/OgImage/BlogPost.d.vue.ts

This file was deleted.

114 changes: 114 additions & 0 deletions app/components/OgImage/BlogPost.takumi.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script setup lang="ts">
import type { ResolvedAuthor } from '#shared/schemas/blog'

const {
title,
authors = [],
date = '',
} = defineProps<{
title: string
authors?: ResolvedAuthor[]
date?: string
}>()

const formattedDate = computed(() => {
if (!date) return ''
try {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
} catch {
return date
}
})
Comment on lines +14 to +25
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and examine the relevant lines
find . -name "BlogPost.takumi.vue" -type f

Repository: npmx-dev/npmx.dev

Length of output: 105


🏁 Script executed:

# Verify JavaScript Date behavior with invalid dates
node <<'EOF'
// Test 1: Does invalid Date throw when calling toLocaleDateString?
try {
  const invalidDate = new Date('invalid-date-string');
  const result = invalidDate.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'short',
    day: 'numeric',
  });
  console.log('Test 1 - Invalid date toLocaleDateString result:', result);
  console.log('Test 1 - Did NOT throw');
} catch (e) {
  console.log('Test 1 - Threw error:', e.message);
}

// Test 2: Check if getTime() returns NaN for invalid date
const invalidDate = new Date('invalid-date-string');
console.log('Test 2 - getTime() for invalid date:', invalidDate.getTime());
console.log('Test 2 - Number.isNaN(getTime()):', Number.isNaN(invalidDate.getTime()));

// Test 3: Check timezone effect
const testDate = new Date('2024-01-15T00:00:00Z');
const localResult = testDate.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'short',
  day: 'numeric',
});
const utcResult = testDate.toLocaleDateString('en-US', {
  year: 'numeric',
  month: 'short',
  day: 'numeric',
  timeZone: 'UTC',
});
console.log('Test 3 - Without timeZone: UTC:', localResult);
console.log('Test 3 - With timeZone: UTC:', utcResult);
console.log('Test 3 - Are they same?', localResult === utcResult);
EOF

Repository: npmx-dev/npmx.dev

Length of output: 341


🏁 Script executed:

cat -n ./app/components/OgImage/BlogPost.takumi.vue | head -40

Repository: npmx-dev/npmx.dev

Length of output: 1087


🏁 Script executed:

# Check imports at the top of the file
cat -n ./app/components/OgImage/BlogPost.takumi.vue | head -15

Repository: npmx-dev/npmx.dev

Length of output: 443


🏁 Script executed:

# Check if computed is imported in this file
rg "import.*computed" ./app/components/OgImage/BlogPost.takumi.vue

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Read the entire file to check for imports and overall structure
cat ./app/components/OgImage/BlogPost.takumi.vue

Repository: npmx-dev/npmx.dev

Length of output: 3322


Add missing import for computed, and fix invalid date handling with UTC consistency.

The computed function is used but not imported from 'vue', so this code will not work. Additionally, line 17 will not throw for an invalid Date, so the catch block never executes and this can render Invalid Date. For server-side OG image rendering, omitting timeZone: 'UTC' can also shift the calendar day across environments.

Suggested change
 <script setup lang="ts">
+import { computed } from 'vue'
 import type { ResolvedAuthor } from '#shared/schemas/blog'

 const {
   title,
   authors = [],
   date = '',
 } = defineProps<{
   title: string
   authors?: ResolvedAuthor[]
   date?: string
 }>()

 const formattedDate = computed(() => {
   if (!date) return ''
-  try {
-    return new Date(date).toLocaleDateString('en-US', {
-      year: 'numeric',
-      month: 'short',
-      day: 'numeric',
-    })
-  } catch {
-    return date
-  }
+  const parsed = new Date(date)
+  if (Number.isNaN(parsed.getTime())) return date
+
+  return parsed.toLocaleDateString('en-US', {
+    year: 'numeric',
+    month: 'short',
+    day: 'numeric',
+    timeZone: 'UTC',
+  })
 })
📝 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
const formattedDate = computed(() => {
if (!date) return ''
try {
return new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
})
} catch {
return date
}
})
<script setup lang="ts">
import { computed } from 'vue'
import type { ResolvedAuthor } from '#shared/schemas/blog'
const {
title,
authors = [],
date = '',
} = defineProps<{
title: string
authors?: ResolvedAuthor[]
date?: string
}>()
const formattedDate = computed(() => {
if (!date) return ''
const parsed = new Date(date)
if (Number.isNaN(parsed.getTime())) return date
return parsed.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
timeZone: 'UTC',
})
})


const MAX_VISIBLE_AUTHORS = 2

const getInitials = (name: string) =>
name
.split(' ')
.map(n => n[0])
.join('')
.toUpperCase()
.slice(0, 2)

const visibleAuthors = computed(() => {
if (authors.length <= 3) return authors
return authors.slice(0, MAX_VISIBLE_AUTHORS)
})

const extraCount = computed(() => {
if (authors.length <= 3) return 0
return authors.length - MAX_VISIBLE_AUTHORS
})

const formattedAuthorNames = computed(() => {
const allNames = authors.map(a => a.name)
if (allNames.length === 0) return ''
if (allNames.length === 1) return allNames[0]
if (allNames.length === 2) return `${allNames[0]} and ${allNames[1]}`
if (allNames.length === 3) return `${allNames[0]}, ${allNames[1]}, and ${allNames[2]}`
const shown = allNames.slice(0, MAX_VISIBLE_AUTHORS)
const remaining = allNames.length - MAX_VISIBLE_AUTHORS
return `${shown.join(', ')} and ${remaining} others`
})
</script>

<template>
<OgLayout>
<div class="px-15 py-12 flex flex-col justify-center gap-5 h-full">
<OgBrand :height="48" />

<!-- Date + Title -->
<div class="flex flex-col gap-2">
<span v-if="formattedDate" class="text-3xl text-fg-muted">
{{ formattedDate }}
</span>

<div
class="lg:text-6xl text-5xl tracking-tighter font-mono leading-tight"
:style="{ lineClamp: 2, textOverflow: 'ellipsis' }"
>
{{ title }}
</div>
</div>

<!-- Authors -->
<div v-if="authors.length" class="flex items-center gap-4 flex-nowrap">
<!-- Stacked avatars -->
<span class="flex flex-row items-center">
<span
v-for="(author, index) in visibleAuthors"
:key="author.name"
class="flex items-center justify-center rounded-full border border-bg bg-bg-muted overflow-hidden w-12 h-12"
:style="{ marginLeft: index > 0 ? '-20px' : '0' }"
>
<img
v-if="author.avatar"
:src="author.avatar"
:alt="author.name"
width="48"
height="48"
class="w-full h-full object-cover"
/>
<span v-else class="text-5 text-fg-muted font-medium">
{{ getInitials(author.name) }}
</span>
</span>
<!-- +N badge -->
<span
v-if="extraCount > 0"
class="flex items-center justify-center text-lg font-medium text-fg-muted rounded-full border border-bg bg-bg-muted overflow-hidden w-12 h-12"
:style="{ marginLeft: '-20px' }"
>
+{{ extraCount }}
</span>
</span>
<!-- Names -->
<span class="text-6 text-fg-muted font-light">{{ formattedAuthorNames }}</span>
</div>
</div>
</OgLayout>
</template>
142 changes: 0 additions & 142 deletions app/components/OgImage/BlogPost.vue

This file was deleted.

84 changes: 0 additions & 84 deletions app/components/OgImage/Default.vue

This file was deleted.

Loading
Loading