Page MenuHomePhabricator

Add an "Essential work done through the kit" stats card to the /impact page
Open, HighPublic4 Estimated Story Points

Description

Background

/impact currently shows two cards: Key Highlights (wikis using the kit, contributors onboarded, tasks completed) and Users onboarded. "Tasks completed" is a single aggregate — it says how many task runs finished, but not what got made. The per-task outputs the kit actually produces (articles created, policy pages translated, infoboxes imported, main pages customised…) are not surfaced anywhere.

Proposal

Add a third card at the bottom of /impact:

Essential work done through the kit
The detailed outputs behind the highlights, across all wikis.

(The subtitle originally promised a per-wiki filter; that is split into its own task — see the per-wiki filter section.)

Ten stat tiles in two rows of five, each with a coloured left accent, a large value and a caption — visually the same tile idiom as KeyHighlights.vue.

Essential work impact card (1,147×224 px, 42 KB)

Kit-side sources (seed data + kit-action tiles)

Each tile mapped to what the kit itself records. How to read this table under the decided reach scope:

  • For the kit-action tiles (4, 6, 8, 9) — tiles that count something the kit itself did — this is the displayed metric; reach and direct coincide there.
  • For the reach tiles (1, 2, 3, 5, 7) these are the seed sets and anchors their reach aggregation is computed from (twn_links, install events, ledger anchors, imported-template titles) — the displayed numbers are defined in the metric-scope section.
  • These same sources, read directly as the tile values, are the rejected direct-completions alternative (see that subsection) — so this table doubles as the record of what that alternative would have shown, and its Status column tracks which MR/task unblocks each source.

The values in the mockup are placeholders, not targets — see the metric-scope section.

#TileProposed sourceStatus
1essential articles addedtask_verifications rows for create-essential-article (distinct created_title)Available after !152
2interface messages translatedT433428's twn_links (last_count − baseline) and/or its milestone ledger rowsUnblocked by T433428 — see below
3policy pages addedon-wiki: the user STARTED the translation from the kit (a translate_policy_page cta_click of either open-translation or open-cx — both, see its section) and the policy page's langlink target was later created by that same user — NOT .complete clicksT433617 implemented (MR !156) — once merged this is a plain task_verifications read like tiles 1 and 5, no aggregation needed; that task's verifier IS this predicate
4HotCat gadget installsinstall_gadget.cta_click, cta=install-success (fires only after the common.js write succeeds)Available — an install need not lead to an edit, so this will exceed tile 5
5articles categorized with HotCattask_verifications rows for install-gadgetAvailable — but = 3 in prod today
6infoboxes importedimport_infobox.cta_click, cta=import-template-successAvailable
7articles given missing infoboxesMW API transclusion counts of kit-imported infobox templatesDefined — needs the aggregation subsystem, see its section
8main page customizationswizard.publish with outcome=successAvailable
9recruit shares initiatedcommunity.recruit_shareAvailable
10accounts created via the kitrecruit-link account creations — no accessible attribution channelBlocked — see its section

Metric scope — the card shows kit-seeded reach (DECIDED)

Decided (see discussion below): the card reports kit-seeded reach. The /impact page answers "what difference has the kit made on wiki", and reach — work causally downstream of the kit — is that difference. Direct task completions remain valuable as *operational* metrics (completion rates, drop-off, task improvement) and belong to the task-level analytics of T429291: Improve the visualization of the Sankey user journeys analytics graph, not to this card. Every caption must still say what its number is ("seeded by the kit", "created by users the kit activated") — the scope is decided, honest labeling is not optional.

Kit-seeded reach, defined: work causally downstream of the kit even when the kit did not observe it happen, in two flavors —infrastructure reach (activity using something the kit installed: categories added with a kit-installed HotCat, articles transcluding kit-imported infobox templates) and user reach (activity by people the kit activated: categories added by users whose first HotCat use came via the kit, articles created by first-time creators the kit onboarded). Note the boundary: reach still counts only kit-connected people and kit-installed infrastructure — never a wiki's unrelated activity.

Per-tile reach definitions and their feasibility (the kit-side-sources table above keeps the direct sources: they are the seed sets and anchors the reach metrics are computed FROM, and tiles that count kit actions themselves — installs, imports, publishes — are identical under either scope):

#TileReach metricFeasibility
1essential articlesmainspace articles created by kit users who were first-time creators (no creation before their kit anchor), plus CX-published articles by first-time translators (no contenttranslation-tagged contribution before the anchor)good — the was-first-time predicate is one bounded usercontribs call per user (ucshow=new / uctag=contenttranslation, ucend=anchor, limit 1) and is a historical fact, cacheable forever; the ongoing count reuses getUserNewPages / the same tag query
2messages translatedcore-group translation edits made on translatewiki.net by kit-linked accounts after linking (usercontribs bounded at linked_at, intersected with the cached core message-key set — see the tile 2 section)good — one paged query per linked account plus one shared, cached group enumeration. The direct metric (last_count − baseline) is already activation-anchored, so "same as direct" is a fine fallback; this variant additionally escapes the direct metric's 500-result API cap and last-translator churn (edit counts are monotonic — someone re-editing a message later doesn't erase the work) while staying core-only by design
3policy pagespolicy page created by any kit user, even without starting it from the kit (no open-event anchor)good — same langlink + creator lookup as the direct metric, minus the anchor; see the tile 3 section for both definitions
5HotCat categorizations(a) all HotCat edits on wikis where the kit installed it, or (b) HotCat edits by users who first used HotCat via the kit(b) good — per-user contrib scans for the distinctive HotCat edit summary (the kai audit used exactly this), cacheable; (a) heavy — per-wiki RC scans, no edit tag to filter by
7infobox articlesarticles transcluding kit-imported templatesalready defined this way — tile 7 IS the reach pattern's template
8main pagee.g. pageviews of kit-published main pagespossible (public pageviews API) but a different unit — needs discussion

Common properties of every reach metric: external-API aggregation on a schedule with caching (never per dashboard load — same subsystem tile 7 already needs), and a seed set the kit records anyway (install events, twn_links, ledger users, imported-template titles). Design rule for the aggregation cache: store item rows (wiki, title, timestamp), not counts — every tile count is derived as COUNT(*) over its items, which is what makes the per-tile outputs drill-down (see the discussion) free and guarantees the headline always equals its list.

Activation-anchored reach (the unifying rule): the strong versions of these metrics don't count all work by kit users — they count work by users the kit *activated*: no prior work of that kind before their kit anchor. TWN's baseline already implements this at the account level (pre-link translations never credit); HotCat's user-reach variant is "first used HotCat via the kit"; tile 1's is "first article created / first translation published after meeting the kit". Suggested defaults (each a judgment call to confirm before implementation):

  • Anchor = the user's first contact with the relevant task (first .open event / metadata / ledger row for that task on that wiki), falling back to contributors.first_seen_at. Task-contact is the stronger causal claim than mere login.
  • Per wiki, consistent with contributors, baselines and everything else — a prolific creator on eswiki can still be an activated first-time creator on gnwiki.
  • The predicate is immutable once computed (a historical fact), so it's evaluated once per (user, wiki) and cached forever; only the ongoing count refreshes on the aggregation schedule.
  • Known blind spot: deleted contributions are invisible to usercontribs, so a user whose only pre-kit creation was deleted reads as first-time. Rare, biases slightly upward, accepted.

The rejected alternative — direct task completions

For the record, the dilemma this section used to pose: every tile can also be measured at the direct scope — only work the kit itself observed (verification-ledger runs, TWN baseline deltas, kit events). Its appeal was real: every source is a local query (no external-API aggregation subsystem, the card could ship immediately), and the numbers grow with genuine usage from a small base — tile 5 stood at 3 in production (the ledger's verified runs as of 2026-07-29) against a mockup value of 9,956; the mockup's three largest values (34,917 / 9,956 / 5,370) are only reachable at the reach scope.

Rejected for this card because it answers the wrong question here: "how many task runs did the kit observe" is an operational measure of the tasks, not of the kit's on-wiki impact — a wiki whose community adopted a kit-installed HotCat, or whose articles transclude kit-imported infoboxes, delivers value the direct scope is structurally blind to. The direct metrics are not discarded: they move to the task analytics of T429291: Improve the visualization of the Sankey user journeys analytics graph (completion rates, drop-off, per-task improvement), and inside this card they survive as the machinery — the ledger predicates, baselines and events are the seed sets and activation anchors every reach metric is computed from.

Per-wiki filter — not part of this card (T433618: Add a wiki filter to the /impact dashboard)

This card does not implement per-wiki filtering. The mockup subtitle's "filter by language code in the header" is delivered by T433618: Add a wiki filter to the /impact dashboard, which adds a page-wide wiki filter to the whole /impact dashboard; when it lands, this card inherits the filter like every other card on the page (its endpoint accepts the same wiki parameter as the rest — worth keeping in mind when writing this card's queries, but nothing here blocks on it).

The card therefore ships with the subtitle:

The detailed outputs behind the highlights, across all wikis.

Tile 2 — core-message translations, per-edit (windowed and exact) — DECIDED

Decided source — settled in this task's comments (the windowed preference and the closing tile-2 summary, {T433621#12175151}): a per-edit scan of the linked accounts' translatewiki.net contributions, filtered to the MediaWiki core message group. This satisfies both constraints from that thread at once — the tile follows the card's days selector like every other tile (windowed; the ≈10-step granularity caveat that originally made windowing a design question no longer exists on this source), and it counts exactly the kind of work the task asks for (core messages only) — while staying message-exact: each item is an individual core-message translation with its own timestamp, so the window selects single translations. Remaining design surface is presentational only (tile caption, drill-down layout).

Two API ingredients, composed by the aggregation:

  1. The per-account edit scan: list=usercontribs on translatewiki.net per linked account, ucnamespace=8 (every translation lives at MediaWiki:<message-key>/<language-code>), bounded at the account's linked_at — pre-link work is excluded by the bound itself. Incremental per scheduled run (continue from the last scanned timestamp).
  2. The core-group membership set: list=messagecollection&mcgroup=core, paginated via mcoffset — the complete core key list (a few thousand messages, a handful of requests), shared across all users, cached and refreshed on the aggregation schedule. Note this is the same API the task's own counter uses, minus its single-request mclimit=500 simplification — so the tile escapes the 500-message cap the in-flow read accepts.

An edit counts when its title parses as MediaWiki:<key>/<lang> with <key> in the cached core set. Items are stored as rows (the card-wide design rule), so the tile count is COUNT(*) in the window, the outputs drill-down lists the messages (each linking to its page/diff on translatewiki.net), and the window filters on the edit's own timestamp — when the work happened, the same convention the verification ledger uses.

Definition pins:

  • Language scope — decided: any language. Every core-message translation by a linked account counts, as long as it happened after linking — the kit activated the translator, and all their core work is reach. Attribution follows the translation, not the link: a hausa translation by a kcg-linked user belongs to hausa. Concretely, each item row stores the target language code parsed from the title (MediaWiki:<key>/<lang><lang>), and the per-wiki filter (T433618, when it lands) matches items by the selected wiki's language code — not by the twn_links row's wiki_url, which remains only the seed that says whose contributions to scan.
  • Unit — decided: distinct messages. One item per (message key, language) per account — the user's later edits to their own translation don't create new items. The item's timestamp is the first post-link edit of that message, so a message counts in exactly one window, windows are additive, and the all-time total equals the sum of the periods.

The seed and anchor come from T433428's twn_links (linked account + linked_at); its two direct derivations remain as supporting reads, not the tile:

  • Fallback while an account's scan hasn't run yet (or translatewiki is unreachable): SELECT COUNT(*) * 10 FROM task_verifications WHERE task_key='translate-interface-messages' AND verified_at >= datetime('now', ?) AND <EXCLUSIONS_SQL> — windowed but decade-granular (≈10 per credited milestone); label with ≈ if ever shown.
  • Cross-check (all-time only): SELECT SUM(MAX(0, last_count - baseline)) FROM twn_links WHERE baseline IS NOT NULL AND last_count IS NOT NULL (+ exclusions) — message-exact for the core group but a current-state snapshot with no time dimension.

Do NOT use the mark-done analytics event's translations property — it is frozen at the click and superseded by all of the above.

Scope caveats by source: the scan is core-only by design and monotonic (an edit is a historical fact — someone re-editing the message later doesn't erase it); the last-translator semantics and the 500-message cap apply only to the fallback/cross-check derivations above.

Tile 3 — count published policy pages on-wiki, not Done clicks

.complete events and task_completions rows only exist when the user clicks "Mark as done" — a translator who publishes the policy page via Content Translation and never returns is invisible to them. That is the same false negative the verified tasks eliminated, so the tile must read the wiki, not the click. Both scopes are defined on-wiki:

  • Direct: the user started the translation from the kit — an open-cx cta_click for translate_policy_page anchors the run — and the policy page's langlink target was created by that same user, at or after the open (resolveLanglinkTarget('Wikipedia:Five pillars', <code>) then getPageCreator; same-user and created-at-or-after-open, i.e. exactly the create-essential-article verifier predicate pointed at a fixed title). No return visit needed; the Done click is irrelevant to the count.
  • Reach (weaker): the policy page was created by any kit user, whether or not they started it from the kit — creator matched against the wiki's contributors, no open-event anchor.
  • The task currently covers a single policy page (Wikipedia:Five pillars, tasks.js:79), so both counts are per-wiki ("wikis whose Five pillars a kit user created…"). If the task grows more policy pages, the same lookups apply per page.
  • One langlink + one creator lookup per kit wiki — external API, so it rides the same scheduled cached aggregation as tile 7's transclusion counts. Tiny volume; the open-event anchor is a local query.
  • Systematic fix, filed as T433617: register translate-policy-page in VERIFIABLE_TASKS — the direct predicate above IS the verifier that task specifies, so completion itself becomes on-wiki verified and ledger rows feed this tile for free. The tile does not depend on it — the aggregation works against today's schema — but if T433617 lands first, this tile simplifies to a ledger read like tiles 1 and 5.

Tile 7 — articles transcluding kit-imported infoboxes (DEFINED)

Definition (decided 2026-07-30): distinct mainspace articles, across all wikis, that transclude an infobox template the kit imported. This is what "articles given missing infoboxes" honestly means — not template imports (tile 6), but the articles that ended up using them.

Derivation:

  1. Which templates count: the local template pages the import-infobox tool created, recoverable per wiki from the import-template-success cta_click events ({ type, title }title is the local template page). De-duplicate per (wiki, title).
  2. Count per template: MediaWiki API list=embeddedin (einamespace=0) on each wiki, counting distinct pages; union the page sets per wiki before summing so an article with two kit templates counts once.
  3. This is the card's only external-API tile, and transclusion counts change as wikis edit — so the number must be computed on a schedule and cached (a table or in-memory snapshot refreshed by the existing in-process sweep cadence), never per dashboard load. Politeness-delay the API calls.

Because of the aggregation+caching subsystem this can ship as a fast-follow to the card rather than blocking it: the card renders the tile once the aggregate exists (the acceptance criteria's omit-until-real rule covers the interim).

Scope note: this counts transclusions of kit-imported templates regardless of who added the infobox to the article — it is an infrastructure-reach metric (see the metric-scope section), and the template for how the card's other reach metrics should be built.

Refinement — resolve WHEN the infobox was added (decided 2026-08-01)

As first built, this tile stamps each item with its discovery time: list=embeddedin reports which pages transclude a template, never when the transclusion appeared. Two costs. The days window moves when a scan runs rather than when work happens, so everything found on the first pass lands in that pass's window. And the row carries no change tags, so unlike every other item it cannot say whether the work was done on mobile.

Both are fixable, because the revision history holds the answer:

  1. Bound the search. A transclusion cannot predate the template's import, whose timestamp the import-template-success event already gives. Fetch the page's revisions from that point: prop=revisions&rvprop=ids|timestamp|tags&rvdir=newer&rvstart=<import>.
  2. Find the revision that introduced it. action=parse&oldid=<revid>&prop=templates reports the templates of that revision. Scan the candidates linearly while the list is short (exact); binary-search above a threshold, documented as an approximation because add → remove → re-add is not monotonic and a binary search would find a later addition.
  3. Store what it found. That revision's timestamp becomes item_ts and its change tags become the row's tags. Both are immutable, so a page is resolved once and never revisited.

Insert only once resolved, which keeps the items table insert-only — no discovery-time row to update later. A page that cannot be resolved (RevDeleted content, a failed request) simply does not count yet and is retried on the next pass.

Cost: heavy once, then negligible. The first pass works through the existing backlog at one revisions call plus a handful of parse calls per page. Afterwards each pass sees only pages new to embeddedin, whose transclusion necessarily happened within the pass interval — so the candidate list is one or two revisions. A template imported after this ships never accumulates a backlog at all. Keep a per-(wiki, template) cursor and a per-pass page budget so one widely-adopted template cannot monopolise a pass.

Two details: match the template by page id, not title, so a later rename or redirect does not orphan the search; and a page that gains, loses and regains the infobox keeps its first origin — the metric is "when this page first got an infobox".

Sequencing: land this before the first production scan pass. Otherwise tile 7's rows exist with discovery timestamps and have to be deleted and re-derived — recoverable, since nothing about these rows is unique to the moment they were written, but avoidable.

What it buys beyond this card: tile 7's rows become tag-bearing and truthfully dated like every other item, which is what lets them join the /impact Key Highlights contributions tile and its mobile subset on the same terms as the rest.

Tile 10 — recruit-link account creations are not attributable (BLOCKED)

Goal: count accounts created on the wiki through the link the RecruitDialog shares.

Why it is blocked (analysis 2026-07-30): the shared link is <wiki>/wiki/Special:CreateAccount?utm_source=starterkit&utm_medium=<platform> (RecruitDialog.vue:130-135). MediaWiki ignores utm_* parameters — they are recorded nowhere a Toolforge tool can read. The mechanism that DOES attribute account creations to a link is the campaign= parameter on Special:CreateAccount, which lands in the ServerSideAccountCreation EventLogging stream — WMF-internal analytics (Data Lake), not exposed by any public API. The public list=logevents&letype=newusers shows account creations with no referrer/campaign data. So there is no automatable way for the tool to know which creations came from the kit's link.

Two paths, recorded for whenever this is picked up:

  1. Internal-analytics route (accurate, not automatable): add campaign=starterkit-recruit to the recruit URL (one-line change, worth doing regardless — it starts accumulating attributable data from the moment it ships), and query ServerSideAccountCreation internally for periodic numbers. Those numbers cannot feed a live tile (the card's no-hardcoded-values rule), but they answer the question for reports.
  2. Kit-redirect proxy (automatable, weaker): route the shared link through a kit endpoint that logs an analytics event and 302s to Special:CreateAccount. That yields recruit-link arrivals (clicks by recruits, a real funnel stage beyond tile 9's shares-initiated) but still not completed account creations.

Until one of these is chosen, the tile is omitted (per the acceptance criteria).

Acceptance criteria

  • New card renders at the bottom of /impact, titled "Essential work done through the kit" with the agreed subtitle, matching the mockup's two-row five-tile layout and per-tile accent colours
  • Each shipped tile is backed by a real query — no hardcoded or mocked values
  • Every tile honours the existing days window and binds EXCLUSIONS_SQL + ...ADMIN_USERS, so test wikis and analytics admins are excluded in production exactly as the other cards do
  • Tiles whose definition is unresolved are omitted, not shipped with placeholder numbers — the precedent is already in KeyHighlights.vue, which ships three tiles and documents the deferred two in a comment
  • Values use the dashboard's existing number formatting — the mockup's 34.917 dot separators are a locale-formatting question, not something to reproduce literally from the image
  • Card is visible to all /impact viewers; no tile exposes usernames (no maintainer-only gating needed unless drill-downs are added)
  • i18n: en.json + qqq.json only (translated files are bot-synced)
  • Responsive: tiles wrap on narrow viewports rather than overflowing

Implementation notes

  • Tile 2 additionally depends on T433428 (feat/T433428-twn-repeatable, stacked on !153) merging — its twn_links table and milestone ledger rows are the tile's data source.
  • Tile 3 is best sequenced after T433617 (policy-page verification): its ledger rows make the tile a local read and the bespoke langlink/creator lookup unnecessary. Not a hard dependency — see the tile 3 section.
  • Branch off feat/T433369-phase5-dashboard (!152), not main. !152 already edits server/db/impact.js (getTaskCompletionsDetail) and server/db/journey.js, and tiles 1 and 5 read the task_verifications ledger that !152 teaches the dashboards to use. Branching off main means resolving that conflict twice.
  • Server: one new aggregate in server/db/impact.js beside getTaskCompletionsDetail, returning all tiles in a single object; one new route in server/routes/impact.js following the existing pattern (clampDays, sinceParam).
  • Client: new client/src/components/impact/EssentialWorkCard.vue mirroring KeyHighlights.vue (accent span + value + caption, formatValue, days prop, fetch on mount and on days change); mounted last in ImpactDashboard.vue.
  • One endpoint for all ten tiles rather than ten calls.
  • Event-derived tiles should reuse the shared definitions in server/db/journey.js (completedTaskForEvent, CTA_COMPLETION) where they apply, so the card cannot drift from what the rest of the dashboard calls "done".

Open questions (blocking for the affected tiles only)

  1. Metric scopeRESOLVED: kit-seeded reach (see the metric-scope section, which also records the rejected direct-completions alternative and why).
  2. Tile 10 unblocking — pick a path from its section (internal-analytics via campaign=, or the kit-redirect arrivals proxy), or drop the tile. Adding campaign=starterkit-recruit to the recruit URL is worth doing immediately either way, so attributable data starts accumulating.

Update (2026-08-06): this ledger now also defines the /impact "Policy pages added" tile.

As of !173, the verification ledger introduced here is the source for tile 3 of the essential-work card on /impact (T433621: Add an "Essential work done through the kit" stats card to the /impact page). That tile previously counted the wiki's Five-pillars langlink target whenever its creator was any contributor of that wiki, with no anchor of any kind — so it credited pages created before the kit existed (production carried a 2023 creation on a wiki whose author only became a kit user later) and disagreed with this task's own ledger, reading 4 against 2.

Both the tile and its drill-down now read task_verifications for translate-policy-page, so "the kit caused this policy page" has a single definition across /impact, /analytics and the task's own completion state: the user opened the translation from a kit CTA and the creation was then verified as theirs. The scanner that used to produce the tile's rows is retired, and the drill-down links the localized page the run actually produced rather than the constant English source.

One consequence worth recording at the source: the card's other reach tiles count downstream work by kit-activated users without requiring each individual artifact to originate from a kit click (essential articles, HotCat-categorized articles, infobox transclusions). Tile 3 is now the only one that requires per-item provenance from the kit. That is stricter than the card's general reach rule, and it is precisely what makes the tile agree with this task.

Details

Related Changes in GitLab:
TitleReferenceAuthorSource BranchDest Branch
Fix: Attest the gadget-installs tile from the on-wiki install revisionrepos/lpl/starterkit-v1!169ngkountasfix/T433621-gadget-installs-ledgermain
Customize query in GitLab

Related Objects

Event Timeline

ngkountas triaged this task as High priority.
ngkountas set the point value for this task to 4.Jul 30 2026, 4:49 PM

Thanks Nik . For the first part of the question, I think the Impact dashboard should report kit-seeded reach rather than direct task completions.

The purpose of the /impact page is to answer the question "What difference has the Starter Kit made "on wiki"?", not "How many tasks were completed?" For example, reporting articles categorized with HotCat, articles using imported infoboxes, or essential articles created by users activated through the kit better reflects the value delivered to Wikimedia projects than simply counting installs or completed task runs.

By contrast, direct task completions are still valuable, but they are better suited to the task-level analytics described in T429291, where the goal is to understand how users interact with individual Starter Kit tasks, measure completion rates, identify drop-off points, and improve the tasks themselves. Those operational metrics belong in a task analytics view rather than a product impact dashboard.

One question: would it be possible for each of these metrics to link through to the underlying outputs? For example, clicking Essential articles added could show the list of articles, Policy pages added could link to the created policy pages, Infoboxes imported to the imported templates, Main page customizations to the customized main pages, and so on. I think this enable users to move from the aggregate numbers to the actual work produced and verify or explore the impact directly, but not sure the dynamics of making this possible?

Regarding Tile 10 and having it blocked, i think we can loop in analytics

@KCVelaga We're working on an impact dashboard for the Starter Kit https://phabricator.wikimedia.org/T430096 and have run into a limitation around attributing account creations from the Recruit task. We wanted to check whether there's an analytics-supported approach before we decide how to proceed. See description on TILE 10?

Regarding Tile 2

I would lean towards the windowed metric. Since the rest of the dashboard supports a days filter, it makes sense for this tile to behave the same way and show translations completed within the selected time period. @srishakatux feel free to add from a design perspective what your thoughts are

@MaryMunyoki regarding your first comment:

Agreed on reach as the card's scope — noting the implementation consequence: the reach tiles are the ones that need the scheduled + cached aggregation subsystem described in the task (the direct sources are all local reads). I'll treat reach as decided and drop the scope section's DECISION NEEDED flag.

On drilling through to the underlying outputs: yes for every tile that has an on-wiki artifact, and the reach decision is what makes it cheap. /impact already pairs every headline number with a drill-down dialog whose row count equals the headline by construction; an "outputs" drill-down is the same pattern listing page links instead of usernames. Under the reach scope the tiles split into two kinds, and both itemize naturally:

  1. Kit-action tiles (4, 6, 8) — the tile counts something the kit itself did, so the artifact is already in our own records: the Common.js the install wrote, the imported template's title, the customized wiki's Main Page. Itemizing is just an endpoint + dialog.
  2. Reach tiles (1, 2, 3, 5, 7) — the counts come from the scheduled aggregation, and every MediaWiki/translatewiki API call that computes them returns the item lists themselves (new-page scans, contribution scans, transclusion lists). The design rule that makes drill-through free rather than a second feature: the aggregation cache stores item rows (wiki, title, timestamp) and every tile count is derived as COUNT(*) over them — the number and the list can never disagree, and the links cost nothing beyond what counting already costs.

Per tile, what the drill-down lists under the reach scope:

#TileDrill-down listsWhere the items come from
1essential articles addedthe articles created by kit-activated users (first-time creators, incl. CX-published by first-time translators), as linksthe activation scan's usercontribs calls return exactly these page lists
2interface messages translatedthe interface messages translated after linking, each linking to the message page (or diff) on translatewiki.netthe post-link usercontribs scan on translatewiki.net is per-edit — itemization comes free with the reach count
3policy pages addedthe created policy pages (one per wiki), as linksthe per-wiki page check that produces the count identifies the page itself
4HotCat gadget installsthe MediaWiki:Common.js pages the kit wrote, per wikikit's own install events
5articles categorized with HotCatthe articles categorized via HotCat downstream of the kit, as linksthe HotCat-edit-summary contribution scans are per-edit — same free itemization as tile 2
6infoboxes importedthe imported template pages, as linkskit's own import events (title recorded)
7articles given missing infoboxesthe articles transcluding kit-imported infoboxes, as linksthe embeddedin calls return exactly these lists
8main page customizationsthe customized Main Pages, per wikikit's own publish events
9recruit shares initiatednone — the share is an off-wiki action with no artifact to linkN/A
10accounts created via the kitn/a while the tile is blockedN/A

One nice property: these dialogs list page titles only, no usernames, so unlike the existing contributor drill-downs (maintainer-only because they expose who did what) the outputs drill-downs can be public — anyone can move from the aggregate to the actual work, which is exactly the verification you describe.

I'd fold this in as an acceptance criterion: every tile with an on-wiki artifact (1–8 except 9's off-wiki share) opens a drill-down listing the underlying pages as links, and each headline equals its list's row count.

Following up on tile 2 — I think we can settle this one: windowed, and the reason the question was worth design input has actually gone away since it was asked.

When windowed was proposed, the available windowed source counted credited milestones (≈10 translations per step), so the tile would have needed a "counted in runs of 10" caveat — that coarseness was the design trade-off to weigh. The tile's source has since been redefined (see the updated Tile 2 section): a per-message scan of linked accounts' translatewiki.net contributions, filtered to the MediaWiki core message group. Each counted item is an individual core message with its own timestamp, so the days window selects exact counts — windowed with no granularity caveat, consistent with every other tile, and the number always equals its drill-down list for the selected period.

Definition pins, for the record: core messages only; any target language, attributed to the language translated into (a hausa translation by a kcg-linked user counts for hausa); one distinct message per (key, language), timestamped by its first post-link translation so each message lands in exactly one window.

@srishakatux — with the metric question resolved, the remaining design surface is presentation: the tile caption ("core interface messages translated by kit-linked translators" or similar) and how the drill-down lists the messages. Input welcome there, nothing blocking.

ngkountas opened https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/159

Feat: Add the "Essential work done through the kit" card to /impact (T433621, MR 1/4)

ngkountas opened https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/160

Feat: Essential-work aggregation subsystem + first reach tiles (T433621, MR 2/4)

ngkountas opened https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/161

Feat: Kit-activated user scanners — essential articles + HotCat reach (T433621, MR 3/4)

ngkountas opened https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/162

Feat: Articles using kit infoboxes — the embeddedin reach tile (T433621, MR 4/4)

Thanks Nik, regarding your response to the first comment. sounds good to have it as an acceptance criteria , all good to proceed with implementation.

One comment to consider for a future iteration regarding essential articles created by first-time creators: the initial implementation can use the simpler attribution model of attributing output to kit-activated users. However, we may later want to refine this by distinguishing between activity that happens directly through the Starter Kit and downstream activity that occurs independently after the initial activation. I don't think this needs to be addressed now, but it may be worth considering as the impact metrics evolve.

ngkountas merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/159

Feat: Add the "Essential work done through the kit" card to /impact (T433621, MR 1/4)

ngkountas merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/160

Feat: Essential-work aggregation subsystem + first reach tiles (T433621, MR 2/4)

eamedina merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/161

Feat: Kit-activated user scanners — essential articles + HotCat reach (T433621, MR 3/4)

eamedina merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/162

Feat: Articles using kit infoboxes — the embeddedin reach tile (T433621, MR 4/4)

ngkountas merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/163

Verify import-infobox completions on-wiki (5th verification task) + ledger-source impact tiles 6/7

Status wrap-up now that the merge train is in: all nine buildable tiles are merged to main (MRs !159–!162, logged above) and the card is deploy-gated from here. Per tile, as shipped:

#tilesourcedrill-down
1Essential articles addedscanner: new-page scan of kit-activated users (no pre-anchor creation/CX)
2Interface messages translatedscanner: per-message translatewiki.net contribs of linked accounts, windowed (as agreed above)✓ (scope column = language)
3Policy pages addedscanner: Five-pillars langlink target created by a kit user
4HotCat gadget installskit's own events (install-success), one per wiki
5Articles categorized with HotCatscanner: HotCat-summary mainspace edits of kit-first users
6Infoboxes importedverification ledger (see note below)
7Articles given missing infoboxesscanner: transclusions of kit-imported templates, dated by the introducing revision (per the tile-7 refinement comment)
8Main pages customizedkit's own events (wizard.publish success), one per wiki
9Recruit shares initiatedkit's own events, every initiation— (no artifact)

Every drill-down lists page links and dates only — no usernames — which is what keeps the outputs endpoint open to any logged-in user, per the drill-through discussion above. Headline == list length holds by construction (counts are COUNT(*) over the same rows the dialog lists).

One sourcing change since the MRs logged above: tiles 6 and 7 originally read the import-template-success analytics event, which only exists since 2026-07-21 — on production that would have shown ~nothing for the wikis with the real import history. Both now read the import-infobox verification ledger, whose summary-token backfill recovers the full history with real on-wiki edit timestamps. That landed in a separate MR (!163, the import-infobox verification work).

What to expect right after deploy: the aggregation scan starts 45 s after boot and repeats every 6 h; the reach tiles (1, 2, 3, 5, 7) fill as scanner passes walk their subjects' histories, so they start sparse and converge over the first few passes rather than appearing fully populated at once. The kit-event tiles (4, 8, 9) are correct immediately. ESSENTIAL_SCAN_DISABLED=1 is the operator kill switch if the external-API scan ever needs to be off; the card keeps serving recorded items.

Rendering with real data (dev DB, 3-month window; the same screenshot posted on T433766 — this card is the lower half):

image.png (1,158×1,490 px, 445 KB)

Tile 10 (accounts created via kit): split out to T433802: Attribute account creations to Starter Kit recruit links (impact tile 10), which carries the current state and the open questions — in short: recruit links already carry utm_source=starterkit&utm_medium=<channel> today, but we know of no way to consume those parameters, and whether campaign= can be used instead (and how the kit could acquire stats from it) remains the open ask from above; that conversation continues there. The card omits unshipped tiles by design, so nothing on screen waits for tile 10 — with the split, this task covers exactly the nine shipped tiles and can move to Waiting for Deployment and close once the deploy window (including the tiles-6/7 backfill) has run.

Two small notes for the record:

  • Tile 4's drill-down links MediaWiki:Common.js per the task text. The installer actually writes MediaWiki:Gadgets-definition + MediaWiki:Gadget-HotCat.js; if the link should point at what the kit wrote rather than where HotCat conventionally lives, it is a one-line change (flagged in code).
  • The direct-vs-downstream split floated above stays future work; nothing in the shipped card precludes it (items carry their provenance).

ngkountas opened https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/169

Fix: Re-source the gadget-installs tile from the verification ledger

ngkountas merged https://gitlab.wikimedia.org/repos/lpl/starterkit-v1/-/merge_requests/169

Fix: Attest the gadget-installs tile from the on-wiki install revision