Skip to content

Tie the AG page's rollup numbers to their labels the way the fleet page's now are #3045

Description

@erikdarlingdata

#3038 fixed the fleet page's rollup tiles: every tile's number is now programmatically tied to the text that says what it counts, so a consumer reaching the number's node on its own gets the figure and its meaning together. The AG page's rollup has the identical defect and was deliberately left out of that PR, because the two pages' tile builders share no code path and one PR touching both would have been reviewed as two changes wearing one number.

The site

Darling/PerformanceMonitor.Darling.Service/wwwroot/js/pages/ag.js, rollup() at line 97:

const tile = (num, lbl, cls) =>
  el("div", { class: "tile " + (cls || "") }, [
    el("div", { class: "num", text: fmtInt(num) }),
    el("div", { class: "lbl", text: lbl }),
  ]);

Three tiles ride on it — Groups, Reporting servers, Views. The number and its label are sibling divs and nothing else. Reading order leaves the relationship inferable but not determinable, which is the same thing #3031 was about.

Correcting how this was scoped when #3031 closed

I described the fix as "the util.js hoist is the shape." There is no util.js hoist. rollupTextId and the wired tile closure both live in fleet.js (lines 480–525) and nothing in util.js exposes a tile or id helper. So this is not "call the existing shared helper" — the shared helper does not exist yet, and creating one is the actual decision this issue carries.

Two ways to go, and the choice is worth making deliberately rather than by whichever is quicker:

  • Hoist rollupTextId into util.js and have both pages use it. It is eight lines with no page-specific knowledge — a label, a part name, and a used set — so it hoists cleanly. This keeps one de-duplication rule for both pages, which matters because the rule is subtle (see below).
  • Duplicate the id derivation in ag.js. Cheaper to review in isolation, and leaves the two pages free to diverge. But it makes the de-duplication rule a thing that exists twice, and a rule that exists twice is a rule that will be fixed once.

I lean to the hoist, for the reason fleet.js already documents: the used set exists because two tiles sharing a label emit a duplicate id, and aria-describedby resolves a duplicate to the FIRST match — a silent mis-association rather than a visible break. That is the kind of hazard you want one implementation of. AG's three current labels are distinct, so the set is inert there today, which is exactly why a local copy would be written without it and be correct until someone adds a fourth tile.

What not to copy from fleet.js

The sub-line machinery. fleet.js's tile takes sub and title because the fleet rollup carries a coverage sub-line and a long coverage note; the AG rollup has neither, and six of the seven fleet tiles have no sub-line either. subId should not appear in an AG implementation just because it appears in the model. The part that transfers is the number describing itself with the label's id.

Also worth keeping: the reason the label rides in aria-describedby rather than aria-label or aria-labelledby — .num is a plain div, ARIA prohibits naming role=generic, so a name set there is invalid and may be dropped, while a description is a supported property. That reasoning is page-independent and should not be re-derived.

Verification this needs

Whatever shape it takes, the guard should fail on a tile whose number carries no description — not merely pass on the tiles that have one. A positive-only check cannot find an unremembered site, which is the whole reason this page survived #3031.

Activity

  1. erikdarlingdata commented on Sep 6, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Closed by #3054, merged to dev at f1bf0386079e8170e5dda3181f8f91eb65000b0c. Closing by hand because a closing keyword does not fire against dev.

    Every rollup figure on both web pages is now programmatically tied to the text that says what it counts.

    The decision this issue carried was the hoist, and it went the way the issue argued. rollupTextId moved from fleet.js into util.js — identical except function → export function, which is the keyword the hoist requires; worth stating precisely so a later reviewer who diffs it and finds a difference does not think something slipped. Both pages now share one de-duplication rule.

    The grounds, because they are the interesting part: the used set is inert on the AG page today, since its three labels differ and cannot collide. That is exactly why a local copy would have been written without it and stayed correct by luck until a fourth tile repeated a label — at which point aria-describedby resolves a duplicate id to the FIRST match, a silent mis-association rather than a visible break.

    subId deliberately did not come across, and its absence is pinned rather than left to the next reader's restraint: the AG tiles qualify nothing and hover over nothing, so there is no second text row for an id to point at, and hoisting an unused parameter invites a caller to pass something meaningless.

    The guard fails on the defect rather than passing on the fix, which is what this issue asked for. ag.js restored to what dev shipped reds four tests; .num renamed on both pages reds the population floor with found 0. Nine mutations, each landing on a different named assertion, run against a sandbox copy after commit.

    The subtle part is worth recording: "every tile is wired" is not a per-tile source fact, because three tiles come from one closure. EveryRollupPage_BuildsEveryTileFromOneClosure is what converts it into one — tiles == 1 && figures == 1 per page means a hand-rolled fourth tile is a second construction and reds even if it carries a description.

    Not verified: nothing was rendered and no screen reader was run. Behaviour was exercised under a DOM shim in node against the shipped modules, including the duplicate-label control — guard in place, the third tile gets rollup-groups-lbl-2; guard removed, the figure reading 9 is described by the label of the tile reading 4.

    The same gap exists on the desktop surface and is filed separately rather than left in a chip. It is larger than this issue: AutomationProperties appears nowhere in the Viewer, PerformanceMonitor.Ui or Lite.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions