Repository navigation
Conversation
…wn on (qorus#646) David's review found black text on a dark gray background: the muted intent (#f2f2f230) picked its text from the near-white it is made of, but is drawn at 19% over a dark page. Text and icon colors are now picked against what is actually drawn, to WCAG AA: 4.5:1 for text, 3:1 for icons and large text. - getReadableColorFrom / getReadableColor choose the candidate with the higher contrast (not a luminance threshold), draw a translucent background over what is under it (the theme it was made from), and give up the dimmed #f2f2f2 / #0d0d0d for pure white / black where the dimmed one is under 4.5:1 (#f2f2f2 on the info blue is 4.2:1). - New: getDrawnColor, getContrastRatio (unrounded: polished rounds 4.495 up to 4.5), getReadableColorOn (one color for every color of a gradient), getReadableAlpha / getReadableFadedColor (fade only as far as it reads), getReadableAccentColor(On) (keep the hue, move toward the side that can read on every background), getReadableIntentColor, getReadableThemeColor, getReadableIconLook, getReadableTextOn, getReadableGroupOpacity (how far a group drawn as a whole can fade and every mark in it still read), getThemeSurface(s), fadeColor (a translucent color at a panel's opacity stays translucent; rgba() replaced its alpha). - A one-color gradient (buildGradientColorsFromString) keeps its text readable at both ends, and a step lighter as it is drawn hovered or pressed: info's light end was 3.25:1 with white. - ReqoreSurfaceProvider / useReqoreSurface (exported, with TReqoreSurface): what a component paints - one color, or each color of its gradient - is handed to what it holds, which reads against it rather than the page. An app can tell Reqore about a background of its own the same way. Tests that fail before: __tests__/helpers/readableColors.test.tsx (the helpers missing, black picked for the muted intent, #f2f2f2 kept on the info blue, info's gradient 3.86:1 a step lighter) and __tests__/surfaceContext.test.tsx.
…orus#646) Each component picks its text and icon colors against the background it actually draws (a minimal wash, a translucent intent, a gradient, a lightened header), hands that background to what it holds, and fades secondary text and icons only as far as they stay readable (4.5:1 text, 3:1 icons and large text; WCAG 1.4.3 / 1.4.11). - Button: text, description and icons against its drawn fill at rest, hovered and active; a minimal or transparent button keeps its color in a readable shade; badges read on the button, every color of it. - Tag: solid, minimal, soft and text tags, against every color they are drawn on; the key is darkened as far as its text needs (over a light color 30% black left it in the mid-tones, where nothing reads); icons and actions; label and key effects read on the tag. - Input / Textarea / RichTextEditor: text, placeholder, icons and the clear button against the field's own wash. - Span / Paragraph / Heading / Icon: an intent, or a theme color named by name, in a shade of it that reads where it is drawn; text drawn in a gradient is drawn in colors that read, hovered and pressed too; a text glow's letters sit in a halo of the glow and are drawn in the color that reads on it (white letters in a white halo inside a red glow were 3.85:1). - An effect with several gradients is read as the layers they are drawn as, each over the ones under it. - Panel (content, the title bar over the content's gradient, description, title icon, a title rendered as a link), Message, notifications, Callout, EntityRow, Statistic, Testimonial, FeatureCard, Accordion, Timeline, Table cells, NavRail, Breadcrumbs (the separator too), SegmentedControl, CollapsibleContent's reveal button, Rating, Tree, StackedBar, DescriptionList, Bubble timestamps: each reads against its own surface; one with a gradient hands every color of it to what it holds. A ControlGroup or a Menu drawn in a gradient does too, and a menu divider's label reads on the menu. - Checkbox: the box against every color under it; a switch's word on the knob reads on the knob and the other on the track (one color for both was white on the light info knob, 2.56:1), a gradient track and a knob in the effect's colors are read as drawn, and an unset knob is drawn faint without fading its icon, which still stands out by 3:1. - Notifications: the text, icon, close button and pending spinner read on the notification as drawn over every color under it; content of the caller's own (a tag) is no longer faded with the text under a title; an iconColor is drawn in a shade of it that stands out from its tile. - NavRail: an idle rail rests at idleOpacity, or as little less faded as its icons need to stand out by 3:1 - the marks' and the active mark's on its fill, faded into the page with the rail (it rested at 0.34: 2.3:1). - A translucent theme drawn at a panel's opacity stays translucent (the muted EmptyState was white on white). - Breadcrumbs no longer force the bar's text color onto a crumb with its own intent (dark text on the warning crumb). - StackedBar writes each segment in what reads on it (white on a light segment was 1.9:1). - Disabled components that are not form controls say so (aria-disabled): Tag, Callout, Panel, Accordion item, EntityRow, Statistic, CollapsibleContent, the Checkbox row, Progress, SeverityRow, StackedBar, Testimonial, the Textarea wrapper, a disabled row's table cells. WCAG exempts disabled controls from contrast; assistive technology is told. Tests that fail before: __tests__/componentSurfaces.test.tsx (a group or a menu drawn in a gradient hands its colors on), __tests__/tagColors.test.tsx (a tag's key darkened as far as it takes, a tag reads on every color it is drawn on). The story run's contrast gate (the last commit) failed 524 of 1186 story tests before these and the story colors, and none after.
…us#646) Some stories put text or icons on colors no text color reads on: a cyan-to-pink badge gradient, a purple-to-teal item, purple-to-pink groups and menus, a pink message, an orange badge under a darkened key, a light green backdrop blob, a magenta sphere, dark icon colors on a dark panel, a red spinner and a red bold leaf on dark gray, a muted wash on a mid-blue picker. Each keeps the same kind of color in a shade the text reads on, so the demos show readable choices. The notification stories' busy backdrop tells the notifications on it what it is drawn in (ReqoreSurfaceProvider), as an app would for a background of its own, and its row labels are a shade less faint.
…orus#646) After each story settles (its transitions and finite animations finished), in its theme: - text by axe-core's color-contrast rule (4.5:1, 3:1 when large); - text on a gradient, which axe cannot decide, against the gradient's colors where the text is drawn on it, and text drawn in a gradient (background-clip: text) in each of its colors; - icons (an svg in .reqore-icon) at 3:1 against what they are drawn on (WCAG 1.4.11), which axe does not check. What a mark is drawn on is composed as the browser draws it: each ancestor's paint over what is under it (a translucent color blended, a gradient as its colors along the part the mark covers, a fill over a ring counting alone), an element at an opacity drawn as a whole - its paint and everything in it - then faded into what is under it, and a floating layer (a popover, a modal) over the app layout's surface. Disabled content is exempt (WCAG), as is anything not drawn and decoration the page marks as such (data-decorative). Fail-first: the first gated run failed 524 of 1186 story tests in 62 of 75 story files (Button 22/25, Tag 18/22, Table 49/71, Panel 32/59, ...); with the previous commits every story passes. Unit tests: __tests__/contrastGate.test.ts (the gradient position, faded group and settling cases fail on the gate before them). axe-core 4.10.2 is a dev dependency. src/components/COMPONENTS.md has a section on how Reqore picks text and icon colors now: against what is drawn, the surface handed down (ReqoreSurfaceProvider), the floors on fades, what stays the caller's.
…city the button draws it at (qorus#646)
A button draws its icons at CONTROL_ICON_OPACITY and picks an icon's own
color (leftIconColor, iconColor) in the shade and at the opacity that
stand out by 3:1. A color given in the icon's own props instead -
leftIconProps={{ intent: 'warning' }}, as the Qorus IDE's readiness rows
give it - was spread over that look: the icon made it readable at full
opacity and the button drew it at 70%, a warning icon at 2.4:1 (814
findings in the IDE's contrast gate).
The button now takes the intent or color in leftIconProps /
rightIconProps as the icon's own color (they still win over leftIconColor,
as they did when spread last) and picks it as it does leftIconColor; the
rest of the props are passed on as before.
Test (fails before): an icon colored through its props is drawn exactly
like one colored through leftIconColor, left and right; that look stands
out by 3:1 at rest and hovered, on the dark theme and a light one; the
other icon props (its size) still reach it.
…ld holds (qorus#646) The contrast work (85f6099..98c46c3) computes each faded description, label and icon opacity in the render and hands it to a memo component (ReqoreIcon, ReqoreP, ReqoreSpan, ReqoreButton) as a new object every time, and DeepScan reported 21 REACT_INEFFICIENT_PURE_COMPONENT_PROP and one INSUFFICIENT_NULL_CHECK as new on #708 (FAILURE at 98c46c3, 6e203e6 and 5960623). Each such effect, style and handler is memoized on what it is made of: Button, Input and Tag's icon effects; Checkbox's knob and box icons; Callout, EntityRow and FeatureCard's descriptions (and EntityRow's metadata, Callout's close handler); RichTextEditor's placeholder and paragraph styles; SegmentedControl's item refs (one callback per item); Statistic's label; Timeline's timestamp and content styles. Panel reads the description effect's color and opacity null-safely, as it reads its textSize below.
…ks for it (qorus#646) A user who asks for reduced motion (WCAG 2.3.3) still got every spring Reqore runs - an input's clear button fading in, a drawer sliding, a reveal - and every CSS transition. A contrast check measured them on their way: the clear button, which rests at 4.8:1, at 1.4-2.6:1 in the IDE's gate (a react-spring fade, which no gate can wait on). - ReqoreUIProvider turns react-spring's skipAnimation on while the user prefers reduced motion (set as it renders, so the first render's springs settle too, and again when the preference changes; it turns off only what it turned on): every spring settles at once, at its end. - Its global style takes every CSS transition inside the layout and the portal to 0.01ms under the preference (not none: a transition of no time sends no transitionend, which some components wait for). Keyframe animations (a spinner, a pulse) are as they were. - The story run's contrast gate asks for reduced motion while it measures (a browser command, emulateReducedMotion), so it measures what stays on screen, never a frame of a fade. A story tagged `reduced-motion` runs under it; the others run with the motion a user sees, as their checks of a marquee, a count-up or a sheet expect. Tests (fail before): under reduced motion an input's clear button is at its settled opacity on first paint and Reqore's springs skip; the story Form/Input "Transitions Under Reduced Motion" finds the clear button's transition at 0.01ms (0.1s without the preference).
…put, a tag or a panel draws it faded (qorus#646) As with the button (5960623): an input's icons, a tag's and a panel's title icon are drawn at CONTROL_ICON_OPACITY, and the component picks an icon's own color and that opacity together so that it stands out by 3:1. A color given in the icon's props - leftIconProps={{ intent }}, iconProps={{ color }} - was spread over the pick: the icon made it readable at full opacity, and drawn at 70% it fell short (a collection's filter icon given leftIconProps={{ intent: 'muted' }} at 2.2:1, the IDE's gate). Each now takes it as the icon's own color (it still wins over the component's own, as it did when spread last) and passes the rest on. The split is one helper, splitIconColor (helpers/iconProps), the button uses it too. Test (fails before): an input's left and right icon, a tag's and a panel's title icon colored through their props are drawn exactly like ones colored through the component's own prop.
…#646) A link with an intent was written in the shade of it that reads on the theme's page, wherever it was: on a table cell tinted with the warning intent 3.5:1, over an entity row's gradient 2.8:1 (the IDE's gate). It now reads the surface a component around it hands down, as a span does. Test (fails before): an info link reads 4.5:1 on a warning-tinted cell and on every color of a gradient, in a shade of blue.
…646) An active button (a selected menu item, a sidebar section on the active path) is filled with its color, but its description, its badges and its icons were picked for the button at rest - its color's wash over the page: a light blue description 2.0:1 and badge 2.7:1 on the info blue of the IDE's account menu, a grey description 4.4:1 on a sidebar's section toggle. The description is now picked on the active fill, the button hands that fill to what it holds, and its icons are picked on it. The nav rail reads its active mark as the button now draws it (its icon on the fill, in the rail's activeEffect), so it still rests only as faded as that icon stands out by 3:1. Tests (fail before): an active button with a color writes its description and draws its badge on its fill; one without a color writes its description on its fill; at rest the description reads as before. The NavRail idle stories fail with the old rail and this button.
…at they hold (qorus#646) An entity row faded its description and metadata as a whole, so what they hold with a color of its own was faded a second time: a minimal warning tag in the metadata at 4.0:1, a link in the description at 2.8:1 (the IDE's gate). Their text is now written in the faded color (the readable floor as before; an opacity a caller gives is still that floor, a color a caller gives is the caller's), and a tag or a link in them is drawn as it is. Test (fails before): both slots are drawn whole in a faded color, a tag and a link in them are not faded, and a caller's opacity is kept.
…through it (qorus#646) A gradient effect paints its fill over a lighter ring of the same gradient (border-box). With an opaque fill the ring is hidden; with a translucent one - a glow of 'info:lighten:1:0.5' over the page - it shows through, but the colors a component read its text against left it out: a statistic's label faded for the blue over the page read 3.6:1 on what was painted (the IDE's support hero). The effect's own readable colors and getEffectBackgrounds now draw the fill over the ring (one fill layer stop by stop, as they share the gradient's geometry). Test (fails before): a statistic's label stands out by 4.5:1 from the corner of a translucent radial glow as it is painted, ring included.
…rt hex as CSS reads it, and results are cached (qorus#646) Review of the contrast pass found these in the color helpers: - A color the helpers cannot parse (a CSS variable, `currentColor`, a name, a typo, `#f00a` before this fix) threw. It is now returned as it is given: `getContrastRatio` is NaN for it, a surface it names is left out, and a wash of it is mixed by the browser (`getColorAtAlpha` writes `color-mix(...)` for it). `isReadableColor` tells whether the helpers can read a color. - A short hex was repeated instead of doubled (`#fdc` read as `#fdcfdc`). polished writes colors short where it can (`#bbaaff` as `#baf`), so a minimal button's text came out green instead of the lavender it was picked as. Every helper now reads and writes the long form. - `getContrastRatio` is unrounded (polished rounds luminance to three places: `#595959` on black was 3.0, it is 2.998). - An explicit hex icon color is drawn as given, as a standalone ReqoreIcon draws it and as COMPONENTS.md says; only a theme color (an intent, `main`, a shade of one) is moved to a readable shade (`getReadableIconLook` -> `getReadableThemeColor`). - A caller's own `ifLight` / `ifDark` pair stays theirs when neither reaches 4.5:1: the better of the two is returned. - Each pure helper keeps its last 2000 results (`createColorCache`): every Button, Tag, Effect and Input re-render ran the color maths again. - `ReqoreSurfaceProvider` keeps its value while its colors are the same, so an inline effect does not re-render everything below it. Tests: helpers/colorParsing, helpers/explicitColors, surfaceProviderIdentity. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…, colors are worked out once per render, and reduced motion stays inside Reqore (qorus#646)
The rest of the review of the contrast pass:
- Crashes: `descriptionEffect.color: 'red'`, a CSS variable or an intent
named where a hex was expected no longer throw (entity row, callout,
feature card, panel, statistic).
- Explicit values are the caller's: a nav rail's `idleOpacity`, an
explicit `descriptionEffect` / `labelEffect` / `metadataEffect`
`opacity`, a hex icon color on a button, a tag or an input. The
floors apply to the defaults only.
- Own fills and surfaces:
- `effect.color` on a button or a tag is read on its own fill, at
rest and hovered; a soft or text tag reads on every stop of a
gradient it sits on;
- a transparent or minimal element with its own `customTheme` reads
on that color; a callout's own text, a transparent statistic or
testimonial, and an entity row's actions read on their own surface;
- a popover's content and a drawer read on their own fill, not on
whatever opened them;
- a nav rail's active group hands its gradient (or tint) to its marks:
a resting section mark picked its text for the rail around the
group, and only read on the Effects story because `#baf` was misread
as a light green;
- `theme.breadcrumbs.item.color` reaches the crumbs; description list,
rich-text placeholder, stacked bar, timeline, message, tree and the
segmented control's More button read on what they are drawn on.
- `aria-disabled`: a component that is not disabled keeps the one its
caller gives.
- Render cost: Button and Tag work their colors out once per render
(`getButtonPaint`, `getTagPaint`) instead of in each style rule, Tag
is a `memo` component, Effect remembers its gradient pass, Span, P and
Link memoise their color, Input and Textarea use `getInputLook`. A
hovered button's text color declaration lost its semicolon and was
dropped with button animations on.
- Reduced motion reaches Reqore's own springs and `reqore-` elements
only; the app's own react-spring animations and transitions are left
alone (no `Globals.assign`).
COMPONENTS.md describes each. Tests: ariaDisabledPassThrough,
breadcrumbsColors, colorTolerance, controlOwnColors,
descriptionColorTolerance, explicitOpacityAndAria, fieldColors,
messageColors, navRailActiveGroupSurface, notificationColors,
portalSurfaces, reducedMotionSprings, stackedBarColors, timelineColors,
unpaintedSurfaceText, and updates to DescriptionList, reducedMotion and
segmentedControl.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ere they are drawn (qorus#646) A hex icon color and an explicit opacity are now drawn as given, so the stories that passed values which only read after Reqore changed them fail the contrast gate. Each keeps the same kind of value in one that reads: - Button: the minimal button's left and right icons in a light purple and blue (they were near-black on the dark washes); the described button's icon in `success:lighten:2`, a theme color, since one hex cannot read on both the white and the dark fills the template is drawn in. - Tag and Input: a lighter pink and purple. - EntityRow and Testimonial With Effects: metadata and role at 0.7 opacity (0.6 read at 4.18:1 and 4.13:1). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…w holds (qorus#646) DeepScan's REACT_INEFFICIENT_PURE_COMPONENT_PROP, 13 new on 473ada3: - ReqoreTag is a memo component now, so every new object it is handed is flagged: DataView's bold and type-label effects are module constants, and the dropdown's trail of levels takes its click handlers from a `useCallback` and a `useMemo`'d list instead of new arrows. - The segmented control's More button had the same three issues before (its props, its menu and the menu items' click handlers); its tap and surface changes moved their lines, so DeepScan counted them as new. They are memoised now. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Qorus IDE draws a refusal as a muted ReqoreMessage (default fill, an icon and a title). On reqore 0.78.5 its text was near-black on the translucent muted grey over a dark page: the text was picked against the opaque muted color the fill is made from, which is near-white. This PR's contrast work picks it against the fill as drawn, which already fixes it. Story Other/Message "Muted Refusal On The Page" draws the IDE's exact message and asserts that every text in it reads (axe color-contrast against the composited fill). It fails on 0.78.5 (#0d0d0d on #575757, 2.68:1, title and text) and passes here.
…(qorus#646) Frosted text fades toward its foot, and Reqore raises the default opacity as far as the foot needs to stand out from what it is drawn on. It raised an opacity the caller gave the same way, against COMPONENTS.md: "what a caller sets is the caller's: an explicit hex color or effect.opacity is drawn as given ... the floors apply to the defaults only". It could even go past 1 (an opacity divided by the foot's 0.52). The frost's opacity is now `getFrostOpacity`: the caller's as given, or the default 0.72 raised as far as the foot reads. The foot's part is the named `FROST_FOOT`, used in both places that drew it. Tests: a frost's given opacity is drawn as given (fails before: raised); by default its foot reads by 4.5:1.
…for all of them (qorus#646) Five components paint a card - an entity row, a callout, a testimonial, a statistic, a feature card - and each worked out on its own what it was drawn on: the surface around it, its fill over that, its gradient, the text that reads there. The copies had drifted (the /audit of #717): - an entity row's and a statistic's own text was picked against their fill, never their gradient, and their root never got the gradient for an `effect.color` either (a testimonial's, a feature card's and a callout's root neither), so on a light gradient a dark page's colors were kept; - a callout's content took its `effect.color` against the page, not the callout (it was handed its fill under a name the effect does not read); - a feature card picked its text and its description's fade against its flat fill, and handed only its footer the card's surface: its label and description were read on what was around it; - a statistic with an intent rings its gradient in the intent's color, and a ringed gradient is drawn without its own surface color, but it read its text on that surface color all the same. `useReqoreDrawnSurface` now works it out for all five: the surface, the fill over it, each color of the gradient the card paints (a statistic's as it paints it), and the text that reads on all of them. Each card's root draws that text and reads an `effect.color` on those colors; a feature card hands its whole body the card's surface. The surface helpers move from the Panel folder to `src/helpers/surface.ts`: six components use them. Tests (cardsReadOnWhatTheyPaint): on a light gradient over the dark page, an entity row's own effect.color, a callout content's effect.color and a feature card's text and handed-down surface read on the gradient (each fails before); an entity row's, a statistic's and a testimonial's text, and a statistic with an intent, likewise.
…d leaves decoration alone on both paths (qorus#646) Two gaps in the story run's contrast check, from the /audit of #717: - What axe-core could not decide was measured only when it sat on a gradient; everything else it left undecided was dropped without a word (a single character, which axe cannot tell from an icon font; text beside pseudo-content; a text shadow), though the check said only text on an image is left alone. It is now measured against what it is drawn on, as text on a gradient is. Left alone, by name: text on an image (IMAGE_KEYS) and text something else is drawn over or that is off screen, where what is under it is not what its ancestors paint (POSITION_KEYS). - `data-decorative` was honoured only on the gradient path: decoration on a solid fill that axe found too faint still failed the story. Every path now leaves exempt text alone (`isExempt`, was `isDisabled`, a name that hid the decoration rule). The helpers only the check itself uses are no longer exported. Stories (Other/Contrast Check): Decoration Is Left Alone and Undecided Text Is Measured both fail before.
… how it came to be (qorus#646) Message › Muted Refusal On The Page now says what it renders and shows, in the "Renders X…" form, without naming internals or history (the /audit of #717). The contrast part of 8e93b4e on #717; the Collection story's description is on the functional branch with the story it belongs to.
… portal too (qorus#646) An app can open Reqore's drawers, modals and popovers in a portal of its own (`customPortalId`). Reduced motion stilled Reqore's elements in the layout and in Reqore's own portal only, so there they kept their transitions. The provider now names the app's portal in the rule as well (`getReducedMotionSelectors(customPortalId)`). Test: a drawer in the app's portal is stilled where the provider is told the portal, and Reqore's own portal alone does not reach it.
…nds out (qorus#646) A rating's `filledColor` or `emptyColor` given as a theme's color (`'warning'`, `'info:lighten:2'`) was turned into its hex first, and a hex is drawn as given, so the star kept the color however little it showed on what the rating was drawn on, while an icon given the same color is moved to a shade that stands out by 3:1. It is now that shade; a hex is still drawn as given. The surfaces it reads are worked out once per surface (they were built on every render and keyed on a joined string), and the hook is no longer called inside an argument. Tests: a star in the theme's warning stands out by 3:1 on a brown close to it (fails before: 1.6:1); a hex is drawn as given.
…(qorus#646) Each of these was written out twice, the copies able to drift apart with nothing to notice; each now has one owner, and nothing changes in what is drawn: - the search for the least readable alpha (colors.ts, in getReadableAlpha and getReadableGroupOpacity): `leastReadingAlpha`; - the ring a gradient effect draws under its fill (Effect, in getEffectGradientBackgrounds and getEffectRingStops): the latter; - a panel's fill and its bar's fill, in the styled rules that paint them and the memos that hand them down as surfaces: `getPanelFill`, `getPanelBarFill`; - the color of a card's own mark (an entity row's and a callout's icon, a testimonial's avatar): `getCardMarkColor` in helpers/surface; - a field's look (Input's and Textarea's `lookOf`): Input's `lookOfField`; the clear button's opacity (`getInputClearButtonOpacity` repeated `getInputLook`): defined by it; - a notification's softer content color: `getReadableFadedColor`; - whether a checkbox's knob paints nothing (twice in Checkbox). Dead code removed: `getReadableOpacity` (colors.ts) and `getInputPlaceholderColor` (Input), which nothing called. Effect's `getEffectRingStops`, `drawEffectLayers` and `drawGradientLayers` are no longer exported: only Effect uses them. Three comments sit with the code they describe again (colors.ts, Input, NavRail). Checkbox no longer calls a hook inside an argument and keys its colors on a joined string. The reduced-motion story command is typed with vitest's own `BrowserCommand`. COMPONENTS.md labels the two sections this PR adds 0.78.7, the version it ships in.
… (qorus#646) From the /audit of #717, each checked against the code first: Stories - Input › Transitions Under Reduced Motion passed whether or not reduced motion was asked for; it now checks it is, then that the transition takes no time. Its description no longer describes the test run. - NavRail › Idle Reveal / Idle Resting, Icon › Basic, Notification › what goes inside: described in the "Renders X…" form, not as test steps. - DatePicker › With Effect is on the theme's info again (its darker shades), not hex. The notification backdrop is drawn from the colors its surfaces are read from, with its stripes as the plain wash they are read as (no blend mode the model did not follow). Tests - aria-disabled: the disabled branch of the five components is tested (disabled says so, whatever the caller says; neither says nothing). - A crumb with an intent is checked to read on its own fill, not just to differ from the theme's item color; a menu's handed-down surface is checked against what it paints. A commit hash left a test name. - Shared, instead of copied: the reduced-motion mock (and the two cases both files tested), the surface probe (five files), the tests' independent color maths, the icon look reader. The contrast part of 5732f35 on #717; its functional part (the row-menu and folded-trail stories, the control group tests' shared layout model) is on the functional branch.
…r is drawn on (qorus#646) The accordion read its header's text against one color: the header's fill over what is around the accordion. When the accordion's effect paints a gradient, a minimal or transparent header is drawn on that gradient, and its text was picked for the page instead. What a header holds (its icons, its title, its badge) was read on the page too, not on the header. Now the accordion hands its items each color of its gradient as what they are drawn on. A header's text reads on every color it is drawn on, and the header hands those colors to what it holds. An icon of the item's own color is shaded by the icon itself, against the header. The body's fill is one helper, used both by its styled rule and by what it hands its content. Tests: Accordion.test, two cases, both failing before: - a minimal header's text reads on the gradient; - what a header holds is drawn on the header.
…r as its intent's shade reads (qorus#646) An uppercase label is drawn fainter than the content, as far as it still reads. How far was worked out for the list's text color, even when the label is written in its intent's shade (labelIntent). Danger's shade on the dark theme stands out less than white, so faded as far as white can be, it no longer read (2.4:1). Now the fade is worked out for the color the label is written in. A label in the content's case is not faded, and nothing is worked out for it. The label is no longer a memo component: what it holds is new on every render of the list. Tests: DescriptionList.test. - An uppercase danger label reads at 4.5:1 or better on what the list hands its rows. Fails before (2.4:1). - A label in the content's case is drawn at full strength. Test helper: colorMath.fromHex.
…(qorus#646) The rail read what its marks are drawn on as its fill over what is under it, even when its own effect paints a gradient. On the dark theme a light gradient gave the marks light text on light (1.15:1). Now the rail's surfaces are each color its effect paints: its fill when the effect paints no gradient, as before. Its marks, the active group's tint over it, the idle opacity's measure and what the rail hands its items are all read on those colors. The rail's fill color is one helper, used by its styled rule, by its surfaces and by the fade at its scrolling edges, which each wrote it out before. Test: navRailActiveGroupSurface.test. A rail with a light gradient of its own gives its resting marks text that reads at 4.5:1 on every color of the gradient. Fails before (1.15:1).
…opacity (qorus#646) At full opacity, a panel wrote its own text (what it holds that sets no color of its own) for its theme's main. The panel does not paint that color: it paints it a step darker. On a mid grey the step alone decides which text reads. A panel in #808080 paints #737373, and dark text picked for #808080 read at only 4.1:1 there, where white reads. Now its text is picked for what its content is drawn on, at any opacity: its fill as painted, or each color of its content's gradient. Below full opacity it already was. Tests: secondaryMarksReadWhereDrawn. - A panel's own text reads on the fill it paints. Fails before (4.1:1). - A bubble's timestamp is faded only as far as it reads where the bubble is drawn (inside such a panel it failed with the panel). - A timeline marker's icon in its item's intent stands out from the marker by 3:1.
…e props for memo children (qorus#646) From the #717 audit's leads, each checked against the code first. What a component paints is written once, and read from the same place: - a fill its styled rule paints and its surface memo reads: the menu (getMenuFill), the segmented control's track, the switch's track, the input's and the text area's wash (getInputFill), the notification's fill (getNotificationFill); - the newer notification's frosted surface, bloom and icon tile alphas are named constants, used by its rules and its palette alike; - the placeholder's least fade is INPUT_PLACEHOLDER_ALPHA, shared by the input and the rich text editor; - FeatureCard's large-text size is LARGE_BOLD_TEXT_PX (WCAG's 14pt bold), and its marker scale is a named constant. Memo children are handed the same props for as long as they hold: - a segmented control's items get one click handler and one ref callback each, which select through the latest onChange. An item that is gone takes them along: the map was never pruned. The selected item's theme is memoized; - a testimonial's author row gets memoized label and description effects; - a menu divider calls its surface hook at the top level and memoizes the surfaces. Hygiene: - the clear button no longer omits a prop it never forwards; - the tree's and the heading's comments say only what holds. Test: segmentedControl.test. A click answers to the onChange the control has now, not the one it was first given. It fails if the handler keeps the first select.
…them at any width (qorus#646)
Tier · Modern, Modern Light and Tier Group · Row checked one row of
equal-height cards, which holds only where the grid has room for every
card on one line. Opened at a phone's width, the grid puts one card
under the other, as it should, and the plays failed ("expected 54.5 to
be less than or equal to 1").
`expectModernRow` now reads the line count from the grid's own rule
(auto-fit columns no narrower than its `minColumnWidth`, with its gap,
on its content width): one row where they all fit, one card a line on a
phone, and the lines in between. It checks each line's heights and
buttons. The group's default minimum is exported as
TIER_GROUP_MIN_COLUMN_WIDTH, so the play and the group read the same
value. The frames do not change; the plays pass at 390, 800 and 1440 px
and in the Storybook UI.
|
Merged develop (#729). Version 0.78.13, develop+1. Added reqore fixes for qorus#646:
Test infrastructure:
Docs: the features labelled 0.78.7, a version never published, now say 0.78.10. No review comments or qlip denials were open. |
…anguage itself (qorus#646) On the CI runner (ubuntu-latest, amd64) all 27 stories of the storybook-posix-locale project failed on its guard: the browser, launched with LANG and LC_ALL set to C.UTF-8, reported `en-US`, so the project would have checked nothing. Reproduced on ubuntu 24.04 amd64 with Node 22.19.0 and `playwright install --with-deps chromium`, as the job installs it, and in the Playwright 1.60.0 image on amd64: the amd64 Linux Chromium (headless shell and full) reports `en-US` whatever LANG, LC_ALL, LANGUAGE or `--lang` says. Only the arm64 Linux build, where the project was first checked, takes its language from the POSIX locale and reports `en-US@posix`. Neither the runner's LANGUAGE nor the provider was the cause: the provider passes no locale and no `--lang`. The project now gives its browser context the language itself, with Playwright's `locale: 'en-US@posix'` (POSIX_LANGUAGE). Chromium then reports it as it is, Intl's default locale follows it, and `new Intl.DateTimeFormat(navigator.language)` throws as it does for such a user, on every platform and build, macOS included. The guard checks for exactly that language, everywhere. On ubuntu 24.04 amd64 the project fails 22 of 27 DatePicker stories on develop's DatePicker (`Invalid language tag: en-US@posix`) and passes all 27 with the fix.
|
CI fix (11cb7ba): the POSIX-locale project now gives its browser the POSIX language through Playwright's |
|
@Foxhoundn qlip build 20261024-132450: 336 of the 549 changed frames are the same capture against the same baseline as build 20261022-115740, which passed them at 0%, so they now cross qlip's threshold rather than changing in this push (probably the comparison change after qlip-server#50); each has a one-sentence cause. |
…aws (qorus#646)
ReqoreDataView declared and documented a `labels` prop (0.71.15) and
never read it: every section summary ("Object · 2 fields"), edit and add
affordance and type-picker name was the built-in English, so a consumer
translating the view, or naming a hash "2 fields", changed nothing. The
prop was not even destructured, and went on to the panel.
Every word the view draws now comes from the labels, the consumer's over
REQORE_DATA_VIEW_DEFAULT_LABELS (the words it drew before, so a view with
no labels reads exactly as before). Two labels are new: booleanTrue and
booleanFalse, the words of a yes/no value ("Yes", "No"), which keeps its
boolean colour.
Tests: a hash and a list named by the labels, the root's single value, a
yes/no in the labels' words, the edit affordances, and the defaults
unchanged (four fail without the fix). 0.78.13.
…orus#646) The labels were spread over REQORE_DATA_VIEW_DEFAULT_LABELS, so a label given as `undefined` replaced its default with nothing: a consumer that builds its labels from its own translations, and passes those it has none for as `undefined`, made the view call `undefined` for a summary, and it threw (TypeError: ctx.labels.objectSection is not a function). The view now takes only the labels that are given (omitBy isUndefined, as SegmentedControl does), resolved once per labels object. IReqoreDataViewLabels and REQORE_DATA_VIEW_DEFAULT_LABELS are exported from the package, so a consumer can type its labels and build on the built-in words. Tests: undefined labels keep their defaults, the built-in words are published, and a yes/no in the labels' words keeps the boolean colour (two fail without the fix).
…phone (qorus#646) Custom Labels renders an editable DataView whose labels are plain words: "9 fields" and "3 entries" in its summaries, "Yes" and "No" for its yes/no values, and "New field" / "New entry" on its add buttons. Custom Labels On A Phone is the same view at 390px (touch). Both plays check the summaries, that no built-in "Object ·" / "List ·" or "true" is left, the yes/no words and the add buttons; the phone play also checks the page is no wider than the screen. Both fail without the labels fix; the 25 existing DataView frames are byte-identical.
…orus#646)
Every Vitest run, and so every unit and story run, warned:
(!) Your Vite config uses features that are unsupported by
`configLoader: 'native'` ...
- ESM syntax in a file loaded as CommonJS (vitest.config.ts:1:1)
- ESM syntax in a file loaded as CommonJS (src/stories/utils/posixLocale.ts:10:1)
- ESM syntax in a file loaded as CommonJS (vite.config.ts:1:1)
The configs are written as ES modules, but a `.ts` file takes its module
format from the nearest package.json, and reqore's has no "type", so
Node, and Vite's native config loader (planned to become the default),
read them as CommonJS. Vite 8's bundling loader still loads them, as
CommonJS, and its compatibility check reports each file it loaded.
"type": "module" in package.json is not the fix: it would make the
CommonJS that `yarn build` publishes in dist/ load as ESM.
vite.config, vitest.config and the module the config shares with the
story setup (src/stories/utils/posixLocale) are now `.mts`, ES modules
whatever the package says, and the config imports them by their real
names, as Node needs. tsconfig.json includes src/**/*.mts, so
posixLocale stays type-checked. References to vitest.config.ts name
vitest.config.mts.
No behaviour change: the resolved config (every option, project and
plugin) is the same as before, loaded by the bundling loader and by the
native one, which now loads it too. Storybook finds vite.config.mts.
…the first render (qorus#646)
Picking a date in a picker with none warned, once for each field that
changed (WARN: A component changed from uncontrolled to controlled.):
- the calendar was given `focusedValue={focusedValue}`, which is `null`
until a date is focused, and react-aria's useCalendarState reads a
null focused value as none given. It was uncontrolled until the first
date was focused, then controlled. It is now given a date always: the
one focused, else the value's, else today's, the date it opened on
before.
- the time field was given `value={time}`, `undefined` until the first
date set a time. With none it is now `null`, which react-aria reads as
a controlled empty field.
What the picker shows and does is unchanged: its 27 frames are the same.
The story run now fails a story that warns (the next commits), and
Without Default Value and Should Save Time When Date Value Is Null
failed on these warnings.
…ontrol (qorus#646) Entity Row's Transparent With Icon Tile warned: "Addon controls: Control of type color only supports string, received "boolean" instead". The preview gives a color control to every arg whose name ends in "background" or "color", and `iconHasBackground` (EntityRow, Notification) is a yes or no. Storybook checks every arg's name against the matcher and warns whatever control the story's argTypes give it (a boolean control in Entity Row's meta still warned), so the matcher is where it is fixed: a name that says whether something has a background or a color is no longer read as a color. Every other color-named prop still takes a color picker; the frames are unchanged.
…heck its warning (qorus#646)
Two Table stories give the table what its development warnings are
about, on purpose, and the warnings were printed in every story run:
- Wrapped Rows Fit Their Body gives a wrapping table a `rowHeight`, to
show its body ignores it ("`rowHeight` has no effect on a table that
renders every row");
- Long Content Wrapped And Virtualized wraps a virtualized table
("`wrap` / `cell.wrap` has no visual effect when `virtualized` is
explicitly true").
Both warnings are right. The stories now check that the table gives
each one, once, and keep it off the console: `expectedWarning`
(src/stories/utils/expectedWarning.ts) is a story's `beforeEach` that
takes the console warnings matching it from before the story renders,
and passes any other on. What the stories draw is unchanged.
… it answers (qorus#646) Storybook warned on every start: - "Using CommonJS in your main configuration file is deprecated with Vite": .storybook/main.js was `module.exports`. It is now main.mts, an ES module whatever package.json says (it has no "type"), with `export default` of a typed `StorybookConfig`. Its settings are unchanged. - "--host is set to 0.0.0.0 but no allowedHosts are defined. Allowing all hosts.": served on every interface, it answered a request for any host name. `core.allowedHosts` now names localhost and this machine's own name, in full and its first label (`host.example.com`, `host`); an IP address is always allowed. A page that names another host gets "Invalid host" (403) from Storybook and from Vite, so a site that rebinds its own name to this machine cannot read it. Checked: Storybook starts with neither warning and serves the machine's short and full names, localhost and 127.0.0.1, and 403 for any other name; `storybook build` builds; the story run finds the stories through main.mts as before.
…s each (qorus#646) The table story run warned "Over 200 classes were generated for component styled.div" (StyledTableCell): every column width, minimum and maximum, pin offset, height cap and cell color was written into the cell's styles, so each one made a class of its own - 200 in the Table stories' page, 43 of them for widths alone - and a hovered or selected row swapped every cell's class for a new one. Those values are now CSS variables the cell sets on itself (`attrs` style: --reqore-table-cell-width, -min-width, -max-width, -pin-offset, -max-height, -background, -color, -border-color, -hover-background), read by the same declarations in the same rules, so the cascade is as it was. A value a cell has none for is not set and not read, as the declaration was dropped before. The cell's classes now differ only in what it is (pinned, wrapping, padded, interactive, its size). The 74 Table frames are byte-identical, and the warning is gone. Unit tests compute CSS variables as a browser does: jsdom gives a property that reads one as written (`var(--reqore-table-cell-width)`), so the folded-column tests read NaN for a cell's width. __tests__/cssVariables.ts makes getComputedStyle substitute each variable a property reads with its value where the element is (or its fallback; one set nowhere stays as written) and give it as jsdom gives a declared value; the unit setup installs it.
…class each (qorus#646) The Button story run warned "Over 200 classes were generated for component styled.span with the id of sc-aXZVg". That id is StyledEffect's, but StyledEffect made 31 classes: styled-components copies a component's statics onto every component that extends it, `warnTooManyClasses` among them, so StyledButton, StyledTag and every other styled(StyledEffect) count their classes together under StyledEffect's name. In the Button stories' page StyledButton alone made 150: each color a button is drawn in (22 fills, 17 edges, 13 description colors, 11 text colors, 9 rings) and each length (13 paddings, 9 heights, ...) made a class of its own. The button's paint (rest, hover and active fills, text, edges, ring, description colors) and its lengths (padding, font size, heights, widths, radius, description padding) are now CSS variables the button sets on itself (`attrs` style, --reqore-button-*), read by the same declarations in the same rules, so the cascade - a Tabs item's override of a button's colors included - is as it was. A color the paint has none for is not set and not read, and a length of a size a button lacks is written as before, so each declaration CSS dropped is dropped still. The button's classes now differ only in what it is. The 27 Button frames are byte-identical. The card hover-fill unit test reads a button's :hover rule from the stylesheet, which now names a variable: it computes it where the button is (computedValueAt).
…each (qorus#646) The Tag story run warned "Over 200 classes were generated for component styled.span" (StyledEffect's name, for every component that extends it, StyledTag among them: see the button's commit). StyledTag made 153 classes in the Tag stories' page: each fill and hover fill (12 each), border, text color, `maxWidth`, `width` and radius made a class of its own. A tag's paint (a solid tag's fill and text, hovered too, its key's shade and text, its edge and outline), a soft or text tag's colors, its `maxWidth` and `width`, and its size's font size, height and radius are now CSS variables the tag sets on itself (`attrs` style, --reqore-tag-*), read by the same declarations in the same rules. A value the tag has none for is not set and not read, as the declaration was dropped before. The tag's classes now differ only in what it is. The 21 Tag frames are byte-identical, and the Tag and Button story pages no longer warn.
…akes React warn does (qorus#646)
The story run failed a story that made React warn, and printed any
other warning in the middle of the run and passed: a field that turned
from uncontrolled to controlled (DatePicker), a color control given a
boolean (Entity Row), styled-components making over 200 classes (the
table cell, and Button and Tag together), and the table's own warnings
about props it ignores. The previous commits fix each of them.
The warning gate (src/stories/utils/reactWarningGate.ts) now keeps
every other console.warn too, under its own rule ("a console warning":
its first line, with the frames that name reqore's source), and fails
the story it was given in, before it or after a file's last story, as
it does React's. An error on the console is not a warning: a story
that shows what an error does logs it. A story that does on purpose
what a component warns about checks the warning with `expectedWarning`
instead. The gate also no longer names its own frame as where a
warning was given: its file moved to src/stories/utils/, and the
filter still named the old path.
Fail-first: with the gate and none of the fixes, the full story run
failed 10 stories in 6 files on these warnings; with the fixes, all
1269 pass and the run prints no warning. Documented in
.claude/CLAUDE.md, with how unit tests read CSS variables.
… an error does and checks it (qorus#646)
The story run printed three errors for each Error Boundary story that
throws (Throws, Show Details, With Custom Message, With Custom Global
Message, Resets), and passed:
- "Error: Uncaught Error: This is a test error": React, in
development, reports an error it hands to a boundary as uncaught too,
and the story run logs that report;
- "The above error occurred in the <ThrowsError> component:" (React);
- "ReqoreErrorBoundary caught an error: ..." (the boundary, which logs
what it catches in production too).
The warning gate (reactWarningGate.ts) now fails a story for any
console.error that is not React's warning, under its own rule ("a
console error"), as it does for a console warning. Fail-first: with it,
those five stories failed on exactly these errors.
They are what those stories are about, so the stories check them:
`expectedError(...patterns)`, next to `expectedWarning` in
src/stories/utils/expectedConsole.ts (renamed from expectedWarning.ts),
is a story's `beforeEach` that takes the console errors the patterns
match off the console, and `expectErrored()` checks each was logged
once. Any other error goes on to the gate. Both read a call as the gate
does (`consoleCallText`: its first line, an error as its name and
message).
The precheck and the full story run pass (2090 unit tests, 1269
stories), and the run logs no warning and no error.
…y whoever runs it (qorus#646) Vite 8 forwards a browser's console and its unhandled errors to the terminal by default only when an AI agent runs it (std-env's `isAgent`), so a run in an agent's shell printed what CI's and a person's never did. With every warning and error now judged by the story run's gate, what remained was "[Unhandled error] Error: This is a test error" once for each Error Boundary story that throws: React, in development, reports an error a boundary caught as uncaught too, by dispatching it to the window, and Vite forwarded that event. No story can stop it without stopping React's own handler, which runs after Vite's. `server.forwardConsole: false` in vitest.config.mts makes every run print as CI's does. Nothing a story logs is lost: the gate fails a story for any warning or error it did not expect and names it (checked by dropping the Throws story's expectedError: it failed on its three errors).
Two things printed on every unit run, outside any test's output: - "Error: boom-from-child" and its stack, four times: React, in development, reports an error a boundary caught as uncaught too, by dispatching it to the window, and jsdom prints any error event no listener cancels. The error boundary tests now take that report (cancelled, as a page that handles it does) and check it came. - "Not implemented: HTMLCanvasElement's getContext() method": jsdom has no canvas. The unit setup gives a canvas no 2D context, which the code that measures text with one handles (it measures nothing); a test that measures text still gives the canvas a context of its own. 2090 unit tests pass and the run prints only their results.
… story run (qorus#646) Opened in Storybook (David's review), every Error Boundary story that throws ended "errored", before this branch and after b51b4a4: - Storybook marks a story errored when its page reports any error to the window while it plays, and React, in development, reports an error a boundary caught to the window as uncaught too. The browser printed it as well. - b51b4a4's expectation named the story run's report of that error on the console ("Error: Uncaught Error: ..."), which Vitest makes and a browser opened in Storybook does not. - Show Details and Resets pressed "Details" and "Reset" by their words, which a narrow screen draws as icons named by those words, so at 390px their plays failed. - Both spread Throws after their own parameters, so they showed its description, not theirs. `expectedError({ uncaught, logged })` now takes the window's report of the `uncaught` error and cancels it, as a page that handles it does (so the browser does not print it, and React does not log it again), takes the story run's console report of it where there is one, and takes the `logged` console errors. `expectErrored()` checks the uncaught error was reported once and no other was, and each logged error once; the stories spread its `parameters`, which leave Storybook's own check to it. The stories press the fallback's actions by name and keep their own descriptions. All six pass in Storybook at 1440 and 390px and print nothing there, and pass in the story run, which logs nothing. The frames are the same, but Show Details, whose stack trace names the story file's lines.
|
Added (11cb7ba..87b115a), stories approved:
Default frames are unchanged (Show Details' stack trace shows new line numbers). Precheck: 2093 unit tests; story suite 1269/1269. |
…hen the app gives one (qorus#646) The navigation rail spells out its labels once the pointer has rested on it for showLabelsDelay (1.5 s), on a setTimeout. An app's stories could not say when that is: in the Qorus IDE's story run a play that clicked the rail's chevron and moved on was captured with the labels open when the runner was slow and closed when it was fast, and the pointer parked "on the page" afterwards landed inside the widened rail. ReqoreUIProvider takes options.schedule, (ms, fire) => cancel: how Reqore does something after a while. It is the machine's clock by default (scheduleOnTimer, the setTimeout it was), so nothing changes for an app that gives none; an app's stories hand their own clock and the dwell passes when they say. The rail's label dwell waits on it. Two tests: the labels wait for the app's schedule however long the machine's clock runs, and leaving the rail cancels the wait; both fail on the timer.
…orus#646) A drawer or modal traps focus while it is open and means to give it back to the element that had it when it closes. The give-back read the saved element one frame after the hook had cleared it, so it never happened: focus fell to the page when the dialog went away with the focused control in it, and stayed on the opener only when the dialog closed before its first frame. Which of the two a reader got depended on timing; in the IDE's story runner the same story ended with its tab focused on one run and nothing focused on the next. The element is now read before it is cleared, and given focus back only when focus would otherwise be lost (on the page, or still inside the container that no longer traps it): focus the app has moved somewhere else since is left there. A trap that ends before its first frame no longer pulls focus into its container afterwards.
…ced motion (qorus#646) A table scrolling back to its top, a page of results scrolling into view and a navigation rail's section scrolled to smoothly whatever the user's "reduce motion" setting (WCAG 2.3.3). They now ask smoothScrollBehavior() (next to usePrefersReducedMotion), which answers 'smooth', or 'auto' (at once) for a user who asked for reduced motion, read as each scroll starts. A menu item, which already honoured the setting, read it once when it rendered first; it now follows a change of it. A test fails on a scroll that names smooth scrolling itself.
…p gives one (qorus#646) A table's scrollbar thumb shows while its body scrolls and fades 1.2 s after, on a timer of its own. An app that captures its pages, the IDE's story runner, caught the thumb on one run and not on the next, by when the capture came. The fade now waits on the provider's schedule (options.schedule, as the navigation rail's hover dwell does), and on a timer, as before, without one. Tests: the thumb waits for the app however long the machine waits, another scroll cancels the pending fade, and without a schedule it fades on its timer.
… a pattern that backtracks (qorus#646) CodeQL flagged `toLanguageTag` on #723 (js/polynomial-redos, alert 5): `/[.@].*$/` dropped the POSIX codeset or modifier with a pattern that does not reach past a line break, since `.` stops at one and `$` is only the end. In a name with a line break after the codeset, it left the codeset in place, and for every `.` before the break it tried again, which is quadratic in the length of what a browser or host hands over. The tag is now cut at the first `.` or `@` by position. A test reads a codeset and a modifier that run on past a line break as dropped (it fails with the old pattern), and a long run of dots before a break as no tag.
|
Added (87b115a..a5b4adf), for qorus#646; the four story-related commits were approved by David with their story table (nothing changes how a Reqore story looks):
Each fix has a test that fails without it. Precheck at a5b4adf: lint, 2106 unit tests, both type builds. The touched story files (Table, NavRail, Paging, Menu, DatePicker) pass in all four story projects (193 of 193). DeepScan reports "3 new issues" on this PR. I could not read them: the DeepScan dashboard needs a login, so they are not looked at yet. |
Text and icons read on what they are drawn on: the qoretechnologies/qorus#646 contrast work from #717, as its own PR. The front-end team asked for it to be kept apart from #717's functional fixes, which are in #722.
Base:
develop. #722 merged into develop as 9c340d98 (0.78.10), and #728 as 52d0155a (0.78.11, a rich text editor chip is the size it says). Both are published. Develop is merged in (e1b98b12, 3b9de603), so this diff is only the contrast work.Version: 0.78.13 (8db79562), develop's next version. Develop is at 0.78.12, which is on npm.
What it does:
ReqoreSurfaceProvider/useReqoreSurface), and faded text is faded only as far as it still reads.prefers-reduced-motion) is honoured inside Reqore.How it was built:
wrapLabelstories (c16f4da2, on the functional branch);open()control fix (204dfc79, on the functional branch), found by reqraft's tests on fix: narrow row actions, widths, popover, dropdown keys, checkbox wrapLabel, sticky table header, date picker (qorus#646) — 0.78.9 #722's prerelease;git diff 053b4e41shows only those.Visual change: against the functional branch, 625 of the 1189 captures the two share change. The table David approved counted 626; the one frame apart is Paging · Infinite With Auto Load, whose scroll position is noise.
Gates:
🤖 Generated with Claude Code
https://claude.ai/code/session_01EVRGWEwQAi5btU1qdHBfmb