Skip to content

feat: blog#1094

Open
whitep4nth3r wants to merge 78 commits intomainfrom
feat/atproto-blog-fe
Open

feat: blog#1094
whitep4nth3r wants to merge 78 commits intomainfrom
feat/atproto-blog-fe

Conversation

@whitep4nth3r
Copy link
Contributor

@whitep4nth3r whitep4nth3r commented Feb 6, 2026

Merge this on 3rd March.

Kai-ros and others added 26 commits January 30, 2026 13:44
I think nuxt prepare will try to compile standard-site-sync and since the
lexicon types don't exist yet it will fail. We should generate the types
first, and then run prepare.
Also add some breathing room to the top of the blog post
This is a WIP. It only runs once on the client when you refresh the page.
- Combined keyboard shortcuts (settings, compare, blog navigation)
- Merged package.json dependencies (kept main versions, added blog dependencies)
- Added presetTypography and presetRtl to uno config
Co-authored-by: Dominik K. <dominik@rivo.gg>
Co-authored-by: Luke Warlow <luke@warlow.dev>
Co-authored-by: Victoria <vicmdo@gmail.com>
Co-authored-by: Yevhen Husak <yev.husak@gmail.com>
Co-authored-by: Yevhen Husak <gusa4grr@users.noreply.github.com>
Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jialong Lu <48578099+kalu5@users.noreply.github.com>
Co-authored-by: Alexander Schmid <120512287+alexschmd@users.noreply.github.com>
Co-authored-by: James Garbutt <43081j@users.noreply.github.com>
Co-authored-by: Vincent Taverna <vinnymac@gmail.com>
Co-authored-by: Philippe Serhal <philippe.serhal@gmail.com>
Co-authored-by: Garth de Wet <garthofhearts@gmail.com>
Co-authored-by: Alex Savelyev <91429106+alexdln@users.noreply.github.com>
Co-authored-by: Marcus Blättermann <marcus@essenmitsosse.de>
Co-authored-by: Wojciech Maj <kontakt@wojtekmaj.pl>
Co-authored-by: Chase Naples <cnaples79@gmail.com>
Co-authored-by: Felix Schneider <99918022+trueberryless@users.noreply.github.com>
Co-authored-by: btea <2356281422@qq.com>
Co-authored-by: Santosh Yadav <santosh.yadav198613@gmail.com>
Co-authored-by: Vida Xie <vida_2020@163.com>
Co-authored-by: Okinea Dev <hi@okinea.dev>
Co-authored-by: Maher <50132270+abaza738@users.noreply.github.com>
Co-authored-by: Nandkishor Jadoun <183695114+NandkishorJadoun@users.noreply.github.com>
Co-authored-by: abeer0 <47961062+iiio2@users.noreply.github.com>
Co-authored-by: Alec Lloyd Probert <55991794+graphieros@users.noreply.github.com>
Co-authored-by: Stephen Zhou <38493346+hyoban@users.noreply.github.com>
Co-authored-by: Matteo Gabriele <m.gabriele.dev@gmail.com>
Co-authored-by: Mihkel Eidast <mihkel@eida.st>
Co-authored-by: Craig Hart <i@craigary.net>
Co-authored-by: Houssam Elbadissi <skaldebane@gmail.com>
Co-authored-by: Cassidy Williams <1454517+cassidoo@users.noreply.github.com>
Co-authored-by: Cassidy Williams <cassidoo@users.noreply.github.com>
Co-authored-by: Salma Alam-Naylor <52798353+whitep4nth3r@users.noreply.github.com>
Co-authored-by: Bailey Townsend <baileytownsend2323@gmail.com>
Co-authored-by: Denys <40246260+DDeenis@users.noreply.github.com>
Co-authored-by: SHAMIL <64640025+shamilkotta@users.noreply.github.com>
Co-authored-by: Jiří Michel <admin@venty.cz>
Co-authored-by: Lars Kappert <oss@webpro.nl>
Co-authored-by: Jonathan Yeong <hey@jonathanyeong.com>
Co-authored-by: penjj <32674989+penjj@users.noreply.github.com>
Co-authored-by: Nicolas DUBIEN <github@dubien.org>
Co-authored-by: Chris <hizyyv@gmail.com>
Co-authored-by: Josh King <59017897+kn0wn@users.noreply.github.com>
Co-authored-by: Jens Rømer Hesselbjerg <jh.roemer@gmail.com>
Co-authored-by: Nathan Knowler <nathan@knowler.dev>
Co-authored-by: Stanyslas Bres <15731884+sybers@users.noreply.github.com>
Co-authored-by: Trivikram Kamat <16024985+trivikr@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Bobbie Goede <bobbiegoede@gmail.com>
Co-authored-by: Joaquín Sánchez <userquin@gmail.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: TAKAHASHI Shuuji <id@shuuji3.xyz>
Co-authored-by: Tierney Cyren <accounts@bnb.im>
Co-authored-by: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Co-authored-by: Abderrahmen Mhemed <33667872+MhemedAbderrahmen@users.noreply.github.com>
Co-authored-by: abderrahmen.mhemed <abderrahmen.mhemed@wevioo.com>
Co-authored-by: Julien Déramond <juderamond@gmail.com>
Co-authored-by: Roman <dev@rman.dev>
Co-authored-by: shamilkotta <shamilkotta99@gmail.com>
Co-authored-by: rygrit <62373365+RYGRIT@users.noreply.github.com>
Co-authored-by: Vrajpal Jhala <43914725+vrajpal-jhala@users.noreply.github.com>
Co-authored-by: Lars Kappert <lars@webpro.nl>
Co-authored-by: Jaydip Sanghani <91427591+jellydeck@users.noreply.github.com>
Co-authored-by: wr <58406705+onebyte8bits@users.noreply.github.com>
Co-authored-by: wr <wr@wrtekiMacBook-Pro.local>
Co-authored-by: Emanuele Orfanelli <emanueleorfanelli@gmail.com>
Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: rzzf <cszhjh@gmail.com>
Co-authored-by: Florian Heuberger <10813063+Flo0806@users.noreply.github.com>
Co-authored-by: Mikołaj Misztal <46728995+mikouaji@users.noreply.github.com>
Co-authored-by: Nico <98180436+nitodeco@users.noreply.github.com>
Co-authored-by: Benjamín Vicente <62021328+benjavicente@users.noreply.github.com>
Co-authored-by: Nico Kempe <50241630+nicokempe@users.noreply.github.com>
Co-authored-by: SerKo <serko.dev@gmail.com>
Co-authored-by: Scott Wu <sw@scottwu.ca>
Co-authored-by: Pascal Küsgen <pascalkuesgen@gmail.com>
Co-authored-by: Luke Warlow <lwarlow@igalia.com>
Co-authored-by: Idris Gadi <85882535+IdrisGit@users.noreply.github.com>
@vercel
Copy link

vercel bot commented Feb 6, 2026

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

Project Deployment Actions Updated (UTC)
npmx.dev Ready Ready Preview, Comment Feb 7, 2026 5:41pm
npmx.dev-vercel Ready Ready Preview, Comment Feb 7, 2026 5:41pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
docs.npmx.dev Ignored Ignored Preview Feb 7, 2026 5:41pm
npmx-lunaria Ignored Ignored Feb 7, 2026 5:41pm

Request Review

@github-actions
Copy link

github-actions bot commented Feb 6, 2026

Lunaria Status Overview

🌕 This pull request will trigger status changes.

Learn more

By default, every PR changing files present in the Lunaria configuration's files property will be considered and trigger status changes accordingly.

You can change this by adding one of the keywords present in the ignoreKeywords property in your Lunaria configuration file in the PR's title (ignoring all files) or by including a tracker directive in the merged commit's description.

Tracked Files

File Note
lunaria/files/en-GB.json Localization changed, will be marked as complete. 🔄️
lunaria/files/en-US.json Source changed, localizations will be marked as outdated.
lunaria/files/fr-FR.json Localization changed, will be marked as complete. 🔄️
Warnings reference
Icon Description
🔄️ The source for this localization has been updated since the creation of this pull request, make sure all changes in the source have been applied.

shuuji3 and others added 21 commits February 7, 2026 15:20
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Roe <daniel@roe.dev>
)

Co-authored-by: Robin <robin.kehl@singular-it.de>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Nathan Knowler <nathan@knowler.dev>
Co-authored-by: Daniel Roe <daniel@roe.dev>
Co-authored-by: Willow (GHOST) <git@willow.sh>
Co-authored-by: Daniel Roe <daniel@roe.dev>
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: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
app/pages/privacy.vue (2)

29-32: ⚠️ Potential issue | 🟡 Minor

Remove the per-button focus-visible utility.
Global button focus-visible styling already exists, so this inline utility should be removed.

Proposed fix
-            class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
+            class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded shrink-0"

Based on learnings: In the npmx.dev project, ensure that focus-visible styling for button and select elements is implemented globally in app/assets/main.css with the rule: button:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }. Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements in Vue templates or components (e.g., AccessControls.vue).


29-34: ⚠️ Potential issue | 🟡 Minor

Guard router.options.history.state access to prevent undefined errors on first render.

router.options.history.state is undefined during SSR and on initial render (before any client-side navigation) because Vue Router's memory history doesn't create browser history state. Accessing .back directly will throw a TypeError.

Proposed fix
-            v-show="router.options.history.state.back !== null"
+            v-show="router.options.history.state?.back != null"
app/pages/about.vue (2)

52-55: ⚠️ Potential issue | 🟡 Minor

Remove the per-button focus-visible utility.
Global button focus-visible styling already exists, so this inline utility should be removed.

Proposed fix
-            class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded focus-visible:outline-accent/70 shrink-0"
+            class="inline-flex items-center gap-2 font-mono text-sm text-fg-muted hover:text-fg transition-colors duration-200 rounded shrink-0"

Based on learnings: In the npmx.dev project, ensure that focus-visible styling for button and select elements is implemented globally in app/assets/main.css with the rule: button:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }. Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements in Vue templates or components (e.g., AccessControls.vue).


52-57: ⚠️ Potential issue | 🟠 Major

Guard history.state access with client-side execution for SSR compatibility.

Vue Router 4 uses memory history during server-side rendering in Nuxt, so router.options.history.state is undefined until the app hydrates in the browser. The optional chaining prevents a runtime error, but you should use process.client to ensure the button logic only runs client-side, avoiding implicit undefined checks during SSR.

Preferred fix
-            v-show="router.options.history.state.back !== null"
+            v-show="process.client && router.options.history.state?.back != null"

Alternatively, move this logic into an onMounted() hook to delay execution until after hydration.

app/components/Package/Versions.vue (1)

561-564: ⚠️ Potential issue | 🟡 Minor

Remove the per-button focus-visible utility on the major-group toggle.
Global button focus-visible styling already exists, so this inline utility should be removed.

Proposed fix
-                      class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors shrink-0 focus-visible:outline-accent/70 rounded-sm"
+                      class="w-4 h-4 flex items-center justify-center text-fg-subtle hover:text-fg transition-colors shrink-0 rounded-sm"

Based on learnings: In the npmx.dev project, ensure that focus-visible styling for button and select elements is implemented globally in app/assets/main.css with the rule: button:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }. Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements in Vue templates or components (e.g., AccessControls.vue).

app/components/Package/DownloadAnalytics.vue (2)

977-977: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from select element.

The select element has focus-visible:outline-accent/70 applied inline, but this project applies focus-visible styling globally via main.css for buttons and selects. Remove the inline utility class to maintain consistency.

Based on learnings: "In the npmx.dev project, focus-visible styling for buttons and selects is applied globally via main.css with: button:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }."


1035-1035: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from button element.

The reset button has focus-visible:outline-accent/70 applied inline, which conflicts with the project's global focus-visible styling rule for buttons.

Based on learnings: "In the npmx.dev project, ensure that focus-visible styling for button and select elements is implemented globally in app/assets/main.css... Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements."

app/components/Header/ConnectorModal.vue (1)

71-77: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from disconnect button.

The disconnect button has focus-visible:outline-accent/70 applied inline, which conflicts with the project's global focus-visible styling for buttons.

Based on learnings: "In the npmx.dev project, focus-visible styling for buttons and selects is applied globally via main.css."

app/components/Package/Dependencies.vue (3)

225-236: ⚠️ Potential issue | 🟠 Major

Button missing essential styling classes.

The "show all" button for optional dependencies has been stripped down to just mt-2 truncate, losing the text colour, hover states, and transition styling that the equivalent buttons at lines 136 and 184 have. This appears to be an unintentional regression.

🐛 Proposed fix to restore consistent button styling
       <button
         v-if="sortedOptionalDependencies.length > 10 && !optionalDepsExpanded"
         type="button"
-        class="mt-2 truncate"
+        class="mt-2 font-mono text-xs text-fg-muted hover:text-fg transition-colors duration-200 rounded"
         `@click`="optionalDepsExpanded = true"
       >

133-144: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from button.

The "show all" button has focus-visible:outline-accent/70 applied inline, which should be handled by the global CSS rule.

Based on learnings: "In the npmx.dev project, focus-visible styling for buttons and selects is applied globally via main.css."


181-192: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from button.

Same issue as the dependencies "show all" button - the inline focus-visible class should be removed in favour of the global styling.

app/components/Package/InstallScripts.vue (1)

44-63: ⚠️ Potential issue | 🟡 Minor

Remove inline focus-visible utility from button.

The expandable NPX packages button has focus-visible:outline-accent/70 applied inline. Per project conventions, this should be handled by the global CSS rule.

Based on learnings: "In the npmx.dev project, focus-visible styling for buttons and selects is applied globally via main.css."

app/composables/usePackageComparison.ts (1)

133-144: ⚠️ Potential issue | 🟠 Major

Encode package names in the likes endpoint.

Using raw name will break scoped packages (@scope/pkg) because / becomes a path segment, so likes won’t load. Reuse encodePackageName here.

💡 Suggested fix
-              $fetch<PackageLikes>(`/api/social/likes/${name}`).catch(() => null),
+              $fetch<PackageLikes>(`/api/social/likes/${encodePackageName(name)}`).catch(() => null),
scripts/compare-translations.ts (1)

211-231: ⚠️ Potential issue | 🟠 Major

Avoid writing merged locale content back into country-variant files.

When mergeLocale is true, loadJson returns a merged base+variant object. Writing newContent back on --fix (or on extra-key removal) will expand the variant file with base translations, which breaks the “variants only contain differences” rule. Consider preventing --fix writes for merge locales, or writing a raw (non-merged) variant object instead.

💡 Minimal guard to prevent `--fix` from overwriting variant files
-  const newContent = syncLocaleData(referenceContent, targetContent, stats, fix)
+  const effectiveFix = fix && !localeInfo.mergeLocale
+  const newContent = syncLocaleData(referenceContent, targetContent, stats, effectiveFix)

-  if (stats.extra.length > 0 || fix) {
+  if (stats.extra.length > 0 || effectiveFix) {
     writeFileSync(filePath, JSON.stringify(newContent, null, 2) + '\n', 'utf-8')
   }

Based on learnings: “When using country locale variants (e.g., es-419, es-ES), place only translations that differ from the base language in variant JSON files.”

🧹 Nitpick comments (16)
lunaria/files/de-DE.json (1)

454-473: Terminology inconsistency with account_menu.ops.

The queue section now uses "Aktion/Aktionen" consistently, which is good. However, account_menu.ops on line 823 still uses "Operation | Operationen". This may confuse users seeing different terminology for the same concept.

Consider updating line 823 to match:

"ops": "{count} Aktion | {count} Aktionen"
.github/workflows/ci.yml (1)

89-93: Consider aligning the approach for uploading test results across jobs.

The Unit tests job uses codecov/test-results-action@v1 whilst the Component tests job (lines 120-125) uses codecov/codecov-action@v5 with report_type: test_results. Both approaches work, but the inconsistency may cause confusion for maintainers.

If the migration to codecov-action with report_type is intentional, consider updating this step as well for consistency. If not, consider reverting the Component tests job to use test-results-action.

i18n/locales/de-DE.json (1)

5-5: Keep de-DE as an override-only locale file.

This variant should only contain keys that differ from the base de.json; otherwise maintenance and divergence risk increase. Please move unchanged strings to the base file and leave only overrides here.

Based on learnings: “In the npmx.dev project, when using country locale variants (e.g., es-419, es-ES), place only translations that differ from the base language in variant JSON files; the base file is loaded first, then the variant file overlays any keys it defines.”

server/api/registry/badge/[type]/[...pkg].get.ts (1)

298-318: Consider conditionally rendering the label text element when empty.

When finalLabel.trim() is empty, leftWidth is set to 0, but the <text> element at line 316 still renders with empty content. This works correctly but leaves a redundant SVG element.

🔧 Optional: Skip label text rendering when empty
           <g text-anchor="middle" font-family="'Geist', system-ui, -apple-system, sans-serif" font-size="11">
-            <text x="${leftWidth / 2}" y="14" fill="#ffffff">${finalLabel}</text>
+            ${leftWidth > 0 ? `<text x="${leftWidth / 2}" y="14" fill="#ffffff">${finalLabel}</text>` : ''}
             <text x="${leftWidth + rightWidth / 2}" y="14" fill="#ffffff">${finalValue}</text>
           </g>
server/utils/shiki.ts (1)

79-79: Consider: Narrowed regex may miss edge cases.

The regex now requires exactly one space before style="...". If Shiki's output ever varies (e.g., multiple spaces or tabs), the style attribute won't be stripped. This is likely fine given Shiki's consistent output, but worth noting.

♻️ Optional: More flexible whitespace matching
-      html = html.replace(/<pre([^>]*) style="[^"]*"/, '<pre$1')
+      html = html.replace(/<pre([^>]*)\s+style="[^"]*"/, '<pre$1')
app/components/Filter/Chips.vue (2)

22-29: Consider using global focus-visible styling for buttons.

This button uses inline focus-visible:ring-* classes, but the project has global focus-visible styling for buttons in main.css using outline: 2px solid var(--accent). Using inline utilities here may create inconsistent focus styling across the app.

If the small icon button requires custom focus treatment, this exception is reasonable; otherwise, consider relying on the global rule.

Based on learnings: "focus-visible styling for button and select elements is implemented globally in app/assets/main.css... Do not apply per-element inline utility classes... on these elements."


33-40: Same focus-visible consideration applies here.

The clear-all button also uses inline focus-visible:ring-* classes instead of relying on the global button focus styling.

scripts/rtl-checker.ts (2)

39-43: Add type guard for array access to ensure type safety.

Per coding guidelines, array access by index should be checked. While the loop bounds guarantee lines[i] exists at runtime, TypeScript cannot verify this. Consider using a non-null assertion or a type guard.

🔧 Suggested fix
   const lines = file.split('\n')
   for (let i = 0; i < lines.length; i++) {
     idx = i + 1
-    line = lines[i]
+    line = lines[i]!
     await uno.generate(line)
   }

Alternatively, use for...of with entries() for cleaner iteration:

-  const lines = file.split('\n')
-  for (let i = 0; i < lines.length; i++) {
-    idx = i + 1
-    line = lines[i]
+  for (const [i, currentLine] of file.split('\n').entries()) {
+    idx = i + 1
+    line = currentLine
     await uno.generate(line)
   }

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index".


12-18: Consider adding error handling for file read operations.

The readFile call at line 18 could throw if the file is inaccessible or has encoding issues. While this is a build/CI script, wrapping in a try/catch would provide clearer error messages and prevent the entire check from failing on a single problematic file.

🛡️ Suggested improvement
 async function checkFile(path: Dirent): Promise<string | undefined> {
   if (path.isDirectory() || !path.name.endsWith('.vue')) {
     return undefined
   }

   const filename = resolve(APP_DIRECTORY, path.parentPath, path.name)
-  const file = await readFile(filename, 'utf-8')
+  let file: string
+  try {
+    file = await readFile(filename, 'utf-8')
+  } catch (error) {
+    console.error(`${COLORS.yellow}⚠️ Could not read ${filename}: ${error}${COLORS.reset}`)
+    return undefined
+  }

Based on learnings: "Use error handling patterns consistently".

app/pages/~[username]/orgs.vue (1)

84-84: Remove the stray top‑level error assignment.
Line 84 pre-populates the error state before any request; keeping the initial null simplifies state flow.

🧹 Suggested clean-up
-error.value = $t('header.orgs_dropdown.error')
i18n/locales/fr-FR.json (3)

57-60: Empty translation objects may indicate incomplete work.

The blog.author object is empty. If this is intentional (relying on base locale fallback), consider adding a comment or removing the empty object. If translations are pending, consider adding placeholder text or a TODO.


190-190: Empty likes object.

The package.likes object is empty. Same consideration as above regarding fallback behaviour vs incomplete translations.


959-972: Privacy policy section has empty nested objects.

The privacy_policy section contains multiple empty objects (cookies.what_are, cookies.types, etc.). If these are placeholders for future translations, consider either:

  • Removing them until translations are ready
  • Adding the actual French translations
  • Adding a comment indicating they're pending

Empty objects will fall back to the base locale, which may be the intended behaviour.

app/components/Header/AuthModal.client.vue (1)

9-14: Consider edge case for empty locale value.

If locale.value is an empty string, split('-')[0] returns an empty string (not undefined), so the ?? 'en' fallback won't trigger. The localeSubPath check handles this since an empty string won't match the array, but for clarity you might want to handle this explicitly.

💡 Suggested improvement
 const { locale } = useI18n()
-const currentLang = locale.value.split('-')[0] ?? 'en'
+const currentLang = locale.value.split('-')[0] || 'en'
 const localeSubPath = ['ko', 'pt', 'ja'].includes(currentLang) ? currentLang : ''
lunaria/prepare-json-files.ts (1)

75-77: Consider adding error handling for JSON parsing.

The loadJsonFile function uses JSON.parse without a try-catch. If a locale file contains invalid JSON, this will throw an unhandled error that may be difficult to diagnose.

💡 Optional: Add error context for debugging
 async function loadJsonFile<T = unknown>(name: string): Promise<T> {
-  return JSON.parse(await fs.readFile(path.resolve(`${localesFolder}/${name}`), 'utf8'))
+  const filePath = path.resolve(`${localesFolder}/${name}`)
+  try {
+    return JSON.parse(await fs.readFile(filePath, 'utf8'))
+  } catch (error) {
+    throw new Error(`Failed to load/parse locale file: ${filePath}`, { cause: error })
+  }
 }
app/components/Compare/ComparisonGrid.vue (1)

41-48: Potential v-for key collision when comparing different versions of the same package.

Using col.name as the key could cause issues if comparing different versions of the same package (e.g., vue@3.5 vs vue@3.4), as both would share the same key.

Consider using a composite key that includes the version when present:

♻️ Suggested fix for unique key
-        <div v-for="col in columns" :key="col.name" class="comparison-cell comparison-cell-header">
+        <div v-for="col in columns" :key="col.version ? `${col.name}@${col.version}` : col.name" class="comparison-cell comparison-cell-header">

Comment on lines 120 to 125
- name: ⬆︎ Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
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 | 🟡 Minor

Missing if: ${{ !cancelled() }} condition will skip test results upload on failure.

The Unit tests job (line 90) includes if: ${{ !cancelled() }} to ensure test results are uploaded even when tests fail. This step is missing that condition, so component test results won't be reported to Codecov when the tests fail — precisely when you'd want visibility.

Additionally, the step name says "Upload coverage reports" but report_type: test_results indicates it uploads test results, not coverage. Consider renaming to "Upload test results to Codecov" for clarity (matching the Unit tests job naming).

🔧 Proposed fix
       - name: ⬆︎ Upload coverage reports to Codecov
+        if: ${{ !cancelled() }}
         uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
         with:
           report_type: test_results
         env:
           CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Optionally, also rename the step for consistency:

-      - name: ⬆︎ Upload coverage reports to Codecov
+      - name: ⬆︎ Upload test results to Codecov
📝 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
- name: ⬆︎ Upload coverage reports to Codecov
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: ⬆︎ Upload test results to Codecov
if: ${{ !cancelled() }}
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
with:
report_type: test_results
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Comment on lines 190 to 197
:focus-visible,
:-moz-focusring {
/* weird Firefox behavior makes it necessary to add `!important`
or otherwise the selector would need to be more specific,
which it explicitly should not be. */
outline: 2px solid var(--accent) !important;
outline-offset: 2px !important;
}
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 | 🟡 Minor

Restore the button/select focus-visible rule with border-radius.

The new generic :focus-visible rule drops the project’s agreed button/select focus styling (including the 4px radius). Please reintroduce the explicit rule so button/select focus remains consistent.

💡 Suggested fix
 :focus-visible,
 :-moz-focusring {
   /* weird Firefox behavior makes it necessary to add `!important` 
   or otherwise the selector would need to be more specific,
   which it explicitly should not be. */
   outline: 2px solid var(--accent) !important;
   outline-offset: 2px !important;
 }
+
+button:focus-visible,
+select:focus-visible {
+  outline: 2px solid var(--accent) !important;
+  outline-offset: 2px !important;
+  border-radius: 4px;
+}

Based on learnings: “Focus-visible styling for button and select elements is applied globally via main.css with: button:focus-visible, select:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }.”

Comment on lines 35 to 42
class="group cursor-pointer inline-flex gap-x-1 items-center justify-center font-mono border border-border rounded-md transition-all duration-200 disabled:(opacity-40 cursor-not-allowed border-transparent)"
:class="{
'text-sm px-4 py-2': size === 'medium',
'text-xs px-2 py-0.5': size === 'small',
'bg-transparent text-fg hover:enabled:(bg-fg/10) focus-visible:enabled:(bg-fg/10) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(bg-fg text-bg/50))':
variant === 'secondary',
'text-bg bg-fg hover:enabled:(bg-fg/50) focus-visible:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
variant === 'primary',
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 | 🟡 Minor

Remove inline focus-visible styling from the button root.

This conflicts with the project rule that button focus-visible styling should be handled globally in app/assets/main.css.

Suggested fix
-      'bg-transparent text-fg hover:enabled:(bg-fg/10) focus-visible:enabled:(bg-fg/10) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(bg-fg text-bg/50))':
+      'bg-transparent text-fg hover:enabled:(bg-fg/10) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(bg-fg text-bg/50))':
         variant === 'secondary',
-      'text-bg bg-fg hover:enabled:(bg-fg/50) focus-visible:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
+      'text-bg bg-fg hover:enabled:(bg-fg/50) aria-pressed:(bg-fg text-bg border-fg hover:enabled:(text-bg/50))':
         variant === 'primary',

Based on learnings: “focus-visible styling for button/select elements is implemented globally in app/assets/main.css… Do not apply per-element inline focus-visible utilities.”

Comment on lines 1 to 5
<script setup lang="ts">
const props = defineProps<{
as?: string | Component
}>()
</script>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check nuxt.config for auto-import configuration
find . -name "nuxt.config.*" -type f | head -5

Repository: npmx-dev/npmx.dev

Length of output: 99


🏁 Script executed:

# Look at the actual file to see its current state
cat -n app/components/Button/Group.vue 2>/dev/null || echo "File not found at that location"

Repository: npmx-dev/npmx.dev

Length of output: 508


🏁 Script executed:

# Search for other Vue files that use Component type to understand project pattern
rg "Component" app/components --type vue -B 2 -A 2 | head -50

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Check if Component is imported explicitly in the project
rg "import.*Component.*from.*vue" app/ --type vue | head -20

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Check the nuxt.config.ts for auto-import configuration
cat nuxt.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 8653


🏁 Script executed:

# Search for Component usage in .vue files using find and grep
find app/components -name "*.vue" -exec grep -l "Component" {} \; | head -10

Repository: npmx-dev/npmx.dev

Length of output: 122


🏁 Script executed:

# Look for Component imports in any .vue files
find app/components -name "*.vue" -exec grep -A2 "import.*Component" {} + | head -30

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check tsconfig and type declaration files
find . -maxdepth 3 -name "tsconfig.json" -o -name "*.d.ts" | grep -E "(tsconfig|global)" | head -10

Repository: npmx-dev/npmx.dev

Length of output: 96


🏁 Script executed:

# Look at the other component that uses Component to see if it has the import
cat app/components/Tag/Static.vue

Repository: npmx-dev/npmx.dev

Length of output: 639


🏁 Script executed:

# Check if there's a global type declaration file that might auto-import Component
find . -name "*.d.ts" -type f -exec grep -l "Component" {} \; | head -10

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check tsconfig to understand type checking strictness
cat tsconfig.json | head -50

Repository: npmx-dev/npmx.dev

Length of output: 380


Import Component type from Vue for strict type-safety.

The Component type is used in the props definition but not explicitly imported. This violates the project's requirement for strictly type-safe code. Import it from Vue.

 <script setup lang="ts">
+import type { Component } from 'vue'
+
 const props = defineProps<{
   as?: string | Component
 }>()
📝 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
<script setup lang="ts">
const props = defineProps<{
as?: string | Component
}>()
</script>
<script setup lang="ts">
import type { Component } from 'vue'
const props = defineProps<{
as?: string | Component
}>()
</script>

Comment on lines 115 to 123
<button
type="button"
:aria-label="copied ? $t('connector.modal.copied') : $t('connector.modal.copy_command')"
class="ms-auto text-fg-subtle hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded"
class="ms-auto text-fg-subtle p-1.5 -m-1.5 hover:text-fg transition-colors duration-200 focus-visible:outline-accent/70 rounded"
@click="copy('pnpm npmx-connector')"
>
<span v-if="!copied" class="i-carbon:copy w-5 h-5" aria-hidden="true" />
<span v-else class="i-carbon:checkmark w-5 h-5 text-green-500" aria-hidden="true" />
<span v-if="!copied" class="i-carbon:copy block w-5 h-5" aria-hidden="true" />
<span v-else class="i-carbon:checkmark block w-5 h-5 text-green-500" aria-hidden="true" />
</button>
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 | 🟡 Minor

Remove inline focus-visible utility from copy button.

The copy button has focus-visible:outline-accent/70 applied inline. Per project conventions, focus-visible styling for buttons should rely on the global CSS rule.

Based on learnings: "Do not apply per-element inline utility classes like focus-visible:outline-accent/70 on these elements in Vue templates or components."

import { isCI, provider } from 'std-env'

export default defineNuxtConfig({
extensions: ['.md'],
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 | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Nuxt extensions config option default behaviour override .vue

💡 Result:

  • In Nuxt, nuxt.config has a top-level extensions option that controls which file extensions Nuxt’s resolver will try when you import/resolve a path without an extension. The default includes ".vue" (along with ".js", ".jsx", ".mjs", ".ts", ".tsx"). [1]
  • If you set extensions yourself, you replace the default list—it’s not automatically merged—so if you omit ".vue", Nuxt will no longer resolve FooFoo.vue automatically. [1]
  • Nuxt Kit’s resolvePath explicitly describes these as the extensions “to try”, i.e., they’re attempted in the provided list order (so order affects which file “wins” when multiple exist). [2]

Example (keep defaults but prefer .vue first):

// nuxt.config.ts
export default defineNuxtConfig({
  extensions: ['.vue', '.ts', '.tsx', '.js', '.mjs', '.jsx'],
})

Example (override and effectively “disable” .vue auto-resolution):

export default defineNuxtConfig({
  extensions: ['.ts', '.tsx', '.js', '.mjs', '.jsx'], // no ".vue"
})

References: [1] Nuxt config extensions default list and meaning, [2] Nuxt Kit resolvePath extensions “to try” behavior.


🏁 Script executed:

# Find and read the nuxt.config.ts file to understand the full context
find . -name "nuxt.config.ts" -type f

Repository: npmx-dev/npmx.dev

Length of output: 99


🏁 Script executed:

# Read the nuxt.config.ts file to see the current extensions configuration
cat -n ./nuxt.config.ts | head -30

Repository: npmx-dev/npmx.dev

Length of output: 1306


🏁 Script executed:

# Check if Vue components/files are being imported or used in the project
rg "\.vue['\"]|from '[^']*\.vue'|from \"[^\"]*\.vue\"" --type ts --type tsx --type vue | head -20

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Also check if there are .vue files in the project
fd -e vue | head -20

Repository: npmx-dev/npmx.dev

Length of output: 702


🏁 Script executed:

# Check how .vue files are being imported in the codebase - with or without extension
rg "from ['\"]\.?\.?[^'\"]*\.vue['\"]|import ['\"]\.?\.?[^'\"]*\.vue['\"]" | head -20

Repository: npmx-dev/npmx.dev

Length of output: 1646


🏁 Script executed:

# Check imports without extension to see if auto-resolution is relied upon
rg "from ['\"]~/" --type ts | head -20

Repository: npmx-dev/npmx.dev

Length of output: 2261


🏁 Script executed:

# Check for imports without explicit extensions (which would rely on auto-resolution)
rg "from ['\"]~/" --type ts -A 1 | grep -v "\.vue['\"]" | grep -v "\.ts['\"]" | grep -v "\.tsx['\"]" | grep -v "\.js['\"]" | grep -v "\.mjs['\"]" | head -30

Repository: npmx-dev/npmx.dev

Length of output: 1447


🏁 Script executed:

# Check if any composables or utils are imported without extension
rg "from ['\"]~/(composables|utils)" --type ts | grep -v "\\.vue" | grep -v "\\.ts" | head -20

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check if there are any composables/utils being imported without extension (relying on auto-resolution)
rg "from ['\"]~/(composables|utils)/" -A 0 | grep -v "\\." | head -10

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Double check: search for imports that don't have explicit file extensions
rg "import.*from ['\"]~/?[^'\"]*['\"]" --type ts | grep -v "\\.vue" | grep -v "\\.ts" | grep -v "\\.js" | grep -v "\\.mjs" | head -15

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check what file types are actually being imported in the codebase
rg "from ['\"]" --type ts | sed -E "s/.*from ['\"]([^'\"]*)['\"].*/\1/" | sort | uniq -c | sort -rn | head -30

Repository: npmx-dev/npmx.dev

Length of output: 768


🏁 Script executed:

# Check if the project actually needs .md file resolution or if it's just for build/parsing
rg "\\.md" nuxt.config.ts -B 5 -A 5

Repository: npmx-dev/npmx.dev

Length of output: 572


Include .vue in the extensions array.

Setting extensions: ['.md'] replaces Nuxt's default extension list entirely, which breaks auto-resolution for .vue files. Although the codebase currently uses explicit imports (e.g., import Component from '~/components/Component.vue'), this configuration is non-idiomatic and prevents implicit resolution from working. Add .vue (and optionally .markdown if needed for variant Markdown files):

Recommended change
-  extensions: ['.md'],
+  extensions: ['.vue', '.md'],

Or to be more explicit with full defaults:

-  extensions: ['.md'],
+  extensions: ['.vue', '.ts', '.tsx', '.js', '.mjs', '.jsx', '.md'],
📝 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
extensions: ['.md'],
extensions: ['.vue', '.md'],

import * as v from 'valibot'

const NPM_USERNAME_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i
const NPM_USERNAME_RE = /^[a-z0-9]([\w.-]*[a-z0-9])?$/i
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 | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

npm username allowed characters validation rules official

💡 Result:

Official npm documentation states that an npm username must be lowercase and may contain hyphens (-) and numerals (0-9). In other words, the allowed characters are a-z, 0-9, and - (with no uppercase letters). [1]

Source

  • [1] npm Docs — “Creating a new user account on the public registry” (username field requirements) (docs.npmjs.com)

Citations:


Fix regex to reject uppercase letters, underscores, and dots in npm usernames.

The updated regex now accepts characters that npm usernames do not allow. According to npm's official documentation, usernames must be lowercase and can only contain hyphens and numerals—no uppercase letters, underscores, or dots. The current regex uses the case-insensitive flag (i) and character class [\w.-]*, which accept uppercase letters, underscores, and dots. This will accept usernames that npm would reject.

Comment on lines 1 to 44
import { describe, expect, it, vi, beforeEach } from 'vitest'

const mockRedisSet = vi.fn()
const mockRedisGet = vi.fn()
const mockRedisDel = vi.fn()

vi.mock('@upstash/redis', () => ({
Redis: class {
set = mockRedisSet
get = mockRedisGet
del = mockRedisDel
},
}))

const mockLocalLock = vi.fn()
vi.mock('@atproto/oauth-client-node', () => ({
requestLocalLock: mockLocalLock,
}))

const mockConfig = {
upstash: {
redisRestUrl: '',
redisRestToken: '',
},
}
vi.stubGlobal('useRuntimeConfig', () => mockConfig)

const LOCK_UUID = '00000000-0000-0000-0000-000000000000'
vi.spyOn(crypto, 'randomUUID').mockReturnValue(LOCK_UUID)

const { getOAuthLock } = await import('../../../../../server/utils/atproto/lock')

function getUpstashLock() {
mockConfig.upstash.redisRestUrl = 'https://redis.example.com'
mockConfig.upstash.redisRestToken = 'token-123'
return getOAuthLock()
}

describe('lock', () => {
beforeEach(() => {
vi.clearAllMocks()
mockConfig.upstash.redisRestUrl = ''
mockConfig.upstash.redisRestToken = ''
})
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the test file exists and view its content
git ls-files | grep -E 'test/unit/server/utils/atproto/lock\.spec\.ts'

Repository: npmx-dev/npmx.dev

Length of output: 104


🏁 Script executed:

# Check for Vitest configuration files
git ls-files | grep -E '(vitest|vite)\.config\.(ts|js)' | head -20

Repository: npmx-dev/npmx.dev

Length of output: 77


🏁 Script executed:

# Also check package.json for any Vitest config
git ls-files | grep -E 'package\.json$'

Repository: npmx-dev/npmx.dev

Length of output: 108


🏁 Script executed:

# Read the test file to confirm the code state
cat -n test/unit/server/utils/atproto/lock.spec.ts | head -80

Repository: npmx-dev/npmx.dev

Length of output: 2940


🏁 Script executed:

# Read the vitest config to check for restoreMocks and unstubGlobals settings
cat -n vitest.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 2817


🏁 Script executed:

# Check package.json for any vitest config
cat -n package.json | head -100

Repository: npmx-dev/npmx.dev

Length of output: 4610


🏁 Script executed:

# Check Vitest documentation on defaults by searching for any setup or config overrides
rg -i 'restoreMocks|unstubGlobals' --type ts --type js --type json

Repository: npmx-dev/npmx.dev

Length of output: 43


Move global stubs and spies into beforeEach to prevent test isolation leaks.

vi.stubGlobal('useRuntimeConfig', ...) (line 26) and vi.spyOn(crypto, 'randomUUID', ...) (line 29) are set at module scope and never restored; vi.clearAllMocks() only resets call history. This allows mocked globals to persist across test files. Move these into beforeEach and restore them in afterEach using vi.restoreAllMocks() and vi.unstubAllGlobals().

Proposed change
-import { describe, expect, it, vi, beforeEach } from 'vitest'
+import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
@@
-vi.stubGlobal('useRuntimeConfig', () => mockConfig)
-
 const LOCK_UUID = '00000000-0000-0000-0000-000000000000'
-vi.spyOn(crypto, 'randomUUID').mockReturnValue(LOCK_UUID)
@@
   beforeEach(() => {
     vi.clearAllMocks()
+    vi.stubGlobal('useRuntimeConfig', () => mockConfig)
+    vi.spyOn(crypto, 'randomUUID').mockReturnValue(LOCK_UUID)
     mockConfig.upstash.redisRestUrl = ''
     mockConfig.upstash.redisRestToken = ''
   })
+  afterEach(() => {
+    vi.restoreAllMocks()
+    vi.unstubAllGlobals()
+  })
📝 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
import { describe, expect, it, vi, beforeEach } from 'vitest'
const mockRedisSet = vi.fn()
const mockRedisGet = vi.fn()
const mockRedisDel = vi.fn()
vi.mock('@upstash/redis', () => ({
Redis: class {
set = mockRedisSet
get = mockRedisGet
del = mockRedisDel
},
}))
const mockLocalLock = vi.fn()
vi.mock('@atproto/oauth-client-node', () => ({
requestLocalLock: mockLocalLock,
}))
const mockConfig = {
upstash: {
redisRestUrl: '',
redisRestToken: '',
},
}
vi.stubGlobal('useRuntimeConfig', () => mockConfig)
const LOCK_UUID = '00000000-0000-0000-0000-000000000000'
vi.spyOn(crypto, 'randomUUID').mockReturnValue(LOCK_UUID)
const { getOAuthLock } = await import('../../../../../server/utils/atproto/lock')
function getUpstashLock() {
mockConfig.upstash.redisRestUrl = 'https://redis.example.com'
mockConfig.upstash.redisRestToken = 'token-123'
return getOAuthLock()
}
describe('lock', () => {
beforeEach(() => {
vi.clearAllMocks()
mockConfig.upstash.redisRestUrl = ''
mockConfig.upstash.redisRestToken = ''
})
import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'
const mockRedisSet = vi.fn()
const mockRedisGet = vi.fn()
const mockRedisDel = vi.fn()
vi.mock('@upstash/redis', () => ({
Redis: class {
set = mockRedisSet
get = mockRedisGet
del = mockRedisDel
},
}))
const mockLocalLock = vi.fn()
vi.mock('@atproto/oauth-client-node', () => ({
requestLocalLock: mockLocalLock,
}))
const mockConfig = {
upstash: {
redisRestUrl: '',
redisRestToken: '',
},
}
const LOCK_UUID = '00000000-0000-0000-0000-000000000000'
const { getOAuthLock } = await import('../../../../../server/utils/atproto/lock')
function getUpstashLock() {
mockConfig.upstash.redisRestUrl = 'https://redis.example.com'
mockConfig.upstash.redisRestToken = 'token-123'
return getOAuthLock()
}
describe('lock', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.stubGlobal('useRuntimeConfig', () => mockConfig)
vi.spyOn(crypto, 'randomUUID').mockReturnValue(LOCK_UUID)
mockConfig.upstash.redisRestUrl = ''
mockConfig.upstash.redisRestToken = ''
})
afterEach(() => {
vi.restoreAllMocks()
vi.unstubAllGlobals()
})

Comment on lines 33 to 69
function getUpstashLock() {
mockConfig.upstash.redisRestUrl = 'https://redis.example.com'
mockConfig.upstash.redisRestToken = 'token-123'
return getOAuthLock()
}

describe('lock', () => {
beforeEach(() => {
vi.clearAllMocks()
mockConfig.upstash.redisRestUrl = ''
mockConfig.upstash.redisRestToken = ''
})

it('returns local lock when upstash is not configured', () => {
const lock = getOAuthLock()
expect(lock).toBe(mockLocalLock)
})

it('returns local lock when only redisRestUrl is set', () => {
mockConfig.upstash.redisRestUrl = 'https://totally-a-redis-server.com'
const lock = getOAuthLock()
expect(lock).toBe(mockLocalLock)
})

it('returns local lock when only redisRestToken is set', () => {
mockConfig.upstash.redisRestToken = 'super-fancy-secret-token'
const lock = getOAuthLock()
expect(lock).toBe(mockLocalLock)
})

it('returns upstash lock when both url and token are configured', () => {
mockConfig.upstash.redisRestUrl = 'https://redis.redis.redis'
mockConfig.upstash.redisRestToken = 'token-123'
const lock = getOAuthLock()
expect(lock).not.toBe(mockLocalLock)
expect(typeof lock).toBe('function')
})
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's find the source file for getOAuthLock()
find . -type f \( -name "*.ts" -o -name "*.tsx" \) ! -path "*/node_modules/*" ! -path "*/dist/*" ! -path "*/.next/*" | xargs grep -l "getOAuthLock" | grep -v "spec\|test"

Repository: npmx-dev/npmx.dev

Length of output: 156


🏁 Script executed:

# Let's also check the test file to see what it imports
cat -n test/unit/server/utils/atproto/lock.spec.ts | head -40

Repository: npmx-dev/npmx.dev

Length of output: 1293


🏁 Script executed:

# Search for import.meta.dev in the codebase
rg "import\.meta\.dev" --type ts --type tsx

Repository: npmx-dev/npmx.dev

Length of output: 88


🏁 Script executed:

# Check vitest configuration
fd -e "vitest\|vite" -e "config" | grep -E "(vitest|vite)" | head -20

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

cat -n server/utils/atproto/lock.ts

Repository: npmx-dev/npmx.dev

Length of output: 2742


🏁 Script executed:

# Search for import.meta in the codebase more broadly
rg "import\.meta" --max-count 20

Repository: npmx-dev/npmx.dev

Length of output: 4658


🏁 Script executed:

# Check for any development or environment-based conditions in the lock file
cat -n server/utils/atproto/lock.ts | head -100

Repository: npmx-dev/npmx.dev

Length of output: 2742


🏁 Script executed:

cat -n vitest.config.ts

Repository: npmx-dev/npmx.dev

Length of output: 2817


🏁 Script executed:

# Check if there are any environment variables or define configurations that affect import.meta.dev
rg "define\|import\.meta" vitest.config.ts -A 3 -B 3

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Check the Nuxt configuration which might affect import.meta.dev
fd "nuxt.config" | head -5

Repository: npmx-dev/npmx.dev

Length of output: 95


🏁 Script executed:

cat -n nuxt.config.ts | head -50

Repository: npmx-dev/npmx.dev

Length of output: 1954


🏁 Script executed:

# Check if there's any setup file for the unit tests that might configure import.meta.dev
find test -name "setup.ts" -o -name "setup.js" | xargs cat

Repository: npmx-dev/npmx.dev

Length of output: 919


🏁 Script executed:

# Search for how import.meta.dev is typically controlled in Nuxt/Vite
rg "import\.meta\.dev.*=|dev.*:" vitest.config.ts nuxt.config.ts -B 2 -A 2

Repository: npmx-dev/npmx.dev

Length of output: 317


🏁 Script executed:

# Let's check if we can run the specific test to see if it passes or fails
cd test/unit/server/utils/atproto && npx vitest run lock.spec.ts 2>&1 || true

Repository: npmx-dev/npmx.dev

Length of output: 1069


🏁 Script executed:

# Let's also check what import.meta.dev evaluates to in the test by looking at how vitest configures this
# In Vite/Nuxt, import.meta.dev is controlled by the 'dev' flag. For unit tests in 'node' environment:
rg "env\.DEV|dev.*:" vitest.config.ts nuxt.config.ts -B 3 -A 3

Repository: npmx-dev/npmx.dev

Length of output: 377


🏁 Script executed:

# Check if there's a test that actually validates what import.meta.dev is during test execution
rg "import\.meta\.dev" test/ --type ts -B 2 -A 2

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

# Let's look at the package.json to see how tests are typically run
cat -n package.json | grep -A 5 -B 5 "test\|vitest"

Repository: npmx-dev/npmx.dev

Length of output: 3583


Make the Upstash-branch tests deterministic by explicitly configuring the development mode flag.

The tests assume import.meta.dev is false during execution, but this depends on Vitest's runtime configuration. To ensure the tests explicitly exercise the Upstash branch regardless of how import.meta.dev is configured, consider either: mocking import.meta.dev to false within the test suite, or wrapping the Upstash tests in a block that forces production-mode evaluation. This makes the test intent explicit and eliminates reliance on implicit runtime behaviour.

Comment on lines 96 to 102
export function presetRtl(checker?: CollectorChecker): Preset {
return {
name: 'rtl-preset',
shortcuts: [
['text-left', 'text-start x-rtl-start'],
['text-right', 'text-end x-rtl-end'],
],
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

🧩 Analysis chain

🏁 Script executed:

fd -e ts -e vue uno-preset-rtl | head -5

Repository: npmx-dev/npmx.dev

Length of output: 111


🏁 Script executed:

cat -n uno-preset-rtl.ts | head -120

Repository: npmx-dev/npmx.dev

Length of output: 4706


🏁 Script executed:

rg -n "reportWarning" -A 3 -B 3

Repository: npmx-dev/npmx.dev

Length of output: 2679


🏁 Script executed:

rg -n "function presetRtl|export.*presetRtl" -A 20

Repository: npmx-dev/npmx.dev

Length of output: 1201


🏁 Script executed:

fd uno.config.ts -o uno.config.js | xargs head -60

Repository: npmx-dev/npmx.dev

Length of output: 206


🏁 Script executed:

fd uno.config

Repository: npmx-dev/npmx.dev

Length of output: 74


🏁 Script executed:

cat -n uno.config.ts | head -80

Repository: npmx-dev/npmx.dev

Length of output: 5650


🏁 Script executed:

rg -n "presetRtl\|presetWind4" uno.config.ts -B 2 -A 2

Repository: npmx-dev/npmx.dev

Length of output: 43


🏁 Script executed:

cat -n uno-preset-rtl.ts | tail -100

Repository: npmx-dev/npmx.dev

Length of output: 4237


Define text-left/text-right as rules to override preset-wind4.

Preset-wind4 defines these utilities as rules, which take precedence over shortcuts regardless of preset order. Converting them to rules (while keeping presetRtl after presetWind4 in the presets array) ensures the RTL replacements are applied.

Proposed change
-    shortcuts: [
-      ['text-left', 'text-start x-rtl-start'],
-      ['text-right', 'text-end x-rtl-end'],
-    ],
     rules: [
+      [
+        /^text-(left|right)$/,
+        ([match, direction], context) => {
+          const fullClass = context.rawSelector || match
+          const replacement = direction === 'left' ? 'start' : 'end'
+          const suggestedClass = fullClass.replace(`text-${direction}`, `text-${replacement}`)
+          reportWarning(fullClass, suggestedClass, checker)
+          return { 'text-align': replacement === 'start' ? 'start' : 'end' }
+        },
+      ],
       // RTL overrides

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/pages/blog/index.vue (1)

85-166: Consider removing or tracking this commented-out code separately.

This ~80-line block of commented helper functions clutters the file. If the keyboard navigation logic is planned for future use, consider tracking it in a GitHub issue or a separate branch rather than leaving it inline.

Comment on lines +56 to +59
<template
v-for="(post, idx) in posts"
:key="`${post.authors.map(a => a.name).join('-')}-${post.title}`"
>
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 | 🟡 Minor

Add defensive check for post.authors in key generation.

The key expression post.authors.map(a => a.name) will throw if authors is undefined or not an array. Per coding guidelines, ensure type-safe array access.

🛡️ Proposed fix
         <template
           v-for="(post, idx) in posts"
-          :key="`${post.authors.map(a => a.name).join('-')}-${post.title}`"
+          :key="`${post.authors?.map(a => a.name).join('-') ?? idx}-${post.title}`"
         >

As per coding guidelines: "Ensure you write strictly type-safe code, for example by ensuring you always check when accessing an array value by index."

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.