diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 8b09afa..88fbf77 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -27,6 +27,7 @@ export default defineConfig({
['meta', { httpEquiv: 'Origin-Trial', content: ORIGIN_TRIAL_WEBMCP }],
['script', { src: '/neuron-js/webmcp/webmcp.js' }],
['script', { src: '/neuron-js/webmcp/neuron-webmcp.js', defer: 'true' }],
+ ['script', { async: 'true', src: 'https://news.google.com/swg/js/v1/publisher.js' }],
],
ignoreDeadLinks: true,
markdown: {
diff --git a/docs/.vitepress/theme/PreferredSourceFooter.vue b/docs/.vitepress/theme/PreferredSourceFooter.vue
new file mode 100644
index 0000000..a74c80f
--- /dev/null
+++ b/docs/.vitepress/theme/PreferredSourceFooter.vue
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts
index 1142adf..c292208 100644
--- a/docs/.vitepress/theme/index.ts
+++ b/docs/.vitepress/theme/index.ts
@@ -1,7 +1,9 @@
import DefaultTheme from "vitepress/theme";
import { inBrowser } from "vitepress";
import type { EnhanceAppContext } from "vitepress";
+import { h } from "vue";
import mermaid from "mermaid";
+import PreferredSourceFooter from "./PreferredSourceFooter.vue";
import "./custom.css";
const isDarkMode = () => document.documentElement.classList.contains("dark");
@@ -56,6 +58,11 @@ const scheduleRender = () => {
export default {
extends: DefaultTheme,
+ Layout() {
+ return h(DefaultTheme.Layout, null, {
+ "layout-bottom": () => h(PreferredSourceFooter),
+ });
+ },
enhanceApp({ router }: EnhanceAppContext) {
if (!inBrowser) {
return;
@@ -68,6 +75,9 @@ export default {
router.onAfterRouteChanged = async (to) => {
await afterRouteChange?.(to);
scheduleRender();
+ // The Google preferred-source button lives in the layout-bottom slot,
+ // which VitePress keeps mounted across SPA route changes; publisher.js
+ // initializes it once on load — verified in the browser E2E.
};
},
setup() {
diff --git a/tests/contracts/preferred-source-footer.test.ts b/tests/contracts/preferred-source-footer.test.ts
new file mode 100644
index 0000000..653cc3c
--- /dev/null
+++ b/tests/contracts/preferred-source-footer.test.ts
@@ -0,0 +1,33 @@
+// NJS-SEO-6 follow-up: contract test for the Google "Add to preferred sources"
+// footer button on the documentation site.
+import { readFileSync } from "node:fs";
+import { describe, expect, it } from "vitest";
+
+const read = (path: string) => readFileSync(path, "utf8");
+
+describe("Google preferred-source footer button", () => {
+ it("loads the official publisher.js in the VitePress head", () => {
+ expect(read("docs/.vitepress/config.ts")).toContain(
+ "https://news.google.com/swg/js/v1/publisher.js",
+ );
+ });
+
+ it("renders the standard button container in the layout-bottom footer", () => {
+ const component = read("docs/.vitepress/theme/PreferredSourceFooter.vue");
+ expect(component).toContain("google-add-preferred-source-btn");
+ expect(component).toContain("preferred-source-footer");
+ // theme aware: light/dark follows the site theme
+ expect(component).toContain("isDark");
+ });
+
+ it("mounts the footer via the layout-bottom slot", () => {
+ const theme = read("docs/.vitepress/theme/index.ts");
+ expect(theme).toContain("PreferredSourceFooter");
+ expect(theme).toContain("layout-bottom");
+ });
+
+ it("states the eligible source honestly (domain, not subdirectory)", () => {
+ const component = read("docs/.vitepress/theme/PreferredSourceFooter.vue");
+ expect(component).toContain("sebasoft.github.io");
+ });
+});