4944b9b3b6
It appears on the dashboard and settings only. It is an invitation, not an alert, so it belongs on pages an admin opens deliberately rather than on top of whatever task they are in the middle of. The activity ticker deliberately did NOT move with it. That ticker is what triggers the daily rollup — the backend has no scheduler — so tying it to the banner would have stopped reporting for an admin who works on Events and never opens the dashboard, and stopped it entirely for a participating install, where the banner never renders at all. The effect stays mounted on every admin page and only the visible aside is scoped. Two tests pin exactly that, because it is the kind of thing a later refactor would helpfully "clean up". Highlighted like the migration banner it sits under: tinted surface, border, icon, a title line above the body. It was previously the same neutral surface as the page behind it and read as filler. "Not now" is now "Ignore". The button calls dismiss(), which persists notice_dismissed on the server — the invitation never comes back. "Not now" promised otherwise. The label says what happens and a hint says where to join later. Only shown while participation is off. activation_pending, deletion_pending and identity_conflict are in-flight states the settings page explains properly; inviting someone to join in the middle of their own withdrawal would be worse than saying nothing. The first version of these tests was worthless: the negative cases asserted absence after waiting only for the status call, so the component was still rendering null for want of data and every one passed with the gates removed. They now wait for the query cache to fill. Removing the route gate fails 4; removing the status gate fails 6. Refs #1110