What β use the existing canonical-URL helper at the places that currently hand-roll the same join.
Why β src/lib/helpers/canonical.js exports buildCanonicalUrl(baseUrl, path) and has exactly one importer. The same trailing-slash-strip-and-join is written out by hand in several other places. A helper was written, then not adopted.
Scope β src/lib/plugins/seo-static.js, src/lib/plugins/docs-seo.js, and the two invitation views (invitations.admin.view.vue, invitations.account.view.vue).
This issue deliberately contains no code sketch β a pre-written one asserted the wrong signature for the sitemap builder. Read each call site and decide individually.
Not every match is the same function. At least one occurrence in docs-seo.js normalises a base path (like /docs), not a base URL β same-looking regex, different job. Another is handed an origin that was already stripped upstream. Convert only genuine base-URL joins; leave the rest and say which you skipped and why.
Two semantic traps to settle before writing code:
buildCanonicalUrl strips query strings and fragments. That is correct for canonical and sitemap URLs but wrong for the invitation signup link, which appends a token query parameter. Build the base with the helper, then append the query.
buildCanonicalUrl collapses the root route to a bare origin with no trailing slash, while the sitemap builder currently emits the origin with a trailing slash for the root entry. A naive swap changes the sitemap's root entry. Decide the intended behaviour, pin it with a test in the existing sitemap plugin test file, and note the choice in the PR.
The sitemap builder is exported and already directly tested, so a pin test has a home. Check its actual parameter shape before writing the test β it does not take a bare routes array.
Sequencing: blocks #4518 β both touch the two invitation views. Land this one first.
Definition of done: a production build produces sitemap.xml and llms.txt byte-identical to a master build, except for any root-entry change decided deliberately in trap 2 and recorded in the PR.
Scope: validated 2026-07-28
Created via /dev:issue
Baseline: line numbers and counts here were verified against master @ d246bc14 (2026-07-28). Master moves β re-verify against current master before starting; the invariants above are what must hold regardless of where the code has drifted to.
What β use the existing canonical-URL helper at the places that currently hand-roll the same join.
Why β
src/lib/helpers/canonical.jsexportsbuildCanonicalUrl(baseUrl, path)and has exactly one importer. The same trailing-slash-strip-and-join is written out by hand in several other places. A helper was written, then not adopted.Scope β
src/lib/plugins/seo-static.js,src/lib/plugins/docs-seo.js, and the two invitation views (invitations.admin.view.vue,invitations.account.view.vue).This issue deliberately contains no code sketch β a pre-written one asserted the wrong signature for the sitemap builder. Read each call site and decide individually.
Not every match is the same function. At least one occurrence in
docs-seo.jsnormalises a base path (like/docs), not a base URL β same-looking regex, different job. Another is handed an origin that was already stripped upstream. Convert only genuine base-URL joins; leave the rest and say which you skipped and why.Two semantic traps to settle before writing code:
buildCanonicalUrlstrips query strings and fragments. That is correct for canonical and sitemap URLs but wrong for the invitation signup link, which appends a token query parameter. Build the base with the helper, then append the query.buildCanonicalUrlcollapses the root route to a bare origin with no trailing slash, while the sitemap builder currently emits the origin with a trailing slash for the root entry. A naive swap changes the sitemap's root entry. Decide the intended behaviour, pin it with a test in the existing sitemap plugin test file, and note the choice in the PR.The sitemap builder is exported and already directly tested, so a pin test has a home. Check its actual parameter shape before writing the test β it does not take a bare routes array.
Sequencing: blocks #4518 β both touch the two invitation views. Land this one first.
Definition of done: a production build produces
sitemap.xmlandllms.txtbyte-identical to a master build, except for any root-entry change decided deliberately in trap 2 and recorded in the PR.Scope: validated 2026-07-28
Created via /dev:issue
Baseline: line numbers and counts here were verified against
master @ d246bc14(2026-07-28). Master moves β re-verify against current master before starting; the invariants above are what must hold regardless of where the code has drifted to.