Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

[Bug]: ComposerBanner's `+ :has()` variant makes banner changes restyle the whole page on every thread switch

Aperta Adatta ai principianti
#13,851 3 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
88/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
tailwindcss, typescript

Direzione di ricerca

Inizia in apps/web/src/components/chat/ComposerBanner.tsx alla riga 114 e confronta il selettore emesso con la riproduzione minima di Chromium nell’issue. Verifica la modifica con i casi di equivalenza dei selettori elencati e una traccia Performance mentre attivi o disattivi il banner oppure passi da un thread all’altro. Il lavoro è completato quando lo stile del banner rimane equivalente, mentre viene ridotta l’invalidazione ampia degli stili.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

needs more info via-triage
Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.
Area

apps/web

Steps to reproduce
  1. Build main (measured at 95030dc674) and open the web app in Chromium with a populated sidebar.
  2. Record a DevTools Performance trace while switching between threads.
  3. Look at the "Recalculate Style" events during each switch.

The minimal reproduction below needs no T3 Code. Open it in Chromium, record a Performance trace, and click the button:

<!doctype html>
<style>
  /* ComposerBanner.tsx:114, as Tailwind compiles it */
  .X + :has([data-chat-composer-form]) [data-chat-composer-form] > [data-slot=composer-banner-attachment]:first-child [data-composer-banner-surface=attached]::before { border-radius: 0 }
  /* any group-has-* utility; its group does not even need to be on the page */
  :is(:where(.group\/surface):has([data-banner=attached]) *) { box-shadow: none }
</style>
<button id="toggle">toggle banner</button>
<div id="list"></div>
<div id="stack"><div id="banner" class="X">banner</div><div><form data-chat-composer-form><div data-slot="composer-banner-attachment"><div data-composer-banner-surface="attached"></div></div></form></div></div>
<script>
  document.getElementById("list").innerHTML = "<div><span>row</span></div>".repeat(1500);
  const stack = document.getElementById("stack");
  const banner = document.getElementById("banner");
  document.getElementById("toggle").onclick = () => (banner.isConnected ? banner.remove() : stack.prepend(banner));
</script>
Expected behavior

Adding or removing a banner restyles the banner's neighbourhood, as it does when either rule is present alone.

Actual behavior

The Tailwind arbitrary variant in apps/web/src/components/chat/ComposerBanner.tsx:114 compiles to a bare :has() after a sibling combinator:

.X + :has([data-chat-composer-form]) [data-chat-composer-form] > [data-slot=composer-banner-attachment]:first-child [data-composer-banner-surface=attached]::before

When a group-has-* utility is in the same stylesheet (main ships many), adding or removing a sibling in that area makes Chromium restyle almost the whole document. The reproduction above, 20 banner toggles, counted with the trace's UpdateLayoutTree.elementCount:

Stylesheet in the reproduction Elements restyled per banner toggle (of 3,013)
no :has() rules 1
group-has rule only 1
ComposerBanner rule only 6
both, as in the file above 2,861
both, with the suggested rewrite below 6

The group-has rule's group element is not on the page at all; the rule only has to be in the stylesheet.

A thread switch adds and removes these siblings, so every switch pays for it. Measured on a main build (95030dc674, default settings), 13 scripted thread switches in headless Chromium, three rounds each:

As shipped Only the two ComposerBanner rules rewritten
Recalcs touching ≥1,000 elements 17 1
Elements restyled 32.3k 12.4k (−62%)
Style recalculation time 315 ms 156 ms (−50%)
Main thread blocked in long tasks, rapid switching 728 ms 461 ms (−37%)
Click → next paint, p50 74 ms 59 ms
Keydown → next paint, p95 54–64 ms 25–26 ms

The element counts come from Chromium's own trace and do not depend on machine load; they repeated exactly across rounds.

Suggested fix: remove the :has(), which is redundant. The rest of the selector already requires a [data-chat-composer-form] descendant of that sibling, so + * matches the same elements:

-        "[&+:has([data-chat-composer-form])_[data-chat-composer-form]>[data-slot=composer-banner-attachment]:first-child_[data-composer-banner-surface=attached]]:before:rounded-none [&+:has([data-chat-composer-form])_[data-chat-composer-form]>[data-slot=composer-banner-attachment]:first-child_[data-composer-banner-surface=attached]]:before:border-t-0",
+        "[&+*_[data-chat-composer-form]>[data-slot=composer-banner-attachment]:first-child_[data-composer-banner-surface=attached]]:before:rounded-none [&+*_[data-chat-composer-form]>[data-slot=composer-banner-attachment]:first-child_[data-composer-banner-surface=attached]]:before:border-t-0",

Checked equivalence: computed ::before styles are identical for the old and new selector with the form host after the attachment, with no attachment before it, with the attachment not first, and with the form as the sibling itself. With the rewrite, the reproduction's banner toggle restyles 6 elements instead of 2,861.

Impact

Major degradation or frequent failure

Version or commit

main @ 95030dc674 (rule introduced in #8693)

Environment

Linux, Chromium (headless, for the measurements) and the Electron desktop app. The effect is in Chromium's :has() invalidation, so every Chromium-based client is affected.

Logs or stack traces

The trace shows ScheduleStyleInvalidationTracking with changedPseudo: "has" walking every ancestor up to <html>, followed by StyleInvalidatorInvalidationTracking "Invalidation set invalidates subtree" with allDescendantsMightBeInvalid: true on each of them.

Screenshots, recordings, or supporting files

The reproduction above.

Workaround

None for users. Removing the two classes, or applying the suggested rewrite, removes the effect.

Lingua principale
TypeScript
Stelle
24k
Fork
6.3k
Merge medio
10h 46m
PR unite (30g)
299

Preparare l'ambiente

Apri in Codespaces

Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di pingdotgg/t3code

Tutte le issue di pingdotgg/t3code

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.