diff --git a/.agents/skills/protowiki-components/references/chrome-primitives.md b/.agents/skills/protowiki-components/references/chrome-primitives.md
index 4b5f10b..1ee7d4a 100644
--- a/.agents/skills/protowiki-components/references/chrome-primitives.md
+++ b/.agents/skills/protowiki-components/references/chrome-primitives.md
@@ -12,7 +12,7 @@ chrome-without-the-wrapper (e.g., a custom layout that doesn't use
| Skin | Chrome feel | Notes |
| --- | --- | --- |
-| `desktop` | **Vector 2022–style** | Wordmark/tagline (**`wordmarkSrc`**, **`taglineSrc`**, **`#logo` override**), **`SearchBar`** + **Search** button, username link (**`username`** + **`#username`**), user-tool cluster ( **`navTools`** preset vs **`#nav`** override ). **Main-menu glyph is icon-only** (mock). Global skin stays **desktop** until the viewport is **≤640px**; **below 1120px**, inline search collapses to a search icon; **below 768px**, watchlist hides (`nav-button-desktop` parity). |
+| `desktop` | **Vector 2022–style** | Wordmark/tagline (**`wordmarkSrc`**, **`taglineSrc`**, **`#logo` override**), **`SearchBar`** + **Search** button, username link (**`username`** + **`#username`**), user-tool cluster ( **`navTools`** preset vs **`#nav`** override ). **Main-menu glyph is icon-only mock by default** — override via **`#menu`**. Global skin stays **desktop** until the viewport is **≤640px**; **below 1120px**, inline search collapses to a search icon; **below 768px**, watchlist hides (`nav-button-desktop` parity). |
| `mobile` | **Minerva-style** | Grey elevated bar: menu, Wikipedia wordmark (**`mobileWordmarkSrc`** / **`wordmarkSrc`**, **`#logo`**), search icon + notifications — **`navTools` is ignored**. |
**`ChromeFooter`** matches the skin:
@@ -52,6 +52,7 @@ Desktop **inline search** is always **``** inside the header (not a
| Slot | Default | Use for |
| --- | --- | --- |
| `#logo` | EN Wikipedia wordmark (+ tagline on desktop) via `
` | Replace with another project's wordmark / lockup |
+| `#menu` | Mock burger icon (**desktop**: non-interactive glyph; **mobile**: quiet button) | Replace with an interactive main-menu trigger + popover (**`ChromeWrapper`** forwards **`#menu`**) |
| `#username` | Anchor from **`username`** (hidden when empty after trim) | Replace with custom markup before tool icons (**desktop**) |
| `#nav` | Vector-style tool icons on **desktop** only | Replace the default user-tool cluster (**`navTools` ignored**) |
diff --git a/.agents/skills/protowiki-components/references/dashboard.md b/.agents/skills/protowiki-components/references/dashboard.md
index 469a3f9..d49c300 100644
--- a/.agents/skills/protowiki-components/references/dashboard.md
+++ b/.agents/skills/protowiki-components/references/dashboard.md
@@ -14,7 +14,7 @@ Reference prototypes:
Do not confuse:
- **`SpecialPageWrapper` `help`** — desktop title-row **Help** link (Codex docs URL)
-- **`dashpage/HelpModule.vue`** — prototype-local **"Get help with editing"** sidebar/mobile card (wraps **`DashboardModule`**)
+- **`template-homepage/HelpModule.vue`** — prototype-local **"Get help with editing"** sidebar/mobile card (wraps **`DashboardModule`**)
## `Dashboard`
@@ -35,7 +35,7 @@ With **`data-skin="desktop"`**, the two-column grid (`primary` ~66% + `sidebar`
Utility classes from the component stylesheet:
- **`dashboard-slot`** — min-height baseline on module roots
-- **`dashboard-slot--desktop-primary`**, **`dashboard-slot--mobile-primary`**, etc. — prototype hooks for per-slot sizing (see **`template-dashboard`** / **`dashpage`**)
+- **`dashboard-slot--desktop-primary`**, **`dashboard-slot--mobile-primary`**, etc. — prototype hooks for per-slot sizing (see **`template-dashboard`** / **`template-homepage`**)
### Props
diff --git a/.agents/skills/protowiki-getting-started/SKILL.md b/.agents/skills/protowiki-getting-started/SKILL.md
index bdac5a7..ff9256b 100644
--- a/.agents/skills/protowiki-getting-started/SKILL.md
+++ b/.agents/skills/protowiki-getting-started/SKILL.md
@@ -51,7 +51,7 @@ protowiki/
│ │ ├── index.vue ← home / gallery (auto-lists prototypes)
│ │ ├── template-chrome/index.vue
│ │ ├── template-dashboard/index.vue
-│ │ └── dashpage/index.vue
+│ │ └── template-homepage/index.vue
│ ├── components/ ← shipped components (wrappers, primitives, article, dashboard)
│ ├── composables/ ← useSkin / useTheme (read-only hooks)
│ ├── lib/ ← theming logic, helpers
diff --git a/.agents/skills/wiki-apis/assets/fetch_schemas.sh b/.agents/skills/wiki-apis/assets/fetch_schemas.sh
index e37ddcb..6bd6b42 100755
--- a/.agents/skills/wiki-apis/assets/fetch_schemas.sh
+++ b/.agents/skills/wiki-apis/assets/fetch_schemas.sh
@@ -21,7 +21,7 @@
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-UA='ProtoWiki-snapshot/0.1 (https://github.com/wikimedia-research/protowiki; protowiki@wikimedia.org)'
+UA='ProtoWiki-snapshot/0.1 (https://github.com/wikimedia/protowiki; protowiki@wikimedia.org)'
pretty_json() {
python3 -c 'import sys, json; json.dump(json.load(sys.stdin), sys.stdout, indent=2, sort_keys=True)'
diff --git a/package-lock.json b/package-lock.json
index 2ed7fda..982ded4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
"@wikimedia/codex": "^2.5.1",
"@wikimedia/codex-design-tokens": "^2.5.1",
"@wikimedia/codex-icons": "^2.5.1",
+ "fakewiki": "^0.0.13",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
@@ -1906,6 +1907,7 @@
"resolved": "https://registry.npmjs.org/@wikimedia/codex-icons/-/codex-icons-2.5.1.tgz",
"integrity": "sha512-ZbAXQD0dLuqj8uoMrpswusPymAZ07Pt0Y5wp1mNS3YyHuc1aAvsOGWdsV88YYoJN1l2jvYUE98/KoAKn7Cw5YA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=20.20.2",
"npm": ">=10.8.2"
@@ -2580,6 +2582,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fakewiki": {
+ "version": "0.0.13",
+ "resolved": "https://registry.npmjs.org/fakewiki/-/fakewiki-0.0.13.tgz",
+ "integrity": "sha512-bPIK55+eHRfA5tn7fGHBg/bpTSTP6r4S/HWEskcD5h2XZT3NFB+lxVVgNByvZCOSWI34cv3X8DFCFMzE6R8r+A==",
+ "license": "GPL-2.0-only",
+ "peerDependencies": {
+ "@wikimedia/codex-icons": "^2.0.0",
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
diff --git a/package.json b/package.json
index 5fa316b..d16dd22 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@wikimedia/codex": "^2.5.1",
"@wikimedia/codex-design-tokens": "^2.5.1",
"@wikimedia/codex-icons": "^2.5.1",
+ "fakewiki": "^0.0.13",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
},
diff --git a/scripts/probe-dashpage-recent-changes-signals.ts b/scripts/probe-dashpage-recent-changes-signals.ts
new file mode 100644
index 0000000..66a7488
--- /dev/null
+++ b/scripts/probe-dashpage-recent-changes-signals.ts
@@ -0,0 +1,62 @@
+#!/usr/bin/env npx tsx
+/**
+ * Probe language-agnostic articlequality score deltas for recent edits.
+ * Run: npx tsx scripts/probe-dashpage-recent-changes-signals.ts
+ */
+
+import { createDashpageRecentChangesWiki } from '../src/lib/fetchDashpageRecentChanges'
+import { fetchArticleQualityScore } from '../src/lib/fetchArticleQualityScore'
+import { DASHPAGE_RC_API_USER_AGENT } from '../src/lib/dashpageRecentChangesConstants'
+
+async function sleep(ms: number): Promise {
+ await new Promise((resolve) => setTimeout(resolve, ms))
+}
+
+async function main(): Promise {
+ const wiki = createDashpageRecentChangesWiki()
+ const deltas: number[] = []
+
+ console.log('Fetching recent changes (needs review)…')
+ const rc = await wiki.getRecentChanges({ limit: 12, onlyNeedsReview: true })
+
+ for (const rev of rc.revisions) {
+ if (!rev.id || !rev.pageName || rev.pageName.includes(':')) continue
+
+ const parentId = await wiki.getParentRevisionId(rev.pageName, rev.id)
+ if (parentId == null || parentId <= 0) continue
+
+ const before = await fetchArticleQualityScore(parentId, 'en', DASHPAGE_RC_API_USER_AGENT)
+ await sleep(300)
+ const after = await fetchArticleQualityScore(rev.id, 'en', DASHPAGE_RC_API_USER_AGENT)
+ await sleep(300)
+
+ if (before && after) {
+ const delta = after.score - before.score
+ deltas.push(delta)
+ console.log(
+ `rev ${rev.id} (${rev.pageName}): before=${before.score.toFixed(4)} after=${after.score.toFixed(4)} delta=${delta.toFixed(4)}`,
+ )
+ }
+ }
+
+ if (!deltas.length) {
+ console.log('No deltas collected — keeping default threshold 0.03')
+ return
+ }
+
+ const absDeltas = deltas.map(Math.abs).sort((a, b) => a - b)
+ const median = absDeltas[Math.floor(absDeltas.length / 2)] ?? 0
+ const p75 = absDeltas[Math.floor(absDeltas.length * 0.75)] ?? median
+ const suggested = Math.max(0.02, Math.min(0.05, Math.round(p75 * 100) / 100))
+
+ console.log('\nSummary:')
+ console.log(` samples: ${deltas.length}`)
+ console.log(` |delta| median: ${median.toFixed(4)}`)
+ console.log(` |delta| p75: ${p75.toFixed(4)}`)
+ console.log(` suggested display threshold (sprinthackular uses ${1e-6}): any delta > 0`)
+}
+
+main().catch((error) => {
+ console.error(error)
+ process.exit(1)
+})
diff --git a/scripts/snapshot-wiki-skins.sh b/scripts/snapshot-wiki-skins.sh
index 4caef0f..37a3f65 100755
--- a/scripts/snapshot-wiki-skins.sh
+++ b/scripts/snapshot-wiki-skins.sh
@@ -7,7 +7,7 @@ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
OUT_DIR="${1:-$ROOT/src/styles/wiki-content}"
mkdir -p "$OUT_DIR"
-UA='ProtoWiki-snapshot/0.1 (https://github.com/wikimedia-research/protowiki)'
+UA='ProtoWiki-snapshot/0.1 (https://github.com/wikimedia/protowiki)'
VECTOR_MODULES='site.styles|skins.vector.styles|ext.cite.styles|mediawiki.skinning.content.parsoid|mediawiki.hlist|mediawiki.ui.button|mediawiki.skinning.interface'
MINERVA_MODULES='site.styles|skins.minerva.base.styles|skins.minerva.styles|ext.cite.styles|mediawiki.skinning.content.parsoid|mediawiki.hlist|mediawiki.skinning.interface'
diff --git a/src/components/ArticleLive.vue b/src/components/ArticleLive.vue
index 02ffd2d..0562c2d 100644
--- a/src/components/ArticleLive.vue
+++ b/src/components/ArticleLive.vue
@@ -5,6 +5,7 @@ import { CdxMessage, CdxProgressBar } from '@wikimedia/codex'
import ArticleRenderer from '@/components/ArticleRenderer.vue'
import ArticleWrapper from '@/components/ArticleWrapper.vue'
import type { Skin, Theme } from '@/lib/theming'
+import { wipeLocalStorage } from '@/lib/wipeLocalStorage'
/** Cache of successfully fetched live article HTML (key: host + title). */
type CachedArticleBody = { html: string; liveTitle: string }
@@ -29,6 +30,7 @@ function loadFromStorage(key: string): CachedArticleBody | null {
if (!raw) return null
return JSON.parse(raw) as CachedArticleBody
} catch {
+ wipeLocalStorage()
return null
}
}
@@ -131,8 +133,7 @@ async function fetchArticle(title: string) {
})
const headers: Record = {
Accept: 'text/html; charset=utf-8',
- 'Api-User-Agent':
- 'ProtoWiki/0.1 (https://github.com/wikimedia-research/protowiki) prototype',
+ 'Api-User-Agent': 'ProtoWiki/0.1 (https://github.com/wikimedia/protowiki) prototype',
}
const response = await fetch(url, { headers })
if (!response.ok) {
diff --git a/src/components/ChromeHeader.vue b/src/components/ChromeHeader.vue
index c87617f..48fe46f 100644
--- a/src/components/ChromeHeader.vue
+++ b/src/components/ChromeHeader.vue
@@ -96,10 +96,12 @@ function navHas(tool: ChromeNavTool): boolean {