Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -52,6 +52,7 @@ Desktop **inline search** is always **`<SearchBar />`** inside the header (not a
| Slot | Default | Use for |
| --- | --- | --- |
| `#logo` | EN Wikipedia wordmark (+ tagline on desktop) via `<img>` | 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**) |

Expand Down
4 changes: 2 additions & 2 deletions .agents/skills/protowiki-components/references/dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`

Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/protowiki-getting-started/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
62 changes: 62 additions & 0 deletions scripts/probe-dashpage-recent-changes-signals.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
await new Promise((resolve) => setTimeout(resolve, ms))
}

async function main(): Promise<void> {
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)
})
2 changes: 2 additions & 0 deletions src/components/ArticleLive.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand All @@ -29,6 +30,7 @@ function loadFromStorage(key: string): CachedArticleBody | null {
if (!raw) return null
return JSON.parse(raw) as CachedArticleBody
} catch {
wipeLocalStorage()
return null
}
}
Expand Down
33 changes: 25 additions & 8 deletions src/components/ChromeHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,12 @@ function navHas(tool: ChromeNavTool): boolean {
<!-- Vector 2022–style chrome (desktop skin) -->
<nav v-if="isDesktop" class="chrome-header__nav-desktop" aria-label="Site">
<div class="chrome-header__desktop-start">
<!-- Mock only — not interactive (FakeMediaWiki uses bare chrome / icon affordances). -->
<span class="chrome-header__menu-icon" aria-hidden="true">
<CdxIcon :icon="cdxIconMenu" />
</span>
<slot name="menu">
<!-- Mock only — not interactive (FakeMediaWiki uses bare chrome / icon affordances). -->
<span class="chrome-header__menu-icon" aria-hidden="true">
<CdxIcon :icon="cdxIconMenu" />
</span>
</slot>

<RouterLink class="chrome-header__brand-link" to="/" aria-label="Visit the main page">
<slot name="logo">
Expand Down Expand Up @@ -223,9 +225,11 @@ function navHas(tool: ChromeNavTool): boolean {

<!-- Minerva-style chrome (mobile skin) -->
<nav v-else class="chrome-header__nav-mobile" aria-label="Site">
<CdxButton weight="quiet" size="large" aria-label="Main menu">
<CdxIcon :icon="cdxIconMenu" />
</CdxButton>
<slot name="menu">
<CdxButton weight="quiet" size="large" aria-label="Main menu">
<CdxIcon :icon="cdxIconMenu" />
</CdxButton>
</slot>

<RouterLink class="chrome-header__mobile-brand" to="/" aria-label="Visit the main page">
<slot name="logo">
Expand Down Expand Up @@ -333,6 +337,15 @@ function navHas(tool: ChromeNavTool): boolean {
pointer-events: none;
}

.chrome-header[data-skin='desktop'] :slotted(.chrome-header__menu-btn) {
flex-shrink: 0;
min-width: var(--size-icon-medium, 32px);
height: var(--size-icon-medium, 32px);
margin: 0;
padding: var(--spacing-25, 4px);
padding-inline-start: var(--spacing-50, 8px);
}

.chrome-header[data-skin='desktop'] .chrome-header__menu-icon :deep(svg) {
display: block;
}
Expand Down Expand Up @@ -497,7 +510,11 @@ function navHas(tool: ChromeNavTool): boolean {
box-shadow: inset 0 -1px 3px 0 rgba(0, 0, 0, 0.08);
}

.chrome-header[data-skin='mobile'] .chrome-header__nav-mobile > .cdx-button:first-of-type {
.chrome-header[data-skin='mobile'] .chrome-header__nav-mobile > :first-child {
flex-shrink: 0;
}

.chrome-header[data-skin='mobile'] .prototype-chrome-menu-popover {
flex-shrink: 0;
}

Expand Down
6 changes: 5 additions & 1 deletion src/components/ChromeWrapper.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,11 @@ provide(PROTOWIKI_CHROME_THEME, effectiveTheme)
:tagline-src="props.taglineSrc"
:mobile-wordmark-src="props.mobileWordmarkSrc"
:nav-tools="props.navTools"
/>
>
<template v-if="$slots.menu" #menu>
<slot name="menu" />
</template>
</ChromeHeader>
</slot>

<main class="chrome-wrapper__content">
Expand Down
65 changes: 58 additions & 7 deletions src/components/DashboardModule.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,19 @@ interface Props {
cta?: string | null
/** Light blue informational tint (**`--background-color-progressive-subtle`**). */
subtle?: boolean
/**
* Static mobile card (no **`RouterLink`**) — stacked title + body like a link card,
* but tappable controls in the body/header stay interactive.
*/
mobileCard?: boolean
}

const props = withDefaults(defineProps<Props>(), {
title: undefined,
to: undefined,
cta: '',
subtle: false,
mobileCard: false,
})

/** Resolves **`to`** for **`RouterLink`**; plain **string** targets are trimmed (whitespace-only → no link card). */
Expand Down Expand Up @@ -73,6 +79,22 @@ function trimmedTitle(): string {
</slot>
</RouterLink>

<section
v-else-if="props.mobileCard"
class="mobile-card dashboard-module dashboard-slot"
:class="{ 'dashboard-module--subtle': props.subtle }"
>
<div v-if="trimmedTitle()" class="mobile-card__header">
<span class="mobile-card__title">{{ trimmedTitle() }}</span>
<div v-if="$slots['header-actions']" class="mobile-card__header-actions">
<slot name="header-actions" />
</div>
</div>
<div class="mobile-card__content mobile-card__content--preview dashboard-module__body">
<slot />
</div>
</section>

<section
v-else
class="sidebar-card dashboard-module dashboard-slot"
Expand All @@ -98,12 +120,17 @@ function trimmedTitle(): string {
padding: 1rem;
}

.mobile-card--link {
.mobile-card--link,
.mobile-card:not(.mobile-card--link) {
display: flex;
flex-direction: column;
gap: 0.75rem;
}

.mobile-card--link {
text-decoration: none;
color: inherit;
-webkit-tap-highlight-color: transparent;
}

.mobile-card--link:visited,
Expand All @@ -113,21 +140,31 @@ function trimmedTitle(): string {
}

.mobile-card--link:hover,
.mobile-card--link:focus {
.mobile-card--link:focus,
.mobile-card--link:active {
outline: none;
background-color: var(--background-color-interactive, #eaecf0);
text-decoration: none;
color: inherit;
}

.mobile-card--link:focus-visible {
outline: 2px solid var(--color-progressive, #36c);
outline-offset: 2px;
}

.dashboard-module--subtle.mobile-card,
.dashboard-module--subtle.sidebar-card {
background-color: var(--background-color-progressive-subtle, #e8eeff);
}

.dashboard-module--subtle.mobile-card--link:hover,
.dashboard-module--subtle.mobile-card--link:focus {
background-color: var(--background-color-progressive-subtle--hover, #d9e2ff);
@media (hover: hover) and (pointer: fine) {
.mobile-card--link:hover {
background-color: var(--background-color-interactive, #eaecf0);
}

.dashboard-module--subtle.mobile-card--link:hover {
background-color: var(--background-color-progressive-subtle--hover, #d9e2ff);
}
}

.mobile-card__header {
Expand All @@ -137,6 +174,20 @@ function trimmedTitle(): string {
gap: 0.5rem;
}

.mobile-card__header-actions {
display: flex;
flex-shrink: 0;
align-items: center;
}

.mobile-card__header-actions .cdx-button.cdx-button--icon-only {
min-width: var(--min-size-interactive-pointer, 32px);
min-height: var(--min-size-interactive-pointer, 32px);
width: var(--min-size-interactive-pointer, 32px);
height: var(--min-size-interactive-pointer, 32px);
padding: 0;
}

.mobile-card__title {
font-weight: bold;
font-size: var(--font-size-medium);
Expand Down Expand Up @@ -210,7 +261,7 @@ function trimmedTitle(): string {
display: block;
align-self: stretch;
width: 100%;
margin-top: 0.25rem;
margin-top: var(--spacing-75, 12px);
padding: 0.25rem 1rem;
background-color: var(--background-color-progressive, #36c);
color: var(--color-inverted, #fff);
Expand Down
Loading
Loading