An output line assembled from more than one source (page text on the same
line as an include directive) matches no single physical line, so any
position on it names the wrong place. Rebasing trusted such lines' file
attribution and rewrote the page's own relative links against the included
file's directory when a mid-line include came first; columns on splice
tail lines were measured against the expanded text. Links and attributes
on spliced lines now carry no location at all, and the tail line after a
mid-line include is marked spliced too.
Also from the adversarial review: omit the column when the inline text
appears more than once in its raw line (repeated table cells) instead of
guessing the first occurrence; give a fully-elided source an identity
line map instead of letting resolve() throw; store segment files
posix-style so Windows builds compare and report one separator; offset
alert paragraph_open attrs past the removed marker like their inline
content; keep the page's line map and dev attrs out of excerpt renders;
name the including page in dead-link reports for included files
(deadLinks[].via); and harden the dev click handler (relative-base
endpoint, fetch failure).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vp_github_alerts rule strips the alert marker from the first inline
token's content before inline parsing, shifting every position in the
body up by the marker line and breaking column re-alignment. The rule now
records how many lines it removed (token.meta.vpLineOffset) and the
source positions plugin adds them back.
Also re-emit the source-location attribute from the remaining hand-built
renderers that dropped it (::: v-pre and ::: raw wrappers, math blocks),
and pop it off fence tokens in preWrapper so a custom highlight falling
back to markdown-it's default fence renderer cannot emit it twice.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Page renders in dev stamp block elements with data-v-inspector
attributes carrying the cwd-relative source file, line and column —
include-aware through the line map, so content pulled in via
`<!-- @include -->` points at the included file. The attribute is the
one vite-plugin-vue-inspector's overlay reads off arbitrary elements,
so the Vue DevTools component inspector jumps to the markdown source
out of the box; a ~50-line dev-only client handler additionally makes
alt+click open the editor through Vite's built-in /__open-in-editor
endpoint with no plugins installed (#4293). Fence wrappers, code
groups and GitHub alerts re-emit the attribute from their hand-built
markup; builds, the local search index and content loader output are
env-gated and stay byte-identical. Opt out with
`markdown.sourceAttrs: false`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
env.links becomes an array of { url, raw, loc } objects: the normalized
href, the destination as authored (decoded), and the exact position it was
authored at — including inside `<!-- @include -->`-ed files, which
previously reported the including page with a line in the expanded text.
Reports print the URL as written plus the resolved page path
(`(resolves to /x) in file.md:12:5`), fixing #4992 and #3774's halves of
the same complaint, and table-cell links (which had no line at all) and
links past the first line of a paragraph now carry exact positions.
Carries over the raw-URL reporting and test matrix from #5316.
BREAKING CHANGE: `env.links` entries are objects instead of strings and
`env.linkLines` is gone; `ignoreDeadLinks` strings, regexes and filter
functions now match the link as authored instead of the normalized encoded
URL, and filter functions receive a `{ file, line, column, url }` context
object instead of the source path string; `MarkdownCompileResult.deadLinks`
entries gained `resolved`/`column` and their `url` is now the authored
form. The markdown-it rule names `github-alerts`, `snippet` and
`vitepress_link_lines` are renamed/replaced by `vp_`-prefixed rules.
Co-authored-by: Bjorn Lu <34116392+bluwy@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Include expansion now emits a line map alongside the expanded source
(env.lineMap): one segment per contiguous run of lines, composed through
nested includes, region/heading/range slices (with the included file's
stripped frontmatter height folded in) and the isolation blank lines. Every
resolved source position now names the physical file a construct was
authored in.
This replaces the rebase-marker mechanism outright: relative URLs in
included markdown are rebased by resolving the token's position instead of
maintaining a marker stack scrubbed out of html_block tokens, which also
extends rebasing to lines following an inline include and makes block
isolation of own-line markdown includes unconditional (previously both were
skipped when markers could not be emitted).
BREAKING CHANGE: the `<!-- @include-start/end -->` markers no longer exist
in the expanded source (`env.src`).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every inline rule is wrapped (lazily on first parse, so user config rules
are covered) to record the source range it consumed onto the tokens it
emits; a terminal core rule converts ranges into {file, line, column} via
the parent inline map and env.lineMap, stamped as token.meta.vpLoc together
with the decoded as-authored destination (token.meta.vpRaw). Table-cell
inline tokens inherit their row's map, and links spliced in by the linkify
core rule recover at least their line. Ranges ride on token objects, so
plugins that splice children or mutate content don't disturb them.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vendor @mdit-vue/plugin-frontmatter and pad the stripped frontmatter with
blank lines before parsing, so every token.map stays in the coordinates of
the file instead of shifting up by the frontmatter height. This deletes the
contentLineOffset / src.endsWith(content) reconstruction in markdownToVue.
Offering the padding upstream as a preserveLines option is a follow-up.
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>