Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions .sync/log/906e8fd49f2dbf664133c79f2ebe5569e63d627e.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Port: fix(Link): fall back to original path when `localePath` fails (#6637)

**Upstream:** `906e8fd49f2dbf664133c79f2ebe5569e63d627e` (nuxt/ui)
**Decision:** port

## Upstream change
`Link.vue`: when the i18n `localePath()` returns a falsy value (route can't be
localized), the link rendered an empty `to`. Fall back to the original `path`:
`return localePath(...) || path`. +new nuxt test `LinkLocale.spec.ts`.

## b24ui adaptation
Direct 1:1 — b24ui's `Link.vue` matched the pre-change.
- `src/runtime/components/Link.vue`: `const localizedPath = localePath(path,
…); return localizedPath || path`.
- `test/components/nuxt/LinkLocale.spec.ts` (new; new `test/components/nuxt/`
dir): ported verbatim — mocks `$localePath` to `() => ''` and asserts both
`Link` and `Button` (with `to`) fall back to `/dashboard`.

## Verification (pnpm 11.8.0, gate ON)
dev:prepare · lint · typecheck · test (**5141 passed**, 6 skipped) · module
build — all green. No snapshot churn.
12 changes: 9 additions & 3 deletions .sync/nuxt-ui.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"upstream": "nuxt/ui",
"branch": "v4",
"sync_enabled": false,
"cursor": "0faeb9265e720b43021b020c3bd9d7878b9464da",
"cursor": "906e8fd49f2dbf664133c79f2ebe5569e63d627e",
"_cursor_note": "cursor = last upstream commit ported into b24ui (oldest-first, manual cadence). sync_enabled stays false until Phase 2 (porter workflow #67 + CLAUDE_CODE_OAUTH_TOKEN) is wired and trusted. `processed` is maintained per port from now on (backfilled #68-#72 on 2026-06-09).",
"stats": {
"queue_depth": 0,
Expand Down Expand Up @@ -755,10 +755,16 @@
"summary": "feat(InputRating): new component (#5757) — SKIPPED (maintainer call): brand-new InputRating component, 17 files/+1979 lines (InputRating.vue, theme/input-rating.ts, registration in theme/index.ts + icons.ts + types/index.ts + useComponentProps.ts, tests+snapshot, docs/playground/screenshots). Not a 1:1 port: like Calendar (#194) the theme is built on nuxt/ui theme.colors loops + semantic tokens, while b24ui is air-design-token based with no theme.colors — needs a deliberate air-design pass + full component registration, not mechanical translation. Tracked in issue #220"
},
"0faeb9265e720b43021b020c3bd9d7878b9464da": {
"pr": null,
"b24ui_sha": "pending-merge",
"pr": 222,
"b24ui_sha": "3751f71d",
"decision": "no-op",
"summary": "fix(AuthForm): track password visibility per field (#6638) — NO-OP: upstream edits src/runtime/components/AuthForm.vue (+spec), which b24ui does not have (no AuthForm component anywhere, git ls-files grep authform => none). Nothing to port. Bookkeeping only"
},
"906e8fd49f2dbf664133c79f2ebe5569e63d627e": {
"pr": null,
"b24ui_sha": "pending-merge",
"decision": "port",
"summary": "fix(Link): fall back to original path when localePath fails (#6637) — Link.vue: return localePath(path,...) -> const localizedPath=localePath(...); return localizedPath || path (empty localePath no longer yields empty to). Direct 1:1 (b24ui matched pre-change). +new test/components/nuxt/LinkLocale.spec.ts (new dir) mocking $localePath=()=>'' for Link+Button. No snapshot churn"
}
}
}
4 changes: 3 additions & 1 deletion src/runtime/components/Link.vue
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,9 @@ const to = computed(() => {
return path
}

return localePath(path, typeof props.locale === 'string' ? props.locale : undefined)
const localizedPath = localePath(path, typeof props.locale === 'string' ? props.locale : undefined)

return localizedPath || path
})

const isInternalLink = computed(() => {
Expand Down
43 changes: 43 additions & 0 deletions test/components/nuxt/LinkLocale.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { h, onUnmounted } from 'vue'
import { describe, expect, it } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import { useNuxtApp } from '#imports'
import Link from '../../../src/runtime/components/Link.vue'
import Button from '../../../src/runtime/components/Button.vue'

function createLocalePathFallbackFixture(component: typeof Link | typeof Button) {
return {
setup() {
const nuxtApp = useNuxtApp() as ReturnType<typeof useNuxtApp>
const originalLocalePath = nuxtApp.$localePath

nuxtApp.$localePath = () => ''

onUnmounted(() => {
nuxtApp.$localePath = originalLocalePath
})

return () => h(component, { to: '/dashboard' }, () => 'Dashboard')
}
}
}

describe('Link locale fallback', () => {
it('falls back to the original internal path when localePath returns an empty string', async () => {
const wrapper = await mountSuspended(createLocalePathFallbackFixture(Link))

const link = wrapper.get('a')

expect(link.attributes('href')).toBe('/dashboard')
expect(link.text()).toContain('Dashboard')
})

it('keeps button links navigable when localePath cannot resolve the route', async () => {
const wrapper = await mountSuspended(createLocalePathFallbackFixture(Button))

const link = wrapper.get('a')

expect(link.attributes('href')).toBe('/dashboard')
expect(link.text()).toContain('Dashboard')
})
})