Background
KeyHighlights.vue renders three of the mockup's five tiles:
| Tile | State |
|---|---|
| wikis using the kit | shipped, drillable |
| users onboarded | shipped, drillable |
| tasks completed | shipped, drillable |
| edits made using the kit | missing |
| edits made on mobile | missing |
This is deliberate, not an oversight — the component says so at the tiles computed: "Only the three metrics we have real data for today; the mockup's two edit tiles await an aggregate source." Nothing on the card is wrong or placeholder-filled, so there is no user-facing defect; the card simply shows less than it was designed to.
The mockup captions those two tiles "edits"; this task ships them as contributions instead, for the reason set out under Why "contributions" and not "edits".
The blocker was definitional, not visual — and it is now resolved: the two numbers are the "Essential work" card's own total and its mobile subset, from rows that already exist. What is left is two queries, two tiles, two messages, and one presentation decision (see the last pin under Mobile).
What T433621: Add an "Essential work done through the kit" stats card to the /impact page's aggregation actually stores
The reach aggregation this task rides now exists in code. Read it before designing against it, because its grain is not the grain these two tiles want:
| What shipped | Where |
|---|---|
| essential_work_items — one row per artifact, UNIQUE(tile, scope, title, subject), windowed on item_ts (the on-wiki revision time), with a private username and a JSON detail | server/db/schema.sql |
| essential_work_scan_state — per-scanner cursors and immutable verdicts (activation predicate, resolved langlink targets, the translatewiki core key set) | server/db/schema.sql |
| The scanners: policy pages, core translatewiki messages, articles created, HotCat articles, infobox transclusions | server/services/essential-work-scan.js, server/services/translatewiki.js |
| Tile counts as COUNT(*) over item rows, drill-downs as the same rows | server/db/impact.js (getEssentialWork, ESSENTIAL_ITEM_QUERIES) |
| A tags column carrying the qualifying revision's change tags — this task's mobile source, landed early on purpose (see below) | essential_work_items.tags |
Two consequences follow, and they shape everything after this:
- The grain is artifacts, not edits. UNIQUE(tile, scope, title, subject) means an article created and then edited fifty times is one row. COUNT(*) over items is an outputs count —labelling it "edits made using the kit" would be wrong, and it would also restate the card directly below it on the same page. Hence the naming rule further down.
- The scans are filtered, so they cannot see a broader edit set. They fetch ucshow=new creations, uctag=contenttranslation publishes and HotCat-summary edits — an activated user's ordinary editing is never fetched at all. Any definition wider than "the work behind the artifacts" therefore needs a new, unfiltered scan, not a filter change. This is not a limitation to work around: it is the scope, per the decision below.
The mobile source already exists
Tag capture is done — it shipped inside T433621's stack rather than waiting for this task, because the window for getting it free closes at deploy:
- essential_work_items.tags holds the qualifying revision's change tags: the creation, the categorizing edit, the first post-link translation.
- Encoded pipe-delimited with sentinels — |mobile edit|mobile web edit| — so tags LIKE '%|mobile edit|%' is a whole-tag match. |advanced mobile edit| correctly does not satisfy it.
- '||' means "captured, no tags" and licenses reading a row as non-mobile. NULL means "not captured" — a row written before capture existed. A mobile count must therefore filter on the tag, never on NOT LIKE, or NULL rows would silently read as desktop.
- The raw tag list is stored rather than a derived is_mobile flag, so the umbrella-tag rule stays read-side and can change without rescanning anything.
- Infobox transclusions carry tags too. They are the one item type with no obvious "qualifying revision", since embeddedin names a page rather than an edit; T433621: Add an "Essential work done through the kit" stats card to the /impact page's scanner resolves the revision that introduced the transclusion, so these rows are both truthfully dated and taggable.
Why it did not wait for this task: essential_work_items did not exist in production yet, so capturing tags before the first scan pass covers every row ever written at the cost of one parameter per request. Afterwards it would need a revid-by-revid backfill. This task therefore has no data work left, provided T433621: Add an "Essential work done through the kit" stats card to the /impact page deploys with tag capture in it, which it does.
Definition — the work behind the reach tiles
T433621: Add an "Essential work done through the kit" stats card to the /impact page defines what qualifies as an essential article added, an interface message translated, a policy page added, an article categorized with HotCat, a main page customized and an article given an infobox — and nearly all of those outcomes are MediaWiki edits. So this tile needs no new metric invented for it: it is those same outcomes rolled into one number, and it must use the same scope and the same grain as the card — one row per artifact, not per revision.
That scope is settled: /impact reports kit-seeded reach, not direct task completions. Two consequences that shape everything below:
- The numbers come from usercontribs scans of kit-connected users, bounded by their activation anchor. The kit's own database supplies only the seed sets and anchors (twn_links, install/import/publish events, first-task-contact anchors, imported-template titles) — never the values themselves.
- Reading task_verifications rows as the tile value is the rejected direct-completions alternative recorded in T433621: Add an "Essential work done through the kit" stats card to the /impact page. It is cheaper and exact, but it answers a different question (how many runs the kit observed) and belongs to task analytics (T429291: Improve the visualization of the Sankey user journeys analytics graph), not to /impact.
What counts as a contribution — DECIDED
Six of the card's tiles roll up. Each counted thing is a page or a message that someone produced,
which is what makes one total meaningful:
| Contribution | Source | Can it be mobile? |
|---|---|---|
| articles created | item rows | yes |
| categories added (HotCat) | item rows | yes |
| policy pages added | item rows | yes |
| interface messages translated | item rows (translatewiki.net) | yes |
| pages given a kit-imported infobox | item rows | yes — since T433621: Add an "Essential work done through the kit" stats card to the /impact page resolves each transclusion's introducing revision |
| main pages added | analytics_events (starterkit.wizard.publish) | no — the kit's own server-side write carries no platform tag |
Three of the card's tiles do not roll up: HotCat gadget installs and infobox template imports are infrastructure the kit put in place rather than contributions to the wiki's content, and recruit shares are an off-wiki action with no artifact at all. They stay on the card below, uncounted here.
So the headline is not the sum of the whole card — a reader adding up all nine tiles gets a larger number. The metric definition must name the six.
Interface messages are the one judgment call. They are genuine contributions, and T433621: Add an "Essential work done through the kit" stats card to the /impact page counts them, but they happen on translatewiki.net rather than on the wiki this dashboard describes. They are included here on the same footing as the rest; if that ever reads wrong, the fix is a footnote or a tile of their own, never a silent removal.
Main pages come from a different table. They are counted in getEssentialWork from analytics_events, not essential_work_items. The total is therefore a sum across two sources with their own exclusion fragments — compose it as one SQL union in db/impact.js so both carry the production exclusions, and say so in the metric definition.
Why "contributions" and not "edits"
Item rows dedupe to artifacts: a page created and then improved fifty times is one row. So the number is a count of things produced, not of revisions, and every constituent has at least one edit behind it — it is a floor on editing activity, never the edit count.
That is the deliberate choice, not a limitation to apologise for. The alternative — a companion (wiki, user, revid, ts, tags, tile) table counting qualifying revisions — would cost a second table and a second grain, start empty at deploy with no history, and measure something the card is not about (how many edits one categorization run took).
The consequence is a naming rule: the caption cannot say "edits made using the kit." Get that line wrong and the tile silently claims more than it counts. Say what is counted — "contributions through the kit" — and let the metric definition spell out the grain.
Rejected: all edits by activated users
Counting every edit an activated user makes after their anchor was considered and rejected twice over. It would count unrelated editing as kit impact, which T433621: Add an "Essential work done through the kit" stats card to the /impact page's own boundary forbids ("never a wiki's unrelated activity") — and the shipped scans are filtered at the API call, so it would need a whole new unfiltered scan with its own cost and cache, breaking the "same rows" guarantee the mobile number depends on.
Mobile came free from the same scans
usercontribs returns change tags per edit — so the mobile number costs no extra API calls: it is a WHERE clause over rows the scanners already wrote, at the same freshness, from the same responses. That is why capture landed with the aggregation instead of here.
Verified live:
- ucprop=ids|timestamp|tags returns a tags array on every contribution (e.g. ["wikieditor"], ["wikieditor","visualeditor-switched"]).
- uctag=mobile edit filters correctly and returns only mobile edits.
What the artifact grain means for this tile: the revision whose tags are stored is the qualifying one (the creation, the CX publish, the categorizing edit). A page created on desktop and later improved from a phone reads as non-mobile. That is defensible — the tile then means "the counted work was done on mobile" — but it must be the stated definition, not an accident of the grain.
Three pins from the data:
- Use mobile edit as the umbrella tag and never sum tags. A single kcg.wikipedia revision (47825) carries mobile web edit, mobile edit and advanced mobile edit at once — summing tag counts would treat one edit as three. The stored encoding makes the correct check a one-liner: tags LIKE '%|mobile edit|%'.
- Filter on the tag, never on its absence. NULL tags mean "not captured", not "desktop", so a NOT LIKE formulation would count uncaptured rows as non-mobile.
- The two tiles do not quite share a denominator, and the layout must not imply they do. Main pages can never be mobile — the kit writes them server-side from Toolforge, with no platform tag — so the mobile number is drawn from five of the six contribution types while the headline counts all six. The gap is small (one main page per wiki), but a reader dividing the two tiles gets a share of the wrong denominator. Either caption the mobile tile as a share of the five, or do not place the two adjacent. Decide before shipping.
Unlike a kit-authored-only definition, this yields a meaningful mobile share: these are real edits made by humans in browsers and apps, on wikis that skew heavily mobile — kcg.wikipedia's all-time mobile edit count is 23,216. That figure describes the wikis, not the counted set: it is evidence that a mobile share of the counted work will be non-trivial, not a prediction of the share itself, which only the first scheduled pass can give. The absolute numbers are a different matter — see below.
Verified
- usercontribs exposes per-edit tags and supports uctag filtering (both confirmed against kai.wikipedia and kcg.wikipedia). Confirmed again against kcg.wikipedia when capture shipped: ucprop=…|tags and prop=revisions&rvprop=…|tags both return the array under formatversion=2.
- Tag capture's own smoke (scratch database, stubbed API): every scanner stores the expected encoding, and the umbrella predicate matches mobile edit rows while rejecting advanced mobile edit alone, '||' and NULL.
- The infobox-transclusion resolver's smoke: pages are dated to the revision that introduced the template and carry that revision's tags; a transclusion that predates the kit's import is detected and excluded rather than dated to an unrelated edit.
- One revision can carry several mobile tags simultaneously — hence the no-summing pin above.
- AQS cannot answer the mobile question. https://wikimedia.org/api/rest_v1/metrics/edits/aggregate/ returns real data, but its only dimensions are editor-type and page-type; there is no platform dimension. It is therefore useless for this tile even as a cheap approximation.
- Change-tag hitcounts (list=tags&tgprop=hitcount) give a per-wiki, all-time, wiki-global mobile count in one call. Not usable for a kit-scoped or windowed number, but worth knowing if a community-health "how mobile is this wiki?" tile is ever wanted — that would be a different metric with a different caption.
What the numbers will look like
The mockup's values (56,554 and 22,704) are placeholders, not targets, and nothing in this design will produce five digits. The tile counts artifacts produced by kit-activated users, which T433621: Add an "Essential work done through the kit" stats card to the /impact page itself expects to be small on day one and to grow as the scanners cover more wikis. Expect hundreds, not tens of thousands, and treat a suspiciously large number as evidence the activation anchor is not being applied rather than as success. T433621: Add an "Essential work done through the kit" stats card to the /impact page's first scheduled pass after deploy is the calibration point — and because it will already be capturing tags, the mobile share is known from that same pass rather than after a later backfill.
Say this to whoever owns the mockup before the tiles ship: the design implies a scale the honest metric will not reach, and a tile reading 312 under a caption drawn for 56,554 invites the question "is it broken?".
Proposed approach
There is no data work left. Tag capture shipped with T433621: Add an "Essential work done through the kit" stats card to the /impact page; both numbers are queries over rows that already exist. What remains:
- Two counts in db/impact.js, added to getImpactSummary's totals beside tasksCompleted: the rollup (item rows for the five item-backed contributions, unioned with the main-pages analytics_events count, so both sources carry the production exclusion fragments) and its tags LIKE '%|mobile edit|%' subset over the item rows. Both windowed on item_ts so they honour the days selector like everything else on the page. Put them next to getEssentialWork — the card and its headline must not drift apart.
- Two tiles in KeyHighlights.vue, continuing the mockup's accent sequence (blue, light blue, purple, then the two greens). Not drillable: no identity drill-down is defined for them, and the existing ones are maintainer-only because they expose usernames. A public outputs drill-down is possible later — the item rows carry titles and URLs — but it would restate the card below.
- Two i18n messages in en.json + qqq.json only (translated files are translatewiki-managed), saying what is counted rather than promising an edit count.
- The metric definition, alongside the existing ones: the six contributions that roll up and the three card tiles that don't, the translatewiki.net inclusion, that main pages can never be mobile, and that "mobile" describes the revision that produced the contribution.
The one genuinely unresolved thing is presentational: how the two tiles sit next to each other given they don't quite share a denominator (pin 3 above).
Acceptance criteria
- Key Highlights renders all five tiles from the mockup, in the mockup's order and accent colours
- Both values come from the essential_work_items rows and kit-action events the "Essential work" card already counts — not from task_verifications (the rejected direct alternative), and not from a second aggregation
- The rollup equals the sum of the six contribution tiles on the card for the same period; the three excluded tiles (gadget installs, infobox imports, recruit shares) are not in it
- The caption does not claim a number of edits; it says what is counted, and the metric definition states the artifact grain
- The activation anchor is applied: work done by a kit-connected user before their anchor is never counted (inherited from the scanners' immutable activation verdicts)
- editsOnMobile is the mobile edit-tagged subset of the same rows — never a separately sourced number, and never a NOT LIKE over uncaptured (NULL) tags
- The mobile count uses the mobile edit umbrella tag only — no tag summing
- The two tiles' relationship is presented so that no false percentage is implied (they do not share a denominator)
- Pages given a kit-imported infobox ARE counted, dated by the revision that introduced the transclusion — not by when a scan discovered it
- Both values respect the days period selector, windowed on item_ts like the card below
- No external API call happens during a page load — both tiles read the stored rows
- A missing or failed aggregation renders — (the component's existing null handling), never 0
- The metric definition records: the six contributions that roll up and the three card tiles that don't, the translatewiki.net inclusion, that main pages can never be mobile, and that "mobile" describes the revision that produced the contribution
- The three existing tiles are unchanged in value, order and drill behaviour
- The row still wraps cleanly at phone widths (the flex row already wraps; five tiles is the new worst case)
- New i18n keys added to en.json + qqq.json only
Out of scope
- The per-wiki filter (T433618: Add a wiki filter to the /impact dashboard) — these tiles inherit it when it lands, like the rest of /impact.
- Drill-downs for the two new tiles.
- A wiki-global "how mobile is this community?" tile (possible in one call per wiki, but a different metric with a different scope and caption).
- Revisiting T433621: Add an "Essential work done through the kit" stats card to the /impact page's reach-vs-direct scope decision. This task follows it; it does not reopen it.
Dependencies and sequencing
- Reads T433621: Add an "Essential work done through the kit" stats card to the /impact page's reach aggregation, which is implemented — the per-user usercontribs scans with activation anchors, scheduled every 6h and stored as item rows, change tags included. This task adds two derived totals and two tiles. It must not build a second aggregation, and it needs no new scanner work at all.
- Sequenced after T433621: Add an "Essential work done through the kit" stats card to the /impact page's MRs land, purely so the rows and the column exist. No schema change, no migration, no backfill — the entire data dependency shipped there.
- Definitionally coupled to T433621: Add an "Essential work done through the kit" stats card to the /impact page: the headline is that card's total, so if a tile's definition changes there, this number changes with it. Same for the drill-down invariant.
- The deploy coupling that mattered has already been honoured: tag capture had to be in the aggregation's first production run or the mobile number would have started blind. It is.
- Nothing shows a number until T433621: Add an "Essential work done through the kit" stats card to the /impact page deploys and its first scan passes complete. The inputs exist in code; the rows do not exist yet in production.
- A companion edit-level table (counting revisions rather than contributions) is the only variant that would reopen a schema question; it is not the plan.


