Repository navigation
fix: narrow row actions, widths, popover, dropdown keys, checkbox wrapLabel, sticky table header, date picker (qorus#646) — 0.78.9 - #722
Merged
Conversation
…ch child once (qorus#646) A responsive group folds its children into a `…` menu one at a time while its row overflows. Three things were wrong with how it counted: - Nothing stopped the count once every child was in the menu. A group whose overflow button alone is wider than its room (a labelled or large button, a narrow container) kept counting. Each step re-rendered the same row and measured it again, forever. In a browser that is a render loop at frame rate; under act it never returns. The Qorus IDE's agent found the unbounded counter while chasing a frozen story. - The row sliced the raw `children` array, but the menu took from React.Children.toArray(children), which leaves out the holes a caller leaves (null, false: an action it decided not to render). With a hole in front, a child was drawn in both the row and the menu. - A group with one child, which is not an array, threw at its first fold (`children.slice` is not a function). The row, the menu and the count now read one list, `foldable`: the children as React renders them, holes left out. The count stops when the row fits or when nothing is left in the row to fold. That bound is the end of the list, not an iteration cap. The old comment explained why the count could not be bounded by the number of children: the row and the count read different lists. They no longer do. Tests: controlGroupFoldIsBounded. Three cases fail before the fix: the group measures itself past the test's limit, `children.slice is not a function`, and a child is in both the row and the menu. A fourth case, the row still stops as soon as it fits, passes both before and after.
…s named for its row (qorus#646)
When a narrow EntityRow or SeverityRow folds its actions into one menu, the
action's own aria-label was not passed on to its menu item. reqraft's
experiment card has "Show analytics for Short copy" on a button; folded, the
item was called only "Show analytics", the same as the item in every other
row's menu. Every menu button was also called "Actions", so a list of
versions read as a list of identical buttons.
- An item now keeps its action's own aria-label as its accessible name. Its
words still come from its label, tooltip or aria-label, as before.
- The menu button, and the menu it opens, are named for their row when the
row's label is text: "<actionsMenuLabel>: <row label>", for example
"Actions: Short copy" (rowLabel on ReqoreRowActionsMenu, rowLabelText).
A row whose label is an element keeps the plain "Actions". A colon rather
than "for", so a host's own word ("Akce") reads right in its language.
- Applies to ReqoreEntityRow and ReqoreSeverityRow. A table's row menu is
unchanged, since its rows have no label of their own.
Tests: rowActionsMenu, EntityRow and SeverityRow. Eight cases fail before
the fix: items keep their names, the button and menu are named for the row
(also with a host word), a blank row label names nothing, rowLabelText, and
the rows' folded menus. The EntityRow and SeverityRow narrow-menu stories
check the row-named button. COMPONENTS.md documents both.
…over takes a click at once (qorus#646) useCombinedRefs handed a component's element to the refs it was given in an effect, after the browser had painted it. A popover around a Reqore button or a dropdown learnt of its trigger only then, and armed its click listener in the render after that. So a trigger on screen opened nothing when it was clicked straight away, and nothing on the page said when it would. The Qorus IDE's story harness kept sleeps for it (a dropdown's .template-selector, among others). The effect also ran after every render, since its list of refs is a new array each time. useCombinedRefs now returns setRef, a ref callback. React calls it in the commit that attaches the element, so every ref holds the element before the paint. It changes only when a ref given changes, and then React hands the element over as it does for any new ref callback. Button, Input, Textarea, Tabs item, FadeScroller, Table body and header, Panel, Menu and Menu item draw their element with it. The four that draw through a ref callback of their own (Popover, Panel, Menu, Menu item) call it from a callback that is one function for the component's life: a new callback each render would detach and attach the element every time. The unused `_innerRef` state, set again on every render, is gone. Tests: popoverArmedWhenDrawn. The page is drawn and committed with flushSync and nothing more, which is the state of a page as it is first painted. The trigger is then clicked at once. A dropdown's popover opens; it stayed shut before the fix. A popover around a Reqore button opens too, which already held before the fix.
…story taps them on a phone (qorus#646) A Qonsole panel on a phone folds its whole trail into the dropdown named for the current chat. In the Qorus IDE a tap opened nothing, so a finger reached neither the chat's full name nor "Back to chats". The IDE was on a #708 prerelease from before 26327a4. That commit made every ReqoreDropdown open at a tap (opensOnTap), including both of the breadcrumbs' fold menus, and it is in 0.78.5. Nothing tested it for the breadcrumbs. - Both fold menus (the current-page dropdown and the "…") now say what they are and whether they are open: aria-haspopup="menu" and aria-expanded, as the row actions menu does. - The "…" dropdown has a class of its own, reqore-breadcrumbs-overflow-menu. Story Navigation/Breadcrumbs › Folded Trail Opens At A Tap (touch, 390 px): - A finger taps the folded current crumb. Its menu names the chat in full, and "Back to chats" in it is reached by a tap. - A finger taps the "…" of folded ancestors. It opens, and "Back to chats" is reached there too. - Both buttons say they are menus, closed and then open. Before this change the aria checks fail. With the dropdown's opensOnTap taken out, the tap checks fail (the menu never opens), which was the IDE's case. Hover is unchanged: the menus are still hoverStay for a mouse.
… (qorus#646)
A field with `focusRules: { type: 'auto' }` is focused when it appears. useAutoFocus
re-applied that focus — and moved the selection to the end — every time its element
reference changed. A controlled input hands React a new ref callback on every render,
so the SAME node arrives under a changing identity on each keystroke; under a heavy
per-keystroke re-render the re-applied selection raced the keystroke and dropped a
character — typing "ca" into a ReqoreCollection's auto-focused search left "c".
useAutoFocus now focuses an element once, when it first appears, and leaves a
re-appearing same element alone. A genuinely different element that takes its place —
a new field opening — is still focused.
Tests:
- useAutoFocusOncePerElement: the same element handed back under a changing reference
is focused once, not again (fails before the fix); a different element that replaces
it is focused.
- Collection story "Search Keeps Every Typed Character": a filterable collection whose
search auto-focuses keeps every character typed into its heavy, re-rendering filter
and the caret where it is typed.
…s#646) A ReqoreButton with an icon and no label of its own had no accessible name unless the caller gave it an aria-label: assistive technology heard "button", and a test that finds buttons by name found nothing to test. In the Qorus IDE's Interfaces view, 24 of 29 visible buttons were unnamed. When a button has no visible label, no aria-label and no aria-labelledby, its tooltip names it if the tooltip is a string - the words the button would say. A tooltip that is a node or an object is left to the caller, and a button with a label, or one already named, is not named twice. No pixel changes. Tests: buttonTooltipName. An icon-only button is found by its tooltip's name (fails before the fix); four guards pass before and after: a labelled button is named by its label and carries no aria-label, an explicit aria-label wins, aria-labelledby is not doubled, and an object tooltip names nothing.
…y (qorus#646)
An editable DataView draws each key as a control ("Rename key") holding
the key's tag. The control is a grid item, and a grid item stretches to
its cell, so it was as wide as the key column (220px for a 50px key) and,
in a stacked row, as wide as the row: a click target across empty space,
which the Qorus IDE's width check reported. It now sits in the key's
cell at its own width.
Story: DataView › Editable Key Rename checks the control is as wide as
its key (fails before: 220px for a 59px key).
A design change, from David's width rule for the Qorus IDE: on any screen wider than a phone's, a control is as wide as what it says and never stretches to fill its row; on a phone (480px or narrower) it may span the screen, where a full-width row is a finger's target. A narrow severity row (and an entity row with `narrowActions='stack'`) stacks its actions under the label, one to a line, and each was the row's width wherever the row was narrow - also in a 320px drawer on a 1920px screen, where a button was a bar with a word in it. Stacked, each is now the row's width only on a phone (`isMobile`) and as wide as its label elsewhere. An action that is itself a menu does the same. Stories: SeverityRow › Narrow Container Actions Stack and EntityRow › Narrow Container Actions Stack / Narrow Stacked Menu Action Is Labelled check each action against the row on a phone and against its label on a wider screen; the touch story checks the phone.
…e menu is opted into (qorus#646) Foxhoundn on the ExperimentCard stories (toolkit-react #137, qlip #361): "put the buttons back here, they should NOT be folded into a menu. The buttons need to be accessible and visible. Do not make general changes like that - it needs to be opted in!" 0.78.5 (a7790c8) made a narrow entity row (under 480px of its container) fold two or more actions into one menu button by default, so every row that never asked for it lost its buttons behind a ⋮: the A/B test card's Show analytics, Accept and Remove among them. Up to 0.78.0 a narrow row kept its actions as buttons under the label. - A new narrow mode, `'row'`, is that layout again, and EntityRow's default: under the label, a row of buttons each as wide as what it says, onto another line where they do not fit (0.78.0's did not wrap, and labelled actions reached past a phone's card). - `'menu'` is opted into. A host that relies on the fold sets `narrowActions='menu'`. - `'stack'` is as it was. SeverityRow keeps `'stack'` as its default and takes `'row'` too. - A narrow row says which it is in `data-actions` (`row`, `stack` or `menu`). Tests: a narrow row keeps two actions as buttons under the label by default (fails before: they were folded); the menu tests opt in. Stories: EntityRow › Narrow Container Actions Row (fails before); the menu stories set `narrowActions='menu'`.
… not how it came to be (qorus#646) Collection › Search Keeps Every Typed Character now says what it renders and shows, in the "Renders X…" form, without naming internals or history (the /audit of #717). The functional part of 8e93b4e on #717; the Message story's description is on the contrast branch with the story it belongs to.
…the control group tests share their layout model (qorus#646) From the /audit of #717, each checked against the code first: - Breadcrumbs › Folded Trail Opens At A Tap said both menus say "open" only while open, and checked only "open": it now checks each closes again, and that "Back to chats" is all that was chosen. The narrow header, not a short trail, is what folds it. - EntityRow and SeverityRow › Narrow Container Actions Menu name the menu button they check. - A host word for the row menu is checked without a row too; a test name says what it checks. - The two control group tests share the modelled control-group layout (__tests__/shared/controlGroupLayout.ts) instead of each copying it. The functional part of 5732f35 on #717; its contrast part is on the contrast branch.
The clear button on an input or a text area is an icon with a click handler on it. It had no role and no name: a screen reader said nothing for it, and a test could not find it by role. The Qorus IDE's tests could not find the expression's ×. It is now a button named "Clear". An input and a text area take `clearLabel` for a host's own words. The text area keeps the prop off the element it renders: the rich text editor's Slate editable writes the props it does not know onto its div. Test: inputClearButtonName. An input's button is found by role and name and clears the field; so is a text area's; and a host's word is used when given. All three fail before.
…e end of the label's line (qorus#646)
In the opt-in 'menu' mode, two or more actions fold into one menu button at
the end of the label's line when the row is narrow. A row of that mode with
one action had no menu to fold into, so it wrapped the action under the
label: a line of its own for one button, where a row with more had none.
Now that one action stays a button at the end of the label's line, where the
menu would be. The row says so: `data-actions='end'`. Only 'menu' rows
change. The default ('row', visible buttons under the label) and 'stack' are
as they were (David, qorus#646).
Stories, both failing before (the action was under the label):
- Entity Row > Narrow Container Lone Action In Menu Mode (a 400px container);
- Entity Row > Narrow Container Lone Action In Menu Mode On A Phone: a
finger's size, and a tap runs the action without opening the row.
Narrow Container Actions Menu (two or more) is unchanged. The prop's docs,
TReqoreNarrowRowActions and COMPONENTS.md say it.
…om its render (qorus#646)
The date picker read its value, and its minValue and maxValue, with
parseAbsoluteToLocal, which throws for anything but an ISO date-time with a
zone. Any other string threw from the render, and so did an invalid Date
(through toISOString). The Qorus IDE's Workflow Orders page went to its
router's error page for "2026-06-02T08:00:00".
The rule, in one reader (readDate) that never throws:
- A date-time with a zone or an offset is that instant, in the user's
zone, as before.
- A date-time without one is that wall-clock time in the user's zone: ISO
8601 reads a date-time with no offset as local time.
- A date alone is that day at midnight, local.
- Anything else, text that is no date or an invalid Date, is no date. The
field is empty, and says it was given a value it cannot read the way it
says a value out of its bounds: it is invalid (aria-invalid,
data-invalid), drawn in the danger intent.
- A bound it cannot read is no bound. The year and month dropdowns fall
back to their default range.
Other inputs that read a value while rendering, checked:
- DataView's scalar threw for an invalid Date (toISOString); it now shows
it as it is ("Invalid Date").
- TimeAgo already handled an unreadable time.
- There is no other date, time or date-range input, and the number inputs
parse nothing while rendering.
Test: datePickerUnreadableValues, 7 cases, all failing before:
- a date-time with no zone, and a date alone, shown as local time;
- text and an invalid Date: empty and invalid;
- unreadable bounds, and an invalid Date as a bound;
- DataView with an invalid Date.
…ot read (qorus#646) Form/DatePicker > Values Without A Zone Or Unreadable renders three pickers: - "2026-06-02T08:00:00" shows 08:00, local; - "2026-10-17" shows that day at midnight; - text that is no date is an empty field, drawn as invalid. Before a6def18 the story threw from its render: "Invalid ISO 8601 date time string: 2026-06-02T08:00:00".
…, and a pinned column's header hides what scrolls under it (qorus#646)
Scrolled, a table's pinned columns were drawn over its sticky header: the
label was cut off and the menu button covered, and only the middle showed
the header. The panel's sticky header and a pinned cell were both at
z-index 2 in one stacking context, and the cells come later in the
document, so they won. A panel with a sticky header now draws its body in
a stacking context of its own (isolation: isolate), so nothing in the body
is drawn over the header, whatever its z-index. It is not a containing
block: nothing in the body is placed differently, and popovers and menus
are drawn outside the panel.
A pinned column's header cell painted nothing, so the column headers that
scroll under it showed through ("ORDER DA" under "ACTIONS"). It now paints
the theme's main color, opaque, as a pinned body cell does.
Stories: Layout/Panel "Sticky Header Above Its Body" and Collections/Table
"Sticky Header Above Pinned Columns" scroll each layer under the header and
read every point across it, and require pinned column headers to be opaque
and on top. Unit: __tests__/stickyLayers.test.tsx.
The functional part of 268e2fb on #717. On the contrast branch, the
pinned header cell paints what the header row is drawn on and tells what
it holds.
…the end of the label's line (qorus#646) As on ReqoreEntityRow (da508bf), at David's direction: in the opt-in 'menu' mode, two or more actions fold into one menu button at the end of the label's line when the row is narrow. A severity row of that mode with one action had no menu to fold into, so it stacked the action under the label: a line of its own for one button, where a row with more had none. Now that one action stays a button at the end of the label's line, where the menu would be, and the row says so: data-actions='end'. It is not stacked, so an icon-only action stays its icon with its tooltip, as at full width. Only 'menu' rows change: the default ('stack') and 'row' are as they were. Stories, both failing before (data-actions was 'stack', the action under the label): - Severity Row > Narrow Container Lone Action In Menu Mode: a 400px container, beside the same row with the default, still stacked; - Severity Row > Narrow Container Lone Action In Menu Mode On A Phone: a finger's size, and a tap runs the action without opening the row. Unit (SeverityRow.test.tsx): 'end' for 'menu' with one action, labeled or icon-only (fails before); one action unchanged by default, with 'stack', with 'row', and wide with 'menu'. The prop's docs, RowActionsMenu's TReqoreNarrowRowActions and COMPONENTS.md say it.
… label (qorus#646)
David, on the EntityRow phone story ("the button takes up too much space on
a narrow screen; on a narrow screen it should read 'Test'"), as a general
rule: a row-end action on a narrow screen uses a short label.
Every row action type takes an optional `shortLabel`
(IReqoreRowActionShortLabel: IReqoreEntityRowAction, IReqoreSeverityRowAction,
TReqoreTableCellAction). Where reqore draws an action compactly, on a line
it shares with the row's label, the action says it:
- the one action of a narrow narrowActions='menu' ReqoreEntityRow or
ReqoreSeverityRow, at the end of the label's line (data-actions='end');
- a narrow table's folded column, for an action with no icon (one with an
icon is its icon alone there, as before: the column is the menu button's
width).
Its label is then its tooltip (unless it has one) and its accessible name
(unless it has one), so a short label is written as words of its label.
With no short label (getCompactRowAction), an action with an icon and a
tooltip is its icon alone there, named by its label: its author gave it the
words it reads by without its label. Any other says its label, as before.
So no row drawn today changes unless it gives an action a short label or a
tooltip. Wide, and on a line of its own (stacked, or in a row under the
label), every action says its label. Menus name each action by its label.
Stories:
- Entity Row > Narrow Container Lone Action In Menu Mode On A Phone: the
action reads "Test", and is still called "Test the connection" (fails
before: it read "Test the connection");
- Entity Row > Narrow Container Lone Action Short Label and Severity Row >
Narrow Container Lone Action Short Label: three rows in a 720px box and a
400px one; wide each says its label, narrow its short label, its icon
alone (an icon and a tooltip), or its label (neither). Both fail before.
Unit: __tests__/rowActionShortLabel.test.tsx (getCompactRowAction, both
rows, the table's folded column; 10 of 16 fail before, the 6 that pass are
the cases that do not change).
COMPONENTS.md documents shortLabel on EntityRow, SeverityRow and Table; the
type is exported from the index.
…ose does, as prettier writes it (qorus#646) 90f64b0 escaped the double quotes around "Test" and "Test the connection" in the description, which prettier writes as a single-quoted string: two more deviations in a file whose count must not go up. Typographic quotes need no escape. EntityRow.stories: 36 deviations, as before 90f64b0.
…roup (qorus#646)
A wrapped ReqoreControlGroup gave its children no cap. A child as wide as
content that does not wrap stayed that wide, past the group and the card it
was in: the Qorus IDE's data provider path (a `fluid wrap fill` group whose
steps are `fluid={false}` groups holding a select that says its value and
description) ran 487-712px wide in a 346px card on a phone, and the
favorites dialog's Apply was cut off.
Now each child of a wrapped group is capped at the row (max-width: 100%)
and may shrink below its content to fit it (min-width: 0). A capped button
says its description on as many lines as it takes, as a fluid button in a
wrapped group already did (fluid buttons always had max-width: 100%).
The rule is reqore's global style (UIProvider,
WRAPPED_CONTROL_GROUP_CHILD_SELECTOR, `:where(.reqore-control-group-wrapped)
> *`), and weighs nothing: a child that sets its own width limits (a
button's maxWidth) keeps them. A group's own styles could not weigh less
than the child's: styled-components scopes them to the group's class. The
group marks itself `reqore-control-group-wrapped` when it wraps.
Stories (Form/Control Group):
- Wrapped Group Keeps A Long Button Inside: a picked path in a 346px card;
fails before (children 488, 351 and 382px wide in 346px);
- Wrapped Group Keeps A Long Button Inside On A Phone: the same in a card
on a phone, with the card's Apply; fails before (488px in 332px).
Unit: __tests__/controlGroupWrapCap.test.tsx (which children are capped,
the rule's weight; without the class, the two that cap fail).
…s its own maxWidth (qorus#646) A button had max-width: 100% only when it was fluid and not fixed (`maxWidth || (fluid && !fixed ? '100%' : undefined)`). A fixed button had no cap at all, and fixed reaches a select's button directly or through a group (a group hands its children `fixed`). So in the Qorus IDE's data provider path, a step's select saying its value and description stayed as wide as both on one line, 487-712px in a 346px card on a phone, even inside the group round 9 capped at the row. Now every button is capped at its container (max-width: 100%) unless it sets its own maxWidth, which stays its limit; a square button keeps its side. fixed keeps meaning that the button does not stretch. A capped button's label is clipped as any button's is, and its description takes as many lines as it needs, as in round 9. What else this changes (every story file captured before and after, 1188 captures): a panel's single action that the title bar's responsive group folded into a menu of one, because its labelled button overflowed the group, now stays its icon, as the panel's own stories describe (Panel · Compact Title When Narrow, Fit Label Comparison); and a drawer sheet's header icons sit half a pixel lower (82.64 to 83.14px; the buttons' boxes are unchanged), which shows in Drawer · Responsive Sheet Mobile. Stories, failing before: Button · Fixed Button Stays Inside Its Container (476px in a 346px card) and its phone story. Unit: __tests__/buttonNeverWiderThanItsContainer.test.tsx (fixed, fixed and fluid, and neither fail before; fluid, maxWidth and square are the cases that do not change).
…rus#646) ReqoreColumns, and ReqoreCollection with it, laid its columns out at `minmax(<minColumnWidth>, <maxColumnWidth>)`. A collection's minimum is its zoom width, 400px at 100%, so in a container narrower than that the column stayed 400px and its cards ran past it: in the Qorus IDE's favorites dialog on a phone, a card was laid out at 63-433px in a dialog whose content ends at 342px, and its Apply was out of reach. Now the minimum is `min(<minColumnWidth>, 100%)` (getColumnMinimum): the width is the narrowest a column is where the container has room for it, not a width it keeps where there is less. A keyword minimum (auto, min-content, max-content, fit-content(...)) is kept as it is. Callers that already wrote min(<width>, 100%) themselves are unaffected. What else this changes: nothing else drawn (every story file captured before and after, 1188 captures). Stories, failing before: Collection · Narrow Container Keeps Its Items Inside (cards 420px in a 342px box, Apply and Remove past it) and its phone story (the favorites in a dialog on a phone). Unit: __tests__/columnNeverWiderThanItsContainer.test.tsx (the minimum and the rule fail before; the keywords are the case that does not change).
…e field out of view (qorus#646) Root-caused by the Qorus IDE agent (David's round-7 review): in a short window - a 1280x720 Storybook window's ~380px canvas, a phone in landscape - a click into an editor opened no list at all. A popover held below its target (keepPlacement) makes room by scrolling the target's scroller by what the list lacks. A list taller than the window scrolled the field from y=209 to y=-59: out of view, so Popper marked the reference hidden, and the popover closed as it opened. The field is now scrolled up at most to the top of what shows it (its scroller, or the window where the scroller starts above it). What is still missing is taken from the list's height (it is clamped to the room under the field), or the list flips where that room is under a hundred pixels, as before. Stories (Form/RichTextEditor), both failing before (the list never opens): - The List Opens In A Short Window: a field 200px down a scrolling page in a 980x380 window, a list of twenty templates; - The List Opens On A Phone In Landscape: the same on a touch screen at 844x390, opened with a tap.
…pened level is the same list (qorus#646)
Found building the Qorus IDE's field picker, and in released reqore with
plain nested `items`.
The list of an opened level is drawn by the list of the level above it,
which kept its document key listener and its `passKeyHandler`. With the
focus outside the list - a text field or an editor it is opened from,
which keeps the focus - every key was handled by both levels: ↓ moved the
highlight in both, and Enter in the opened level also selected the row the
level above had highlighted ("Inner 1 | Plain" for one Enter). Only the
level the list shows now listens, and hands its controls to
`passKeyHandler`.
An opened level was also handed only some of the list's options, so it
dropped `filterRanking`, `filterPlaceholderBuilder`, `noItemsFoundText`,
`listId` and `passKeyHandler`: a list in German said "Search 2 items..."
and "No items found" one level down, ranked nothing, and its rows had no
ids for `aria-activedescendant`. It is handed them all now.
Stories (Form/Dropdown), both failing before:
- Only The Open Level Hears The Keys: a group opened from a text field
that keeps the focus; ↓ Enter picks "Inner 1" only;
- An Opened Level Says And Ranks As The List Does: a German list ranked by
relevance with a `listId`, opened at its group.
…as it (qorus#646) Root-caused by the Qorus IDE agent from David's IDE round-1 review: a checkbox with `wrapLabel` in less room than its box and its label on one line - the IDE's "Skip totals rows" in a 38px row - kept its label on one line, past the row. `wrapLabel` only stopped the label's text from being kept on one line. The label sits in two control groups (on either side of the box), and a group that is not fluid is `flex: 0 0 auto`: it kept the width of the label's one line, so there was never less room for the label to wrap in. The checkbox was also exactly one line high, so a label that did wrap would have run out of it. With `wrapLabel` now: - the groups the label is in shrink (`flex-shrink: 1; min-width: 0`); - the checkbox is never wider than its container and shrinks in a row (`max-width: 100%`, `min-width: 0`, `flex: 0 1 auto` unless fluid or fixed); - it is as high as its label, at least one line (`min-height`). A checkbox whose label does not wrap is drawn as before. Stories (Form/Checkbox), each failing before (the label runs to 338px or 632px in a 160px box, or past a 390px screen): - A Long Label Wraps In A Narrow Box (beside one that does not wrap); - A Long Label On The Left Wraps In A Narrow Box; - A Three Line Label Makes The Checkbox As High (a checkbox and a switch); - Long Labels Wrap On A Phone (touch, 390px; a tap on a wrapped label checks its box). Unit: __tests__/checkboxWrapLabel.test.tsx; its three wrapping tests fail before, and a checkbox that does not wrap keeps its height and width.
… saying what is missing (qorus#646) DeepScan on #717 (NULL_POINTER, __tests__/stickyLayers.test.tsx:61): the header row was asserted with `expect(row).toBeTruthy()` and then read through `row!`, and each header cell's probe through `!` too. An assertion that does not narrow the type leaves the next line reading a property of what may be nothing. `requiredElement(selector, what, parent)` and `requiredAttribute(element, name)` (src/stories/utils/required.ts) return what they find, or throw with what is missing. stickyLayers uses them for every element and attribute it reads, and its header cells are told from their row's children by `instanceof HTMLElement`, not a cast. The same in round 11's own tests and stories, where a `!` or a cast read an element that might not be there: checkboxWrapLabel.test, the Checkbox stories' boxes and phone story, the Dropdown stories' search field, and the RichTextEditor short-window stories' page, list and window. Nothing drawn changes: the Checkbox, Dropdown and RichTextEditor TemplateFieldUX story files pass as before (61 stories) with captures identical to the last ones.
…s the same (qorus#646) Found with DeepScan's REACT_INEFFICIENT_PURE_COMPONENT_PROP on #717 (a table header cell's inline click handler for the memo ReqoreButton): handing the button a stable handler did not stop it being drawn again, because the stacked control group it is in handed it a new style object on every render. A stacked group squares the corners that meet and tucks each border under the one before, in a style it makes anew each time; it was a prop that always changed, so a memo child - a button, a dropdown - was drawn again with its group whatever else it was handed. useCloneThroughFragments (the control group's, the button rail's and the menu's) now keeps the style each child was last handed, by its place, and hands it again while it has the same properties with the same values. What is drawn does not change. Unit: __tests__/controlGroupKeepsChildStyle.test.tsx - a memo child is not drawn again when its group is (fails before), and is when its place in the group changes its style (its corners rounded at the new end).
…s and objects while they are the same (qorus#646) DeepScan on #717 (REACT_INEFFICIENT_PURE_COMPONENT_PROP, src/components/Table/headerCell.tsx:430): the cell handed the memo ReqoreButton a click handler made anew on every render, so the button was drawn again each time the cell was - when another column was sorted, or the cell moved - for nothing. Its effect object, and the menus' (ReqoreDropdown is a memo too) handlers, input props, effect and button style were made anew the same way. Now: - the click (⌘-click sorts, then the column's onClick) and the filter handler are useCallbacks of what they use; - the menus' input props, the menu's effect (the filter's glow, the sorted column's gradient) and the button's effect are memos of what they are made of; - whether a menu opens (not on a ⌘-click) and the ⋯'s style are module constants. A ⌘ check of an event that is not there no longer throws. What is drawn does not change. Unit: __tests__/tableHeaderCellKeepsItsControls.test.tsx - the header cell's button, and its menu's, are not drawn again when another column is sorted or the cell moves (both fail before, with the cell's handlers made inline), and are when the cell's own column is sorted or it is handed another click handler.
01ad747 read the sort direction in the menu effect's dependencies, whatever the column: a table that does not sort hands its header cells no sort data, and every such table threw "Cannot read properties of undefined (reading 'direction')" (42 table unit tests). The cell now reads what it is told of the sort only when it is told it: whether it is the column sorted by, and its direction, both of sort data that may be absent. Unit: tableHeaderCellKeepsItsControls - a cell with no sort data is drawn, and drawn again (fails on 01ad747).
…f under its header, as it says (qorus#646) Foxhoundn's qlip review of Panel · Sticky Header Above Its Body: the story did not show what its description says. Its play measured each row under the header by scrolling there and back (findDrawnOverStickyHeader restores the scroll), so the frame was the panel unscrolled, with nothing under the header. The play now ends scrolled with the raised row's middle at the stuck header's bottom edge, and checks that where they overlap the header is what is drawn. The description says what the frame shows.
… it says (qorus#646) Foxhoundn's qlip review of Breadcrumbs · Folded Trail Opens At A Tap: the story did not show what its description says. Its play tapped each fold open and then "Back to chats", which closes the menu, so the frame was the two folded trails with no menu. The play now ends by tapping the folded current crumb open again and checking its menu: Workspace, "Back to chats" and the chat named in full, the crumb saying it is expanded. The description says the story ends there.
…tainer is a phone's width or narrower (qorus#646) Foxhoundn's qlip review of Entity Row · No Wrap, Severity Row · Narrow Container Actions Stack and Severity Row · Narrow Container Lone Action In Menu Mode On A Phone: the lone action should fill the row's width, the easiest target there is (Fitts's law), on a phone and in a tiny container on a desktop; on a phone, a 'menu' row's lone action goes under the text. David: one rule, keyed to the container's width, not the screen's. - In a container a phone's width or narrower (480px, Reqore's phone breakpoint, measured on the row's container), stacked actions and a lone action under the label are the row's width - on a phone, or in as narrow a container on any screen. In a wider container each is as wide as its label, as before. The rows asked the screen (isMobile) before; they ask the container now. - A 'menu' row's lone action stays at the end of the label's line only while its container is wider than a phone; as narrow as a phone it goes under the label and spans the row (replacing ec7fff3/1e35fc08 there). An entity row is narrow only at a phone's width, so its lone action is under the label wherever it is narrow; a severity row (narrow under 640px) keeps it at the line's end between 480 and 640px, saying its shortLabel there. useNarrowContainer reports both answers from one measurement (its breakpoint, and the phone's width). The stories check widths against the same measure (rowContainerIsPhoneWidth), and two new ones show a container just wider than a phone keeping label width: Entity Row · Lone Action Keeps Its Width Just Wider Than A Phone (500px) and Severity Row · Actions Keep Their Width Just Wider Than A Phone (560px). Entity Row · Lone Action Spans The Row On A Phone shows the default ('row') on a phone. Fail-first: with the hook and both rows as before, the new unit cases fail (3: rowActionShortLabel x2, SeverityRow x1), as do 6 Entity Row stories and 3 Severity Row stories. Precheck: 153 files, 1758 tests.
…ere the keys went back (qorus#646) 09e1267 highlighted the row a level came from on any way back - also after a click on the back button or on a crumb. To a pointer a highlighted row looks picked: Dropdown · Back Buttons Work drew its "Test child 3" in the intent's full fill after a click back, and Items Can Be Traversed Via Tags its "Test" row. The highlight answers the keys (Foxhoundn: can the arrow keys go on?), so only the left arrow brings it back; a click back highlights nothing, as before. The list's way back says whether the keys took it (_onBackClick(true) from the left arrow; the back button has its own click handler). Fail-first: dropdownBackHighlightsWhereItCameFrom "highlights nothing after a click back" fails on 09e1267's list (the right arrow opened Carrot); 3 of 3 pass. The Dropdown story file passes 32 of 32, its frames as before but for Keyboard Navigation With Left Arrow Navigates Back.
…shows, a lone action spanning the row with its full label (qorus#646) Since 2f66642 an entity row is narrow only where its container is a phone's width or narrower, and there its lone action goes under the label, the row's width, saying its full label: it draws a short label nowhere. Entity Row · Narrow Container Lone Action Short Label no longer showed a short label. David: rename it to what it shows; short labels stay covered by the severity-row and folded-column stories. Entity Row · Narrow Container Lone Action Spans The Row With Its Full Label: the same three rows in a 720px and a 400px container. Its play now also checks that each action in the 720px container is at the end of the row, beside the label, as wide as its label, and that the first action has a short label it does not say. Fail-first: with the rows' sources of 0820cbf the story fails (the action at the end of the label's line, 310 < 329.5); with 2f66642 the EntityRow story file passes 35 of 35, and the renamed story's frame is the old story's, pixel for pixel.
# Conflicts: # package.json
davidnich
added a commit
that referenced
this pull request
Oct 21, 2026
… (qorus#646) The functional PR's answers to the 8 rejections on its qlip build #410: the dropdown's way back highlights the row it came from, a list never runs off the screen, the panel and folded-trail stories end on what they say, and a lone or stacked row action spans its row where the row's container is a phone's width or narrower. Merged, not rebased: this PR is open. Two conflicts, both this branch's re-indentation of a block the functional branch changed (EntityRow's actions, the popover's surface): this branch's version, with the functional change made in it.
Member
Author
|
Foxhoundn's 8 rejections on qlip build #410, each fixed, pushed in ccf1c85 with develop merged in (#721, #720) and the version at 0.78.10. Each rejected frame also has this answer as a comment in qlip. The new frames are on qlip build #412; every changed and new frame there is flagged with its cause, none accepted.
|
…nd only its rows scroll (qorus#646) Foxhoundn on qlip build #412 (RichTextEditor · The List Opens In A Short Window, and On A Phone In Landscape): "Where is the bottom border of the popover? It still seems its being cut off." 7b71340 held what a capped popover draws inside its cap by scrolling the whole surface. The Dropdown's panel inside stayed as tall as before: at 980x380 the surface is 297px (y 63 to 360) and the panel 382px, its bottom border at y 445, scrolled out of sight under the surface's edge, and its rows scrolled inside that again: two scroll areas. - A capped surface is a column that holds what can scroll its own content - a panel (its body scrolls) or a menu - to the cap: it shrinks to it and keeps its whole frame. Anything else keeps its height, and the surface scrolls it, as before. - The Dropdown's panel lays its body out as a column, and the list may shrink in it (min-height 0), so its rows, the menu, take the room its search and tags leave. Only the rows scroll. Measured after: the panel is 63 to 360, its body 295px with nothing to scroll, the rows 215px scrolling 775px. Fail-first: both list stories now check that the panel's whole frame, bottom border included, is inside the surface and the window, and that one place scrolls, the rows. Before: "expected 445 to be less than or equal to 360.5" (and 370.5 in landscape). After: the TemplateFieldUX file passes 13 of 13.
…e by side, stacking only at a phone's width (qorus#646) Foxhoundn on qlip build #412 (Severity Row · Actions Keep Their Width Just Wider Than A Phone): "These should be rendered next to each other, while keeping their widths - law of proximity. They should only fold under one another on phone / very narrow container size." David: side by side, wrapping if needed. A severity row is narrow below 640px of its container, and its default narrowActions='stack' put its actions one to a line anywhere there. Now 'stack' stacks them, each the row's width, only where the container is a phone's width or narrower (480px). Wider, they sit side by side under the label, each as wide as its label, onto another line only where they do not fit, as with 'row' (data-actions='row'). An entity row is narrow only at a phone's width, so it does not change. Docs: SeverityRow's narrowActions, TReqoreNarrowRowActions, COMPONENTS.md (and the rows' container rule there is 0.78.10, the version #722 ships as, not 0.78.9). Fail-first: - SeverityRow unit: the default and 'stack' at 600px are side by side ('row') and at 480px stacked; one action at 600px is in a row in every mode but 'menu'. 2 of 2 fail before. - Severity Row · Actions Keep Their Width Just Wider Than A Phone: a 560px box with two actions on one line, a 500px box whose three actions wrap onto two lines, each as wide as its label, and a 480px box stacked, each the row's width. Before: "expected 'stack' to be 'row'". After: SeverityRow unit 35 of 35; the SeverityRow story file 28 of 28.
…, so a short label or a label is never cut to "T…" (qorus#646) A narrow table folded its actions column to the menu button's square (38px), whatever its cells drew. That suits a menu or an icon, not words: a row's one action with no icon says its short label there (2610960), and "Test" was cut to "T…" - the cell's text read "Test" while the screen showed one letter, so the unit test that read the text passed. An action with no icon and no short label was cut the same way ("Test the connection" to "T…") since the fold began. David: a folded column is as wide as what its cells draw, never wider than unfolded - the square for a menu or an icon, an action's words whole in their button: its short label, or its full label where it has none, even if the column barely narrows. - getFoldedActionText: what a folded cell says, as the row draws it - nothing for a menu or an icon, else the short label or the label. - useFoldedActionWidths: over the table's rows (so the width holds as they scroll and page), each folded column's distinct words, measured with the browser's text metrics (canvas measureText, as useFitTextSize does) in the font of a button drawn as the cell draws it, plus what that button draws around its words (padding, border, spacers). The button is a probe out of sight in a box of no size that clips it, so it never reaches past the table or makes anything scroll. Measured again when the words change and on document.fonts "loadingdone" (no polling). Words that cannot be measured (an element, no canvas) keep the column its width. - foldActionsColumns takes the widths: the square at least, never wider than the column was, plus a padded cell's padding. - hasRowActionLabel moves to a module of its own (re-exported as before), so the table's helpers use it without loading the menu's components (an import cycle through Dropdown and Paging). Docs: the cell.actions and actionsMenuBelowWidth props, COMPONENTS.md. Tests, failing first with the table's sources of 9ca09c1: - rowActionShortLabel: the rendered cell width against the measured words (7px a character, 18px around them): 4 of 6 fail before (a short label, a full label, the cap at the column's width, words that cannot be measured); an icon in the square and a wide table pass, as before. - helpers/table: getFoldedActionText, getFoldedActionTexts and the folded widths (the square, the cap, Infinity, padding). - Stories Collections/Table · Folded Column Says The Short Label and Folded Column Says The Full Label Without A Short Label, at 720, 480 and 265px: the words whole and in sight inside their button, not cut to an ellipsis, in a column no wider than they need; their label the accessible name (and the tooltip of a short label); a click runs it. Both fail before (the text 27px in 20, and 131px in 20).
…722 ships as (qorus#646)
Foxhoundn
previously approved these changes
Oct 21, 2026
…a memoised group, and plays read the page through requiredElement (qorus#646) DeepScan on 1a95b75: 1 new issue. Its dashboard needs a login, so the new code since ccf1c85 was read for its rule set: - SeverityRow: actionsStacked ended in "&& !loneActionAtLineEnd", which is always true there - it already requires isPhoneWidth, and a lone action is at the line's end only while the container is wider than a phone (CONSTANT_CONDITION). The clause is gone; nothing changes. - Dropdown list: the style handed to its memoised control group was a new object on every render (REACT_INEFFICIENT_PURE_COMPONENT_PROP); it is one module constant now. - Table helpers: getFoldedActionText indexed actions[0] after an optional chain; it destructures the one action instead. - The new Table and Severity Row plays read their boxes through requiredElement instead of a non-null assertion or a cast (NULL_POINTER), as the rest of the stories do. No visual or behaviour change: precheck 153 files / 1769 tests; the Table, SeverityRow, TemplateFieldUX, Dropdown and EntityRow story files pass 182 of 182 and their 180 captures equal 1a95b75's.
Member
Author
|
Foxhoundn's 3 rejections on qlip build #412, each fixed (pushed in 1a95b75; the frames are on qlip build #413, approved by David). Each rejected frame also has this answer in qlip.
|
Foxhoundn
approved these changes
Oct 21, 2026
davidnich
added a commit
that referenced
this pull request
Oct 21, 2026
davidnich
added a commit
to qoretechnologies/toolkit-react
that referenced
this pull request
Oct 21, 2026
… on its row again, as reqore keeps them in sight (qorus#646) 21d2718 moved reqraft to reqore 0.78.5, where a row narrower than 480px folded two or more actions into one "Actions" menu, and the experiment card's stories opened that menu to read Show analytics, Accept and Remove. reqore 0.78.9 (qoretechnologies/reqore#722) keeps a narrow row's actions in sight by default (narrowActions='row'), which answers Foxhoundn's qlip rejections of those frames, so the stories and their descriptions go back to what they were before 21d2718: each version's actions are buttons on its row.
davidnich
added a commit
to qoretechnologies/toolkit-react
that referenced
this pull request
Oct 21, 2026
…m button by the name reqore gives it (qorus#646) reqore 0.78.9 (qoretechnologies/reqore#722) names an icon-only button by its string tooltip, so the compact field's confirm button (the + icon, tooltip "Add item") is now a button called "Add item". Compact Empty asserted that no button had that name, which was only true while the confirm button had no name at all. Its intent is that the disabled confirm button is the one way to add an item: the play now checks that it is the only button called "Add item", and that it is disabled. Fail-first: with reqore 0.78.9-pr.722.g204dfc7 the old play fails ("expected document not to contain element, found <button aria-label="Add item" ...>"); the story file passes 9 of 9 with this one, its captures unchanged.
davidnich
added a commit
that referenced
this pull request
Oct 24, 2026
…se they shipped in (qorus#646) 0.78.7 was never published (npm has 0.78.6, then 0.78.8). Fourteen labels in COMPONENTS.md named it: the button's width cap, the checkbox's wrapLabel, the columns' and the collection's width caps, the wrapped control group's child cap, the dropdown's opened level, the entity and severity rows' narrow actions and end-of-line action, the row actions' shortLabel, the panel's sticky header, and the table's pinned header cell. Each was introduced by a commit of #722, whose merge into develop (9c340d9) published 0.78.10, the first release that holds them (found with `git log -S` on each label and the first develop merge containing it). The pinned header cell's sentence also says what #723 adds: it paints what the header row is drawn on and hands that on (0.78.13).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The functional fixes from #717 that reqraft (qoretechnologies/toolkit-react#137) and the Qorus IDE depend on, as their own PR (qoretechnologies/qorus#646). The front-end team asked for the contrast changes to be kept apart, so they are in a stacked PR of their own (#723, based on this branch). #717 will be closed with a note linking both.
Version: 0.78.10, develop's next version (develop took 0.78.8 with #721 and 0.78.9 with #720, both merged into this branch). #723 is 0.78.10 too; whichever merges second merges develop and bumps again only if its version is on npm by then.
How this branch was built:
developat 0183173, where fix: text and icons read on what they are drawn on; reduced motion (qorus#646) #717 branched, 27 commits of fix: text and icons read on what they are drawn on; reduced motion (qorus#646) #717 were cherry-picked in their order, and 3 commits were split, with only their functional part here.wrapLabelstories' fix from David's review. Each story's second checkbox, kept on one line on purpose, ran past its 160px box; it now wraps with a short label, and every play checks that each checkbox, its box and its label end inside the box.lift, 0.78.8) and chore(deps): clear the last two Dependabot alerts (esbuild, @babel/core) — 0.78.7 #720 (vite 8.3.5, esbuild 0.28; 0.78.9). Only the version line conflicted, in the first. ccf1c857 is the version.Fixes, each with the test that fails without it
The test counts were run on this branch. "Before" means the fix's own source change taken back, with its tests and stories kept.
Rows (EntityRow, SeverityRow, RowActionsMenu), Table:
ReqoreEntityRowkeeps its actions as buttons under the label (narrowActions='row', the default); the menu fold is opted into with'menu'.narrowActions='stack', the default, they stack, each the row's width, only at a phone's width (480px) or narrower. Between 480 and 640px they sit under the label, each as wide as its label, wrapping only when they don't fit, as with'row'.'menu'mode, a lone action stays at the end of the label's line while its container is wider than a phone; at a phone's width it goes under the label and spans the row (2f666426).shortLabelwhere it is drawn at the end of the label's line: a severity row in a container between 480 and 640px (an entity row is narrow only at a phone's width, where its action spans the row and says its label), and a table's folded column.Widths:
maxWidth. Unit: 3 of 6 fail before; the 3 that pass are the cases that do not change. Stories: Button › Fixed Button Stays Inside Its Container, and its phone story.Controls:
open()control opens a popover that is closed now, as itsisOpen()says (204dfc79, new since the first push). With this PR's first prerelease, reqraft's creatable value field lost its list (its unit testformEngineCreatableAllowedValuesfound no "Create new"). The list had closed itself before the popover rendered again, and the field'sopen()read the render's "open" and did nothing whileisOpen()said closed. The refs fix above made that close come sooner; the controls' disagreement was already in develop. A trigger's own click still goes by the popover as it was when the click began. Unit (popoverControlsAnswerNow): 1 of 9 fails before; reqraft's test passes 3 of 3, and reqraft's 2826 unit tests pass.wrapLabelwraps, and the checkbox is as high as it. Unit (checkboxWrapLabel): 3 of 5 fail before. Stories: four, in Checkbox; their boxes are drawn so that every label in them ends inside (c16f4da2, from David's review).clearLabel). Unit (inputClearButtonName): 3 of 3 fail before.requiredElement,requiredAttribute).Stories
Story links go to qlip build #413, which David approved. Every story was approved by David again on this branch, against develop rather than against #717, and again after Foxhoundn's 8 rejections were fixed.
New stories (31), each failing without its fix:
'menu'mode, one action in a 400px container (a phone's width or narrower) goes under the label and spans the row; on a phone it says "Test the connection" (2f666426)shortLabelwrapLabelin 160px boxes, and settings on a phone'row'mode on a phoneExisting stories that change against develop: none now. Before Foxhoundn's review, 11 did (stacked and lone row actions as wide as their labels, the Panel title action, the Dropdown left-arrow frame). Since 2f666426 and 1d133cbb, all of them match develop's frames again. Examples are Entity Row · No Wrap, Severity Row · Narrow Container Actions Stack and the Dropdown frame (0.00% in qlip build #412).
qlip build #413 (1223 snapshots), approved by David: 1 changed and 5 new. The changed frame is Severity Row · Actions Keep Their Width Just Wider Than A Phone. The new ones are the two Table stories and the three fixed rejections from build #412 whose rejected frames left no baseline.
Noise, not from the change:
Gates
open()control): 1 of 9;0.78.10-pr.722.gccf1c85: precheck 278 files, 2828 tests; story run 767 passed, 0 failed. Its frames are unchanged apart from 1px of noise. It moves to the released 0.78.10 once this PR merges.Classification of #717's commits
Classes: F functional (this PR), C contrast (#723), M merge (not picked), S split in two. The "Now" column is the commit on this branch (F) or on #723's branch (C).
ReqoreSurfaceProvider: the foundation of the contrast workaria-disabledto disabled non-form components, which WCAG ties to the contrast exemption for disabled content. I kept it with C because it changes no layout or behaviour that reqraft or the IDE rely onReqoreTaga memo component and fixes crashes. The crashes are in code the contrast pass added, so this stays with CReqoreTaga memo (DataView, the dropdown's trail tags, SegmentedControl's More button)helpers/surface.ts, which C usesisolation); a pinned header cell is opaque and above what scrolls under it; the stories and stickyLayers test. C part: the pinned header's fill read from the handed-down surface (getThemeSurface), and theReqoreSurfaceProvideraround the cell. The dry run fails to type-check on exactly these two importsshortLabel(David's rule)🤖 Generated with Claude Code
https://claude.ai/code/session_01EVRGWEwQAi5btU1qdHBfmb