fix(ui): tailwind-merge стрипал text-body — buttons ушли в 16px inherited

CRITICAL bug — на staging buttons рендерились в 16px вместо handoff
workhorse 13px. UpdateBanner secondary buttons показывали invisible
текст (text-ink overrode by inherited text-on-accent от parent banner).

Root cause:
  buttonVariants base = 'rounded-md text-body font-medium ...'
  primary variant     = 'bg-navy text-on-accent ...'

tailwind-merge default config pattern-matches `text-*` как ОДНУ группу
(color). Видя `text-body text-on-accent` он считал это конфликтом двух
colors → keep last → text-on-accent остаётся, text-body стрипается.
Result: button теряет font-size → inherit body 16px.

Fix: extendTailwindMerge регистрирует наши custom semantic typography
utilities (text-body/cell/mono/cap/title-*) в группе font-size (отдельной
от colors). twMerge теперь знает `text-body text-on-accent` это size +
color, обе сохраняются.

Also: text-mono/text-cap в font-family группе (они baked Mono/Tektur)
— fix конфликт с font-mono/font-display tokens если кто-то их случайно
combined.

Verified в preview: 'Sign in' button class теперь содержит text-body,
computed fontSize: 13px (вместо 16px).

Plus CI: release job image node:22-alpine → repo.nstart.cloud/library/
node:22-alpine (Docker Hub rate-limited, mirror используется во всех
других jobs).
This commit is contained in:
Zimin A.N.
2026-05-11 15:45:27 +03:00
parent 7a3b31b957
commit 9e2d985485
2 changed files with 33 additions and 3 deletions
+1 -1
View File
@@ -393,7 +393,7 @@ trigger-deploy-prod:
# `[skip release]` и semantic-release пропустит этот commit (см. parserOpts). # `[skip release]` и semantic-release пропустит этот commit (см. parserOpts).
release: release:
stage: release stage: release
image: node:22-alpine image: repo.nstart.cloud/library/node:22-alpine
needs: needs:
- job: trigger-deploy-staging - job: trigger-deploy-staging
artifacts: false artifacts: false
+32 -2
View File
@@ -1,12 +1,42 @@
import { type ClassValue, clsx } from 'clsx' import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge' import { extendTailwindMerge } from 'tailwind-merge'
/** /**
* Tailwind class merger — combines clsx (conditional classes) + tailwind-merge * Tailwind class merger — combines clsx (conditional classes) + tailwind-merge
* (resolves conflicts: e.g. `px-2 px-4` → `px-4`). * (resolves conflicts: e.g. `px-2 px-4` → `px-4`).
* *
* Standard shadcn/ui utility, used by every primitive variant. * Standard shadcn/ui utility, used by every primitive variant.
*
* <p>tailwind-merge по умолчанию не знает наши custom typography utilities
* (text-body, text-cell, text-mono, text-title-xl/lg/md, text-cap) — он
* pattern-matches `text-*` как color class, поэтому считал `text-body text-ink`
* конфликтом (двух color rules) и убирал первый. Результат: buttons теряли
* font-size, наследовали 16px от body.
*
* <p>extendTailwindMerge регистрирует наши utilities в группе `font-size`
* (separate от colors), и в группе `font-family` для text-mono/text-cap
* (которые baked в Mono/Tektur respectively). Теперь `text-body text-ink`
* корректно сохраняется как size + color combination.
*/ */
const customTwMerge = extendTailwindMerge({
extend: {
classGroups: {
'font-size': [
'text-body',
'text-cell',
'text-mono',
'text-cap',
'text-title-xl',
'text-title-lg',
'text-title-md',
],
// text-mono / text-cap baked font-family. Регистрируем отдельной группой
// чтобы конфликтовали с `font-mono` / `font-display` корректно.
'font-family': ['text-mono', 'text-cap'],
},
},
})
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return customTwMerge(clsx(inputs))
} }