The stale-file cleanup in emitIconsCSS special-cased one file type for a
general condition: MPA mode never empties outDir, so hashed assets of
every kind accumulate (now a FIXME at the MPA copy step). The base test
harness clears its dist dirs itself, which the mpa/spa parity test
relied on the sweep for. Theme detection reuses the ssr bundle, which
exists in every mode.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- parseIconName requires `collection:name`; bare simple-icons names are
qualified by VPSocialLink alone, with a build warning suggesting
qualification when a bare name reaches generation any other way
- collection resolution keeps loadCollectionFromFS (any level of the
project tree) and falls back generically to the @iconify-json/*
packages in vitepress's own dependencies instead of hardcoding
simple-icons; the attribution comment special case is dropped
- fix the mpa/spa parity test on windows (path.basename, not split('/'))
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- emit the collected icon CSS as a fingerprinted assets/vp-icons.<hash>.css
that rides assetsBase and immutable caching; pages render a placeholder
href that a post-render pass substitutes, or strips entirely on
zero-icon sites (#4869)
- resolve icons in dev from locally installed collections through a
/@vpicons/ middleware instead of api.iconify.design (#5102); the
runtime API fallback is removed everywhere and missing icons warn at
build time with install hints
- icon names generalize to `collection:name` for any installed
@iconify-json/* package, in socialLinks and anywhere else; new VPIcon
theme component, useIcon composable, documented vpIcons SSG channel
and an icons.include site option for client-only renders
BREAKING CHANGE: `vpSocialIcons` on the SSG context is replaced by
`vpIcons`; generated icon classes rename from `vpi-social-<name>` to
`vpi-<collection>-<name>`; the unhashed root `vp-icons.css` is gone;
icons missing from locally installed collections no longer resolve at
runtime via api.iconify.design.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Functions in the site config (e.g. a custom miniSearch tokenize) were
embedded in the client's JSON payload as source strings and rebuilt at
runtime with new Function, which strict CSP blocks unless unsafe-eval
is allowed. Emit them as plain function expressions in the generated
script instead, with only an index marker left in the JSON, so the
deserializer just looks them up.
This also stops data strings that merely start with "_vp-fn_" from
being evaluated as code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
axe's label-content-name-mismatch rule flags the search button
(#5401): its visible text is "Search ⌘ K" while its accessible name
is "Search". aria-hidden on the keys does not help — the rule counts
visually rendered text, since WCAG 2.5.3 is about what sighted
speech-input users see and say.
The button was arguably conformant as it was: 2.5.3 constrains the
label, and the keycaps are a shortcut hint, not the label. Nobody
says "click Search command K". The glyphs move to CSS anyway because
it costs nothing visible and layers things right: the DOM text now
contains exactly the label, so speech software gets no stray "K"
target, strict audits pass without a human ruling them false
positives, and the decorative hint lives in the presentation layer
where decorative content belongs. Screen readers are unaffected — the
keys were already aria-hidden — and aria-keyshortcuts keeps carrying
the shortcut semantics.
Also folds the two display-toggled kbd elements into one whose
content switches per platform.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Safari composites the sticky local nav above the bar's negative-z
rule in their overlapping row, regardless of z-index, hiding the
divider below 60rem (#5399). Promoting the rule to its own layer
restores the order. Flyout panels still cover it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The icon is a masked background on an inline box whose height follows
font metrics per engine. The fixed centered mask left an unmasked
sliver of the background visible below the arrow on iOS. A full-size
mask can only stretch the arrow slightly, never leak.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The nav menu is the bar's only growing element, so without `nav`
configured everything packed to the right edge. Search now carries an
auto right margin. A present menu's flex-grow still wins, so nothing
moves in that case.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The local nav's bottom rule reads its own token instead of a
hardcoded gutter color. It follows --vp-nav-divider-color by default,
like the surface color follows the navbar's.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The frosted-glass surface disappeared from builds. Two causes stacked:
lightningcss (vite's css minifier) collapses a standard-first pair
into the prefixed declaration alone, and -webkit-backdrop-filter is
dead weight everywhere — Chromium and Firefox don't recognize it, and
WebKit ignores var() values in it (bug 289800).
Only the standard property is written now. The minifier re-adds the
prefix from it for its own targets. Safari 17 and earlier show the
translucent color without the blur, noted in the glass recipe's
warning.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The sidebar item row was a `role="button"` wrapping both the group heading
and a second `role="button"` caret, which is invalid HTML and nests
interactive controls. The caret is now the only control (a native button
with `aria-expanded`), the row keeps its click handler as a mouse-only
affordance, and groups without a heading render a `div` instead of a
`section`.
fixes#5366closes#5371
Co-authored-by: Jibin7Jose <jibinjose884@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Any element with an `id` is keyed by it regardless of attribute order, and a
`meta` without one is keyed by its first attribute other than `content`. This
stops differently named meta tags with the same content from overriding each
other and lets repeated meta tags be kept apart with unique ids.
fixes#5362closes#5363closes#5379
Co-authored-by: Lazizbek Ergashev <20501725+lazerg@users.noreply.github.com>
Co-authored-by: shamu45678 <220251922@seu.edu.cn>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`watch([props, navEl], ...)` is only accidentally valid on the client,
where props reaching setup are shallow-reactive. During SSR props are
not reactified (dev mode additionally wraps them in shallowReadonly),
so dev-flavored SSR warns "Invalid watch source: Proxy({ open: false })"
once per rendered page. Normally invisible because build() forces
NODE_ENV=production before vue loads — but any process that already
required vue under a different NODE_ENV (programmatic builds inside
test runners) keeps dev vue in the module cache and surfaces it.
Repro (pure Vue, no vitepress): renderToString of a component doing
watch([props, ref], cb, { immediate: true, flush: 'post' }) under dev
vue. Watching `() => props.open` expresses the actual dependency and
is valid in both environments.
dcb7a755 turned hasActiveLink from a computed into a ref fed only by a
non-immediate watch and onMounted, so is-active/has-active stopped
rendering in static HTML. This broke pre-hydration sidebar highlighting
and the docsearch crawler's lvl0 selector (section.has-active div h2),
which has indexed every record as the defaultValue since then.
Run the update once during setup with the hash check skipped: the server
and the client's initial render compute identical path-only state, so
hydration stays clean; the watch and onMounted refine with the real hash
after mount.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>