@sableui/react 0.6.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # What's new
2
2
 
3
+ ## 0.6.1
4
+
5
+ - **`sable lint` catches a status fill retuned without its text.** Since 0.6,
6
+ `text-destructive`, `border-destructive`, `ring-destructive` and the rest read
7
+ `--destructive-text`; only `bg-destructive` reads the fill. An app that set
8
+ `--destructive` (or `--success`, `--tone-warning`, `--tone-info`) and stopped
9
+ there painted its buttons in its own red and its error text, borders and rings
10
+ in the theme's, and nothing said so. The new consumer rule `status-text-drift`
11
+ reads the app's stylesheets for a status fill set without its `-text` in the
12
+ same theme and mode, and names the mode that is missing. It needs only the
13
+ stylesheet, so it reports even where `label-contrast` cannot measure. `sable
14
+ grade` runs it too.
15
+
3
16
  ## 0.6.0
4
17
 
5
18
  ### Art from the theme
@@ -27,7 +27,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
27
27
  import { dirname, join, relative, resolve, sep } from "node:path"
28
28
  import { fileURLToPath } from "node:url"
29
29
  import { ignoreGlob, lintText, makeRules, report } from "../scripts/lint-rules.mjs"
30
- import { labelContrastScanner } from "../scripts/label-contrast.mjs"
30
+ import { labelContrastScanner, statusTextDrift } from "../scripts/label-contrast.mjs"
31
31
 
32
32
  const HERE = dirname(fileURLToPath(import.meta.url))
33
33
  const PKG_ROOT = join(HERE, "..")
@@ -54,7 +54,7 @@ const motionTokens = new Set([
54
54
  REGISTRY.tokens?.motion?.scale,
55
55
  ].filter(Boolean))
56
56
 
57
- const rules = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
57
+ const rules = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default), statusText: (css) => statusTextDrift(css, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
58
58
 
59
59
  /* ── Args. ─────────────────────────────────────────────────────────────────── */
60
60
  const args = process.argv.slice(2)
package/dist/index.d.ts CHANGED
@@ -7640,6 +7640,6 @@ interface UseCopyResult {
7640
7640
  */
7641
7641
  declare function useCopy(text: string, { timeout }?: UseCopyOptions): UseCopyResult;
7642
7642
 
7643
- declare const SABLE_UI_VERSION: "0.6.0";
7643
+ declare const SABLE_UI_VERSION: "0.6.1";
7644
7644
 
7645
7645
  export { Accordion, AccordionItem, AccordionPanel, AccordionTrigger, AlertDialog, AlertDialogClose, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, AppHeader, type AppHeaderProps, AppShell, type AppShellProps, AreaChart, type AreaChartProps, type ArtTone, Attachment, type AttachmentProps, Avatar, AvatarFallback, AvatarGroup, AvatarGroupCount, type AvatarGroupCountProps, type AvatarGroupProps, AvatarImage, type AvatarProps, AvatarRoot, BADGE_APPEARANCES, BADGE_TONES, Badge, type BadgeAppearance, type BadgeProps, type BadgeTone, Banner, type BannerProps, BarChart, type BarChartProps, BottomNav, BottomNavItem, type BottomNavItemProps, BreadcrumbItem, type BreadcrumbItemProps, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbPage, type BreadcrumbPageProps, BreadcrumbSeparator, type BreadcrumbSeparatorProps, Breadcrumbs, type BreadcrumbsProps, Browser, type BrowserProps, Bubble, type BubbleProps, BubbleReaction, type BubbleReactionProps, BubbleReactions, type BubbleReactionsProps, Button, type ButtonProps, CHART_SERIES_COUNT, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, type CalendarProps, Card, CardAction, CardContent, type CardContentProps, CardDescription, CardFooter, type CardFooterProps, CardHeader, CardList, type CardProps, CardTitle, type CardTitleProps, Carousel, type CarouselProps, type CarouselSlidesToShow, type ChartCategories, type ChartCenterLabel, type ChartCurve, type ChartDatum, type ChartSeries, type ChartTone, type ChartTooltipIndicator, type ChartTooltipOptions, type ChartValueFormatter, ChatComposer, type ChatComposerProps, Checkbox, CheckboxCard, type CheckboxCardProps, type CheckboxCardTone, CheckboxGroup, type CheckboxGroupProps, Code, CodeBlock, type CodeBlockProps, CodeHighlightProvider, type CodeHighlighter, type CodeProps, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, type ColorFieldProps, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, type ComboboxItemGroup, ComboboxItemIndicator, ComboboxList, ComboboxPortal, type ComboboxProps, ComboboxValue, ComboboxVirtualList, type ComboboxVirtualListProps, type CommandFilter, type CommandItem, CommandMenu, CommandMenuGroup, type CommandMenuGroupProps, CommandMenuItem, type CommandMenuItemProps, CommandMenuPanel, type CommandMenuPanelProps, type CommandMenuProps, CommandMenuTrigger, type CommandMenuTriggerProps, type ComponentGroup, type ComponentGuidance, ComponentMeta, type ComponentRegister, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DateTile, type DateTileProps, Density, DensityProvider, Density as DensityValue, DestructiveActionModal, type DestructiveActionModalProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogPortal, type DialogSize, DialogTitle, DialogTrigger, EmptyState, type EmptyStateProps, Entity, type EntityProps, Feedback, type FeedbackData, type FeedbackProps, type FeedbackRating, Field, type FieldProps, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, type FileDropzoneProps, type FileRejection, type FileRejectionCode, Form, type FormProps, GlidingHighlight, type GlidingHighlightProps, GradientArt, type GradientArtProps, type Hsv, InlineStatus, type InlineStatusProps, type StatusTone as InlineTone, Input, type InputProps$1 as InputProps, Kbd, KbdGroup, type KbdGroupProps, type KbdProps, Label, type LabelProps$2 as LabelProps, LineChart, type LineChartProps, Marker, type MarkerProps, Marquee, type MarqueeProps, Menu, MenuCheckboxItem, MenuContent, MenuGroup, MenuGroupLabel, MenuItem, MenuPortal, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuSubmenu, MenuSubmenuTrigger, MenuTrigger, Message, type MessageProps, MessageScroller, type MessageScrollerHandle, type MessageScrollerProps, Meter, MeterIndicator, MeterLabel, type MeterSize, type MeterTone, MeterTrack, MeterValue, MiddleTruncate, type MiddleTruncateProps, type MotionPolicy, MotionProvider, type MotionProviderProps, Note, type NoteAction, type NoteProps, NotificationBadge, type NotificationBadgeProps, NumberField, type NumberFieldProps, type OrbMode, type OrbSize, OtpField, type OtpFieldProps, OverflowMenu, type OverflowMenuProps, PageHeader, type PageHeaderProps, PageLoader, type PageLoaderProps, Pagination, type PaginationProps, Panel, PanelGroup, PanelResizer, type PanelResizerProps, PanelSection, type PanelWidthBounds, Phone, type PhoneProps, PieChart, type PieChartProps, Pill, type PillProps, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, Presence, type PresenceProps, type Presentation, PresentationProvider, Progress, ProgressIndicator, ProgressLabel, type ProgressSize, ProgressTrack, ProgressValue, PropertyPair, PropertyRow, RadarChart, type RadarChartProps, RadialChart, type RadialChartProps, Radio, RadioCard, type RadioCardProps, type RadioCardTone, RadioGroup, RelativeTime, type RelativeTimeProps, Reveal, type RevealProps, type Rgb, SABLE_UI_VERSION, SEGMENTED_BAR_TONES, STATUS_TONES, STATUS_TONE_ICON, type ScrollCommandOptions, Scroller, type ScrollerProps, ScrubField, type ScrubFieldProps, SearchInput, type SearchInputProps, SegmentedBar, type SegmentedBarProps, type SegmentedBarSegment, type SegmentedBarTone, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectPortal, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps$3 as SeparatorProps, SettingsMenu, type SettingsMenuProps, SettingsRow, type SettingsRowProps, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, type ShowMoreProps, SideNav, type SideNavActiveVariant, SideNavGroup, type SideNavGroupProps, SideNavItem, type SideNavItemProps, type SideNavProps, type SideNavSize, Sidebar, SidebarFooter, SidebarHeader, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarProps, SimpleTooltip, Skeleton, Slider, SliderControl, SliderIndicator, SliderRoot, SliderThumb, SliderTrack, SliderValue, Sparkline, type SparklineProps, Spinner, type SpinnerProps, StatusBar, StatusBarGroup, StatusBarSeparator, StatusDot, type StatusDotProps, type StatusTone, type StepStatus, Stepper, StepperItem, type StepperItemProps, type StepperOrientation, type StepperProps, StreamingText, type StreamingTextProps, Switch, TRANSPARENT, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableRow, Tabs, TabsPanel as TabsContent, TabsList, TabsPanel, TabsTrigger, TextReveal, type TextRevealProps, Textarea, type TextareaProps, ThemeId, ThemeManifest, ThemeMode$1 as ThemeManifestMode, type ThemeMode, ThemeSelector, type ThemeSelectorProps, ThemeSwitcher, type ThemeSwitcherProps, ThinkingOrb, type ThinkingOrbProps, ToastAction, type ToastApi, ToastClose, ToastContent, ToastDescription, type ToastManagerApi, type ToastOptions, type ToastPosition, ToastProvider, type ToastProviderProps, ToastRoot, ToastTitle, type ToastType, ToastViewport, Toggle, ToggleGroup, type ToggleGroupAppearance, Toggle as ToggleGroupItem, Toolbar, ToolbarButton, type ToolbarButtonProps, ToolbarGroup, type ToolbarGroupProps, ToolbarInput, type ToolbarInputProps, ToolbarLink, type ToolbarLinkProps, type ToolbarProps, ToolbarSeparator, type ToolbarSeparatorProps, Tooltip, TooltipArrow, TooltipContent, TooltipPortal, TooltipProvider, TooltipTrigger, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, type UseCopyOptions, type UseCopyResult, type UseInViewOptions, type UsePanelWidthOptions, Video, type VideoProps, ViewTransitionName, type ViewTransitionNameProps, Workspace, WorkspaceBar, WorkspaceCanvas, accordionMeta, appHeaderMeta, appShellMeta, areaChartMeta, attachmentMeta, avatarGroupVariants, avatarMeta, avatarVariants, badgeMeta, badgeVariants, bannerMeta, bannerVariants, barChartMeta, bottomNavMeta, breadcrumbsMeta, browserMeta, bubbleMeta, buttonMeta, buttonVariants, calendarMeta, cardMeta, carouselMeta, chartColor, toneColor as chartToneColor, chatComposerMeta, checkboxCardMeta, checkboxMeta, cn, codeBlockMeta, codeMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, commandMenuMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, dataTableMeta, datePickerMeta, dateTileMeta, destructiveActionModalMeta, dialogMeta, emptyStateMeta, entityMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formMeta, formatFileSize, formatHex, glidingHighlightMeta, gradientArtMeta, hsvToRgb, hueColor, inlineStatusMeta, inputMeta, isTransparent, kbdMeta, labelMeta, lineChartMeta, markerMeta, marqueeMeta, menuMeta, messageMeta, messageScrollerMeta, meterIndicatorVariants, meterMeta, meterTrackVariants, middleTruncateMeta, noteMeta, noteVariants, notificationBadgeMeta, notificationCountVariants, notificationDotVariants, numberFieldMeta, otpFieldMeta, overflowMenuMeta, pageHeaderMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, phoneMeta, pieChartMeta, pillMeta, pillVariants, popoverMeta, progressMeta, progressTrackVariants, radarChartMeta, radialChartMeta, radioCardMeta, radioMeta, relativeTimeMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, searchInputMeta, segmentedBarMeta, segmentedBarSegmentVariants, segmentedBarVariants, selectMeta, separatorMeta, settingsMenuMeta, sheetMeta, sheetVariants, showMoreMeta, sideNavMeta, sidebarMeta, skeletonMeta, sliderMeta, sparklineMeta, spinnerMeta, startViewTransition, statusBarMeta, statusDotMeta, statusDotVariants, stepperConnectorVariants, stepperIndicatorVariants, stepperMeta, streamingTextMeta, supportsViewTransitions, switchMeta, tableMeta, tabsMeta, textareaMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, toggleMeta, toolbarGroupVariants, toolbarMeta, toolbarSeparatorVariants, toolbarVariants, tooltipMeta, treeIndent, treeMeta, useAnchoredDensity, useComboboxFilteredItems, useCommandMenuShortcut, useCopy, useDensity, useInView, useMotion, usePanelWidth, usePresentation, useReducedMotion, useScrollActivity, useSidebar, useToast, useToastManager, useTypeTier, videoMeta, workspaceMeta };
package/dist/index.js CHANGED
@@ -11347,6 +11347,6 @@ var carouselMeta = {
11347
11347
  };
11348
11348
 
11349
11349
  // src/index.ts
11350
- var SABLE_UI_VERSION = "0.6.0";
11350
+ var SABLE_UI_VERSION = "0.6.1";
11351
11351
 
11352
11352
  export { Banner, BreadcrumbItem, BreadcrumbLink, BreadcrumbPage, BreadcrumbSeparator, Breadcrumbs, COMPONENT_GROUPS, COMPONENT_TAXONOMY, Calendar, Carousel, CheckboxCard, CheckboxGroup, Collapsible, CollapsibleIndicator, CollapsiblePanel, CollapsibleTrigger, ColorField, Combobox, ComboboxChips, ComboboxClear, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxGroupLabel, ComboboxInput, ComboboxItem, ComboboxItemIndicator, ComboboxList, ComboboxPortal, ComboboxValue, ComboboxVirtualList, ContextCard, ContextCardArrow, ContextCardContent, ContextCardPortal, ContextCardTrigger, ContextMenu, ContextMenuContent, ContextMenuGroup, ContextMenuGroupLabel, ContextMenuItem, ContextMenuPortal, ContextMenuSeparator, ContextMenuTrigger, DatePicker, Feedback, Fieldset, FieldsetContent, FieldsetDescription, FieldsetFooter, FieldsetLegend, FileDropzone, GlidingHighlight, InlineStatus, LineChart, Marquee, MiddleTruncate, Note, NumberField, PageLoader, Pagination, Panel, PanelGroup, PanelResizer, PanelSection, Popover, PopoverArrow, PopoverClose, PopoverContent, PopoverDescription, PopoverPortal, PopoverTitle, PopoverTrigger, PropertyPair, PropertyRow, RadarChart, Radio, RadioCard, RadioGroup, SABLE_UI_VERSION, STATUS_TONES, STATUS_TONE_ICON, ScrubField, Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetPortal, SheetTitle, SheetTrigger, ShowMore, StatusBar, StatusBarGroup, StatusBarSeparator, TRANSPARENT, ThemeSelector, ThemeSwitcher, ThinkingOrb, ToastAction, ToastClose, ToastContent, ToastDescription, ToastProvider, ToastRoot, ToastTitle, ToastViewport, Tree, TreeAddRow, TreeEmpty, TreeRow, TreeSection, Video, Workspace, WorkspaceBar, WorkspaceCanvas, bannerMeta, bannerVariants, breadcrumbsMeta, calendarMeta, carouselMeta, checkboxCardMeta, collapsibleMeta, colorFieldMeta, comboboxMeta, componentGuidance, componentLabel, componentSlug, componentsInGroup, contextCardMeta, contextMenuMeta, datePickerMeta, feedbackMeta, fieldsetMeta, fileDropzoneMeta, fileDropzoneVariants, formatFileSize, formatHex, glidingHighlightMeta, hsvToRgb, hueColor, inlineStatusMeta, isTransparent, lineChartMeta, marqueeMeta, middleTruncateMeta, noteMeta, noteVariants, numberFieldMeta, pageLoaderMeta, paginationMeta, panelGroupMeta, panelSectionMeta, parseHex, pillMeta, popoverMeta, radarChartMeta, radioCardMeta, radioMeta, rgbToHsv, scrollerMeta, scrubFieldMeta, sheetMeta, sheetVariants, showMoreMeta, statusBarMeta, themeSelectorMeta, themeSwitcherMeta, thinkingOrbMeta, toast, toastMeta, treeIndent, treeMeta, useComboboxFilteredItems, usePanelWidth, useToast, useToastManager, videoMeta, workspaceMeta };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sableui/react",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Sable: themeable React components, layout patterns, and tools for coding agents.",
5
5
  "type": "module",
6
6
  "sideEffects": [
package/registry.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://sable.design/registry.schema.json",
3
3
  "name": "@sableui/react",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "description": "Themeable React components, semantic tokens, layout patterns, presets, and package-owned agent skills. Default theme: Sable.",
6
6
  "engine": {
7
7
  "behavior": "@base-ui/react",
@@ -1564,6 +1564,10 @@
1564
1564
  {
1565
1565
  "id": "label-contrast",
1566
1566
  "fails-on": "A label on a brand fill (`--primary`, `--sidebar-primary`, `--highlight`, each with its `-foreground`) should clear 4.5:1 AND APCA Lc 60 — a pair can pass the ratio and still read muddy (dark ink on a bright red-orange, Lc 42). The finding names the nearest pair that reads: the ink the colour takes as picked, with the fill's lightness moved to meet it, hue kept. If the pair is your brand's deliberate choice, keep it on the record: `/* design-lint-disable-next-line label-contrast -- <reason> */` above the declaration."
1567
+ },
1568
+ {
1569
+ "id": "status-text-drift",
1570
+ "fails-on": "A status colour has two tokens: the fill (`--destructive`, `--success`, `--tone-warning`, `--tone-info`), which `bg-*` paints, and its `-text` (`--destructive-text` …), which `text-*`, `border-*`, `ring-*`, `outline-*`, `fill-*` and `stroke-*` read. Setting the fill alone leaves the text on the theme's colour, so a retuned red button sits beside the theme's red error message. Set the `-text` in the same theme and mode: the fill itself where it already reads on your surfaces, or a step of it moved away from them (lighter in dark mode) so it clears 4.5:1 on a card."
1567
1571
  }
1568
1572
  ],
1569
1573
  "escapeHatch": {
package/scripts/grade.mjs CHANGED
@@ -34,7 +34,7 @@ import { createHash } from "node:crypto"
34
34
  import { dirname, extname, join, relative, resolve } from "node:path"
35
35
  import { fileURLToPath } from "node:url"
36
36
  import { ignoreGlob, lintText, makeRules } from "./lint-rules.mjs"
37
- import { labelContrastScanner } from "./label-contrast.mjs"
37
+ import { labelContrastScanner, statusTextDrift } from "./label-contrast.mjs"
38
38
  import { skeletonize } from "./skeleton.mjs"
39
39
  import { assessReview, reviewTemplate } from "./review-evidence.mjs"
40
40
 
@@ -109,7 +109,7 @@ const motionTokens = new Set(
109
109
  REGISTRY.tokens?.motion?.scale,
110
110
  ].filter(Boolean),
111
111
  )
112
- const RULES = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
112
+ const RULES = makeRules({ semanticTokens, motionTokens, labelContrast: await labelContrastScanner(PKG_ROOT, REGISTRY.themes?.default), statusText: (css) => statusTextDrift(css, REGISTRY.themes?.default) }).filter((r) => r.scope !== "package")
113
113
  const PROP_SURFACE = REGISTRY.propSurface?.items ?? {}
114
114
  const ICONS = new Set(REGISTRY.iconContract?.names ?? [])
115
115
  /**
@@ -136,3 +136,39 @@ export async function labelContrastScanner(pkgRoot, defaultTheme = "sable") {
136
136
  return out
137
137
  }
138
138
  }
139
+
140
+ /** Status fills and the token a status colour drawn on a surface reads instead. */
141
+ const STATUS_TEXT = { destructive: "destructive-text", success: "success-text", "tone-warning": "tone-warning-text", "tone-info": "tone-info-text" }
142
+
143
+ /**
144
+ * The `status-text-drift` rule's reader. Since 0.6, `text-destructive`,
145
+ * `border-destructive`, `ring-destructive` and the rest read `--destructive-text`;
146
+ * only `bg-destructive` reads the fill. An app that retunes `--destructive` and
147
+ * stops there gets a button in its red and error text, borders and focus rings in
148
+ * the theme's — and nothing on screen says which is which. CSS cannot notice "the
149
+ * fill moved and the text did not", so the stylesheet is read for it: a status
150
+ * fill set, in a theme and mode, with no `-text` set beside it there.
151
+ *
152
+ * Pure: it needs the declarations only, not the built package, so it reports
153
+ * even where `label-contrast` cannot measure.
154
+ */
155
+ export function statusTextDrift(css, defaultTheme = "sable") {
156
+ const decls = customProperties(stripComments(css))
157
+ const fills = decls.filter((d) => d.name in STATUS_TEXT)
158
+ if (!fills.length) return []
159
+ const themeOf = (d) => d.context.match(THEME)?.[1] ?? defaultTheme
160
+ const out = []
161
+ for (const d of fills) {
162
+ const text = STATUS_TEXT[d.name]
163
+ const missing = ["light", "dark"].filter(
164
+ (mode) => applies(d, mode) && !decls.some((t) => t.name === text && themeOf(t) === themeOf(d) && applies(t, mode)),
165
+ )
166
+ if (!missing.length) continue
167
+ const modes = missing.length === 2 ? "light and dark" : missing[0]
168
+ out.push({
169
+ index: d.index,
170
+ match: `--${d.name} is set without --${text} (${modes}): text-${d.name}, borders and rings keep the theme's colour → set --${text} too`,
171
+ })
172
+ }
173
+ return out
174
+ }
@@ -118,12 +118,12 @@ function classTokens(expr) {
118
118
  /**
119
119
  * Build the rule table against a token contract.
120
120
  *
121
- * @param {{ semanticTokens: Set<string>, motionTokens: Set<string>, labelContrast?: ((css: string) => { index: number, match: string }[]) | null }} contract
122
- * `labelContrast` is the stylesheet scanner from label-contrast.mjs. It is
123
- * passed in so this table stays dependency free; without it the rule is
124
- * listed and reports nothing.
121
+ * @param {{ semanticTokens: Set<string>, motionTokens: Set<string>, labelContrast?: ((css: string) => { index: number, match: string }[]) | null, statusText?: ((css: string) => { index: number, match: string }[]) | null }} contract
122
+ * `labelContrast` and `statusText` are the stylesheet scanners from
123
+ * label-contrast.mjs. They are passed in so this table stays dependency free;
124
+ * without one its rule is listed and reports nothing.
125
125
  */
126
- export function makeRules({ semanticTokens, motionTokens, labelContrast = null }) {
126
+ export function makeRules({ semanticTokens, motionTokens, labelContrast = null, statusText = null }) {
127
127
  // Easing utility names = the theme's motion-ease-* tokens + Tailwind built-ins.
128
128
  const easeNames = new Set([
129
129
  ...[...motionTokens]
@@ -546,6 +546,22 @@ export function makeRules({ semanticTokens, motionTokens, labelContrast = null }
546
546
  return ctx?.isStylesheet && labelContrast ? labelContrast(text) : []
547
547
  },
548
548
  },
549
+ {
550
+ id: "status-text-drift",
551
+ /* CONSUMER-scoped. 0.6 split each status colour into a fill and a
552
+ * `-text`; an app that retunes only the fill paints its buttons in its
553
+ * own red and its error text, borders and rings in the theme's. Nothing
554
+ * on screen says which is which, so the stylesheet is read for it. */
555
+ scope: "consumer",
556
+ since: "0.6.1",
557
+ file: true,
558
+ code: true,
559
+ why:
560
+ "A status colour has two tokens: the fill (`--destructive`, `--success`, `--tone-warning`, `--tone-info`), which `bg-*` paints, and its `-text` (`--destructive-text` …), which `text-*`, `border-*`, `ring-*`, `outline-*`, `fill-*` and `stroke-*` read. Setting the fill alone leaves the text on the theme's colour, so a retuned red button sits beside the theme's red error message. Set the `-text` in the same theme and mode: the fill itself where it already reads on your surfaces, or a step of it moved away from them (lighter in dark mode) so it clears 4.5:1 on a card.",
561
+ scan(text, ctx) {
562
+ return ctx?.isStylesheet && statusText ? statusText(text) : []
563
+ },
564
+ },
549
565
  {
550
566
  id: "hand-derived-surface",
551
567
  /* CONSUMER-scoped. Mixing ink into a surface by hand is what the