Skip to contentWolf-Rayet

Decision records

ADR-0052 The Lightning reconciliation, and three counted claims the roster got wrong about itself

Accepted2026-09-05Phase 4, reopened

#Context

ADR-0021 reopened the component set to Carbon's published roster and named Lightning exactly once — in section F, which opens *"Six components with no Carbon or Lightning equivalent."* That claim was never checked against anything. Two earlier passes read a Lightning information architecture in a browser, produced a number, and never reconciled it row by row; the number has sat in a handoff as an unverified assumption ever since, and the decision it was supposed to inform — which Lightning-only components to carry and which to drop — has been the oldest open question in the project.

There is a second reason to do this now and it is the stronger one. ADR-0021's roster is the artifact every component count in this system derives from: docs/STATUS.md reads it, check-status-figures.js reads it, and every claim about coverage rests on it. Nothing read the record's own prose against the record's own table.

#Where the list came from, and why this one

salesforce-ux/design-system, ui/components/, read through the GitHub contents API on 2026-09-05: 91 component directories.

The blueprint set rather than the marketing site, and that is a decision. The published information architecture is a rendered application that a fetch returns as an empty shell, it groups and regroups by audience, and it is not versioned against anything. The ui/components/ directory is the set Salesforce actually ships CSS for, it is enumerable, it is in git, and a component is in it exactly when there is something to build. ADR-0021 read Carbon the same way — a sitemap rather than a summary — and this pass re-derived Carbon's own list by the same method to check it: carbon-design-system/carbon-website, src/pages/components/, 44 directories, one of which (overview) is the section index, leaving 43 published components. That is the figure ADR-0021 recorded from the sitemap, confirmed a second way.

#Decision

All 91 Lightning components are reconciled: 61 covered by rows the roster already has, 6 carried onto it as 5 new rows, and 24 dropped by name with a reason.

The roster moves 52 → 57, and the five arrivals are section G. Their individual arguments are in ADR-0021 beside the rows; what they have in common is that each one fills a gap the Carbon reading left, and three of them are gaps this system's own records had already named without noticing there was no component for them.

CarriedLightning slugLayerWhy the roster was short without it
avataravatarEmitter 1–2A system that calls itself human-in-the-loop had nothing that draws a human. presence reports which operators hold a view and has no mark for them
button-groupbutton-groupsArbiter 1–3The first component that ranks a set of *controls*. ADR-0050 supplies the case: an alarm's controls arrive as a set of which one may be the local focus, and a Button cannot allocate
comboboxcomboboxEmitter 1–3The case between select, which closes a set, and search, which opens one. Carbon does not publish it — checked, not assumed
empty-stateillustrationEmitter 1–2ADR-0048's thesis in a different place: "there is nothing here" and "nothing has arrived" render identically as blank, and that record refused a screen that replaces a lie with a blank
page-headerpage-headersScopeThe half of ADR-0046 that was never built. ADR-0040's own remedy names it — *"a disclosure header carries a summary for exactly this"* — and no such header was on the roster

avatar-group maps onto avatar rather than earning a row of its own: a group of avatars is a composition, and the component that would need a record is the one that decides what happens past the overflow, which is overflow-menu.

#Three counted claims the record got wrong about itself

Doing the reconciliation meant reading ADR-0021's prose against ADR-0021's table for the first time. Three claims disagreed, and no check in the repository could see any of them.

1. Section F counted itself as six above a table of five. ADR-0050 removed acknowledge from that section and moved the roster total, and did not move the sentence.

2. The summing sentence said "47 from Carbon's published set." Four of those 47 — status-indicator, queue-item, table-row and navigation-item — are components this system shipped before the roster reopened, which ADR-0021's own Decision section says in the same paragraph as the total. A term had been absorbed rather than added.

3. One row claimed a Carbon slug Carbon does not publish. text-area was filed against text-area; Carbon publishes text-input and folds the multi-line control into it. The component is real and built — the *provenance* was wrong, which is the harder kind of error to notice because everything downstream of it is correct.

And the Lightning half of section F's sentence was false. activity-timeline and feeds are timeline and live-feed. Two of the five have a Lightning equivalent and always did. They stay on the roster, because the argument for them was never that nobody else had thought of a timeline — it is that Lightning's is a record of what happened inside an application and this system's is a persistent Scope that may not demand, which is a different component wearing a shared noun. What changes is the sentence.

Half of that defence was false, and ADR-0073 removed the row it defended. *"A persistent Scope that may not demand"* is true of live-feed and is not true of timeline: a timeline is *of* something — an incident, an asset, a shift — and renders wherever that thing renders, so it does not outlive its route. On the identity test it is region, which is this system's Scope for a place the composer defines. activity-timeline's row below is re-pointed accordingly. feeds is unaffected, and live-feed survives on a dimension this record could not have used, since ADR-0048's vouches was not part of the test until ADR-0071.

#The check

tools/check-roster.js, wired into CI, and its arithmetic half is the smaller half.

The load-bearing assertion is the join to the code: every row marked **built** names a component the package exports, every component the package exports is a row marked built, and the layer each built row declares is the layer its own contract declares. Until this file the roster's build state was prose that agreed with reality by habit — a row could have been marked built and not exist, or exist and be marked scheduled, and every suite in the repository would have stayed green.

The arithmetic half derives all five figures in the record's two counting sentences from the table below them, and a sentence it cannot locate fails rather than skipping (ADR-0041). ADR-0049 drew the line this sits on: docs/STATUS.md has a historical half that must be allowed to stay false, and the roster does not. Every row of it is a claim about now.

This is standing item 18 closing on one document rather than in general. The item asked for arithmetic checking of counted prose outside component.config.json and stalled on the difficulty ADR-0049 later resolved for STATUS. A roster has no historical half, so the difficulty does not arise here.

#Rejected options

Carry Lightning's roster wholesale, as ADR-0021 carried Carbon's. ADR-0021's argument for the full Carbon set was that any line short of it is arbitrary and has to be defended per component. That argument does not transfer, and the reason is in ADR-0021 itself: Carbon was adopted as *an adversarial sample* — forty-odd components assembled by people who were not thinking about this system's premise — and the sample has now been taken. A second full roster is not a second test of §3's authority question; it is 91 more components, most of which answer it identically to one already on the list. What Lightning is good for at this point is finding what Carbon's roster did not contain, which is what a reconciliation does and an adoption does not.

Take the numbers from the earlier passes' browser reading. They were never re-derivable: an unverified count with no artifact behind it, in a handoff, twice. Two sessions carried "Lightning 2's roster is 48" without anyone being able to say which 48. The blueprint directory is 91, and the difference is not a correction of the earlier number so much as evidence that the earlier number was of something else.

Carry progress-ring. An earlier session's recommendation, and it is refused on ADR-0028's own terms. A ring already means something in this system — loading and radio-button draw one, and it says *in progress, duration unknown*. A second ring encoding a quantity would be two meanings for one geometry. The quantity is meter's, and the radial *countdown* is sla-countdown, which is already on the roster and is where that drawing belongs.

Carry tree-grid. Also an earlier recommendation. It is data-table hosting tree-view, and both are on the roster. The one genuinely new question it raises — a collapsed parent row hiding a demand — is ADR-0040, already recorded and already checked as salience — hidden.

Fix the three counted claims and not write the check. The cheapest option and the one this project has already refused twice, in ADR-0047 and ADR-0049, both times after prose repair alone had failed to hold. Three wrong figures in one record found by hand is the third instance.

#Consequences

The roster is 57 components, 25 built. That figure appears here, in ADR-0021, in ADR-0048 and in docs/STATUS.md, and two of those four are now derived rather than written.

The enforcement table gains one row, Roster arithmetic.

Standing item 18 closes for this document and stays open for the rest. docs/PLAYBOOK.md and the other fifty records still carry counted prose nothing reads. The item's own text says extending arithmetic checking cannot be solved by pointing the same patterns at more files, and that is still true: what made this document checkable is that it contains a table its prose counts, and most of the others do not.

Nothing is built by this record. Thirty-two components remain scheduled, and ADR-0050's finding stands over every one of them: read what a row actually says before building it, because a row whose content is a rule is a record and not a component.

#Measured

  • Lightning's blueprint set: 91 component directories, salesforce-ux/design-system ui/components/, 2026-09-05.
  • Reconciled 61 covered + 6 carried + 24 dropped = 91, no row unclassified and no row in two buckets.
  • Carbon re-derived independently: 44 directories, 43 components, matching ADR-0021's sitemap reading.
  • Roster 52 → 57, built 25, both derived from the table by roster:check rather than stated.
  • The roster's four provenance terms, derived: 43 from Carbon, 4 this system's own, 5 r136-native, 5 from Lightning.
  • Three counted claims in ADR-0021 corrected, and one row's Carbon slug.

#The full reconciliation

91 rows, in the directory's own order. covered names the roster row that already answers it; dropped gives the reason.

LightningVerdictRoster row, or the reason
accordioncoveredaccordion
activity-timelinecoveredregion — ADR-0073
alertcoveredalert
app-launchercoveredmenu + modal
avatar-groupcarriedavatar
avatarcarriedavatar
badgescoveredtag
brand-banddroppeddecoration on the substrate
breadcrumbscoveredbreadcrumb
builder-headerdroppedproduct-specific chrome
button-groupscarriedbutton-group
button-iconscoveredbutton
buttonscoveredbutton
cardscoveredtile
carouseldroppedrotates a condition out of the frame
chatdroppedan application surface
checkbox-button-groupcoveredcheckbox + button-group
checkbox-buttoncoveredcheckbox
checkbox-togglecoveredtoggle
checkboxcoveredcheckbox
color-pickerdroppedhands colour to a user
comboboxcarriedcombobox
countercoveredtag
data-tablescovereddata-table
datepickerscovereddate-picker
datetime-pickercovereddate-picker
docked-composerdroppedan application surface
docked-form-footercoveredform
docked-utility-bardroppeda fourth persistent Scope proving nothing new
drop-zonecoveredfile-uploader
dueling-picklistdroppedan authoring affordance
dynamic-iconsdroppedmotion outside the ladder
dynamic-menucoveredmenu
einstein-headerdroppedproduct-specific branding
expandable-sectioncoveredaccordion
expressiondroppeda query builder
feedscoveredlive-feed
file-selectorcoveredfile-uploader
filescoveredtile
form-elementcoveredform
form-layoutcoveredform
global-headercoveredshell-header
global-navigationcoverednavigation-item + shell-header
iconsdroppedan asset set
illustrationcarriedempty-state
inputcoveredinput-field
list-builderdroppedan authoring affordance
lookups-mobiledroppeda platform variant of lookups
lookupscoveredcombobox
mapdroppedpayload the budget does not govern
menuscoveredmenu
modalscoveredmodal
notificationscoveredalert + toast
page-headerscarriedpage-header
panelscoveredshell-left-panel + shell-right-panel
path-simplecoveredprogress-indicator
pathcoveredprogress-indicator
picklistcoveredselect
pillscoveredtag
popoverscoveredpopover
processcoveredprogress-indicator
progress-barcoveredmeter
progress-indicatorcoveredprogress-indicator
progress-ringdroppedthe ring-as-quantity belongs to sla-countdown
promptcoveredmodal
publishersdroppedan application surface
radio-button-groupcoveredradio-button
radio-groupcoveredradio-button
regionsdroppeda layout primitive; Scope is the concept
rich-text-editordroppedan authoring surface over payload
scoped-notificationscoveredalert
scoped-tabscoveredtabs
selectcoveredselect
setup-assistantdroppedproduct-specific onboarding
slidercoveredslider
spinnerscoveredloading
split-viewdroppedtwo Scopes side by side
summary-detailcoveredaccordion
tabscoveredtabs
textareacoveredtext-area
tilescoveredtile
timepickercovereddate-picker
toastcoveredtoast
tooltipscoveredtooltip
tree-griddroppeddata-table hosting tree-view
treescoveredtree-view
trial-bardroppedproduct-specific
vertical-navigationcoverednavigation-item + shell-left-panel
vertical-tabscoveredtabs
visual-pickercoveredtile + radio-button
welcome-matdroppedproduct-specific onboarding