Repository navigation
Conversation
|
📦 Prerelease published for this PR: Install it in a consumer (updates yarn add @qoretechnologies/reqraft@0.10.65-pr.137.gfb70c1cPublished under the |
qlip visual regression🔍 446 changed, 22 new of 806 snapshots. |
|
Qlip build 20261007-114032: the 12 pending snapshots, for review. I have not accepted, flagged or denied anything; this only explains each one. I compared each changed snapshot's baseline and new image around its diff regions. New stories (2, no baseline):
Operand message wording (6, ExpressionBuilder): Add Value Slot, Add Value Slot While Incomplete, Addition Picked Fresh, Variable Arguments Can Be Added, Discard All Incompatible Function Args, With Arg With Allowed Values.
Card header centred (1, FormEngine › Compact Expressions): the open card's one-line heading, "Condition", now sits about 4px lower, centred on the actions beside it rather than lined up with their top (CompactRow.tsx). "Moves to" note (3, ExpressionField): Field Menu Stays In The Toolbar, Offer Survives A Compact Row, Text View Survives A Late Host Change. Each edits an expression row whose heading changes when it closes, so its open editor now shows |
|
Qlip build 20261007-164411 (f7424c7): 13 pending, explained, not reviewed. I have not accepted, flagged or denied any of them.
|
|
Qlip build 20261007-211708 (4e406da): 34 pending (22 changed, 12 new), explained, not reviewed. I have not accepted, flagged or denied any of them. I compared each changed snapshot's baseline and new image around its diff regions, and looked at every new one. Already explained above, unchanged (11):
Their images and diff percentages are the same as in the builds those comments describe. A template in a field that takes custom values is now a chip in a text field (section 7 of the description), changed (13):
New stories (10):
Gone since build 20261007-205028 (9). That build also showed text operands narrower than 150px (Accept All / Accept Incompatible Function Args Selection, Focused Editing, Groups Can Be Deleted, New Groups Can Be Created, With Complex Value, With Injected Extra Actions, With Simple Value, Nested Operand Keeps Injected Actions). That was a side effect of 1fabea9 (it removed the 150px minimum in every row, not only in a narrow one), fixed in db8c47c, and those snapshots are unchanged again. |
|
Qlip build 20261008-041002 (b7f95b8): 39 pending (22 changed, 17 new), explained, not reviewed. I have not accepted, flagged or denied any of them. 33 are as in build 20261007-211708, with the same images and diff percentages. They are explained in my comment on that build above. 6 are new or different (section 8 of the description):
|
|
Qlip build 20261008-053829 (6303b15): 43 pending (22 changed, 21 new), explained, not reviewed. I have not accepted, flagged or denied any of them. 38 are as in build 20261008-041002, with the same images and diff percentages. They are explained in my comments above. 5 are new or different (section 9 of the description):
|
|
Qlip build 20261008-065308 (bc24dc0): 44 pending (22 changed, 22 new), explained, not reviewed. I have not accepted, flagged or denied any of them.
|
…kes it text, and to delete it qlip build 20261008-083118 (Foxhoundn, on Whole Number / Number / True Or False / Date With Text Around A Template and Host Reason In Place Of The Field's Own): "How does the user fix this? Why has this happened?" "A whole number can't have text around a template" said what was wrong but not what caused it or what to do. The message now names the text that makes the value text, says it is therefore not of the field's type, and says how to fix it: - "Stk." makes this text, not a whole number. Delete it to keep the template alone. - text on both sides: "ca. … Stk." makes this text, ... - several templates: Templates side by side make this text, not a number. Keep one of them alone. (with text too: Text and more than one template make this text, ... Keep one template alone.) The value stays invalid, as decided on qorus#646; only what is said changes. The Host Reason story's host gives an actionable reason of its own, as a host's reason should. Tests: textAroundATemplate (the reason for each type, text before and on both sides, several templates, textAroundATemplate itself; fail before), hostInvalidReason follows the new wording.
… - its end frame shows nothing
qlip build 20261008-083118 (Foxhoundn): "It doesn't say anything in the story
- if it says that during the play test, then remove the snapshot, as it
doesn't provide any important info."
The note it asserts is shown only while the field is open, and the play
closes it, so the capture is an ordinary filled row. The story keeps running
in the storybook tests; only its capture is skipped (qlip: { skip: true }, as
Compact Row Read Only Is A Way In).
…y its position too
qlip build 20261008-083118 (Foxhoundn, on ExpressionBuilder Add Value Slot,
Add Value Slot While Incomplete, Addition Picked Fresh and Variable
Arguments Can Be Added): "the old version, which provided the argument
index / position is a better UX for the user to understand which "value" is
the message mentioning. This does not say what is exactly wrong".
Every operand of a call taking any number of them is named "Value", so
"Enter a value for "Value"" did not say which one. Where another operand of
the call has the same name, the message says which by its position too, as
the editor shows them left to right:
- Enter the 4th "Value"
- The 2nd "Value" is invalid: ...
- The expression in the 2nd "Value" is invalid: ...
A name that says which ("Lower bound"), or a lone operand, is said by its
name alone, as decided in David's review of qorus#646.
Tests: operandPosition (fail before).
… the first field edited qlip build 20261008-083118 (Foxhoundn, on ExpressionField Field Menu Stays In The Toolbar, Offer Survives A Compact Row and Text View Survives A Late Host Change): 'Moves to "Set" when you close it (✓)' "does not need to be shown everytime, the user will learn what happens after the first time ... otherwise it just takes vertical real-estate." David's decision: once per form, on the first field the user edits. The note is said on the first field held open in its box, and on no field once that one is closed; while two are held (expandMode multi), only the first says it. Tests: movesToNoteOncePerForm (fails before). Story: Compact Row Says Where It Moves fills a second field after the first is closed; it is held as the first was and says nothing.
#137's one patch bump moves to 0.10.62.
|
Qlip build 20261008-083118 (5fa0a45): the 16 rejections, answered. Pushed as 5fa0a45..4ac092c. I have not accepted, flagged or denied anything. ExpressionBuilder › Add Value Slot, Add Value Slot While Incomplete, Addition Picked Fresh, Variable Arguments Can Be Added - "the old version, which provided the argument index / position is a better UX ... which "value" is the message mentioning".
ExpressionField › Field Menu Stays In The Toolbar, Offer Survives A Compact Row, Text View Survives A Late Host Change - 'Moves to "Set" when you close it' "does not need to be shown everytime ... only ... on the first field".
FormEngine › Compact Row Says Where It Moves - "if it says that during the play test, then remove the snapshot".
FormEngine › Whole Number / Number / True Or False / Date With Text Around A Template - "How does the user fix this? Why has this happened?"
FormEngine › Host Reason In Place Of The Field's Own - same question.
FormEngine › Lone Template Written Is Stored Bare, Saved Template Opens In The Text View (+ On A Phone) - "The template button should be the same size as the input, and preferably in a stacked group".
Other changes in this push:
|
…0.61); this PR is 0.10.62
…sion · Visual, or Value · Template (qorus#646) One value, three views. Value to Expression writes the value as expression text; Expression to Value converts a lone value or template, and otherwise says it cannot show the expression and offers to replace it. An expression typed on Value moves to Expression, with an undo. Saved values open on their tab. A template of another type is a warning. Without a language server, Expression moves to Visual, which starts from value(...) as the server parses a value written alone. The menu keeps value actions only. Fields outside a form's options keep the behaviour they have.
|
Qlip build 20261008-123352 (bf948c6): 26 pending, 3 carried-forward rejections, explained, not reviewed. I have not accepted, flagged or denied anything. CI passed (Pull Request Tests, Publish). Build 288's 16 rejections:
The other 17 pending are the revert putting back bc24dc0's images, identical to build 20261008-065308, explained above:
|
|
qlip build 20261008-124320 (86641aa, the value tabs): what changed against bf948c6's build (20261008-123352) Compared image by image with bf948c6's build. Everything below that build already had pending (the written-as-text and text-around stories, Template Value, Number Typed Freely, Shows Templates List, Chip Of Another Type, and the two denied ExpressionField stories) has the same diff as there, so those explanations still apply. What 86641aa changes: New stories (13), the tabs themselves. Value Tab Per Option Type; Value To Expression And Visual (+ On A Phone); Saved Template Opens On Value; Saved Template On A Yes No Opens On Expression; Saved Text With Templates Stays Text; Saved Expression Opens On Expression (+ On A Phone); Template Only Option Has Value And Template (+ On A Phone); Expression Typed On Value Switches With Undo; Template Of Another Type Warns. Each end frame is the state its description names. Value To Expression And Visual On A Phone ends on the Visual tab showing FormEngine stories that now show tabs in an open option (11). Basic, Small, Focused Editing, Description Is Shown, With Types Shown, Row Actions Stay In The Row, Optional With Values, Optional Opened, Invalid Shown Only, Compact Field Types Editing, Option With Any Type. Each option that takes templates has Value · Template in its header row (none of these options take expressions). Where the field's ⋮ held only the old mode switches (Use Template, Use Custom Value), it is gone, because the tabs are the mode switch. Examples are the colour, file, yes/no and date options in Basic and Invalid Shown Only. An untyped option keeps its ⋮ with the "set a value of this type" choices: Option With Any Type ends with an empty option set to Boolean from that menu. Option With Any Type also picks a rich-text template on the Template tab, which turns the value into rich text in its own editor, as before. ExpressionField.
ExpressionBuilder, narrow column (Nested Fits Narrow Column, Operands Fit Narrow Column, 0.927% → 0.938%). The operation picker's count reads 16, not 15: |
…ere not (qorus#646) valueTabsLabels: the tabs, their tooltips, the cannot-show and replace text, the conversion warning and the undo, with English defaults; a label that holds a value is a function of it, so a host keeps its language's word order. A tab is identified by its key (data-tab), not by its label.
… numeric keyboard (qorus#646) As its own input was: inputmode numeric for a whole number, decimal for a number. Templates are picked from the list the field opens, and an expression is written on the Expression tab.
…s position (qorus#646) David: an operand of a call with an "Add value" slot is said by its position even when it is alone - "Enter the 1st \"Value\"" - as the operands that join it will be. The position is said when another operand has the same name or the call is variadic.
…ext and centred on it (qorus#646) The picker took the form's size (32px in a small form) beside the DPQL editor, which is drawn at the normal size (38px) in any form; it takes the editor's size. And the editor had a 48px floor of its own: an empty strip under its 38px text that nothing beside it could match. It has none now - one line as tall as its text, growing with its lines.
…at() (qorus#646) Value to Expression wrote an empty value as the text joined from no parts - concat() - and what was typed then went after it.
…omes from (qorus#646) savedValues: the field's ⋮ offers "Save this value", which hands the value and the field type to the host (it asks for a name and keeps it), and "Use a saved value", which lists the values saved for the field's type; one is the value at once, on its tab, and can be removed as the host keeps it. Not offered for an empty value, a yes / no, an expression, fixed choices or a read-only field. In a form row the actions join the row's ⋮; elsewhere the field's own. templateBadge: the badge a template shows in the template picker - where it comes from, as the host knows. Tests that waited a fixed time now wait for the field's menu to be published.
|
qlip build 20261008-142953 (c780b93): what changed against 86641aa's build (20261008-124320) This covers 07e5333, 5da9d74 and c780b93 (sections 12 to 14 of the description), compared image by image with 86641aa's build. Everything not listed here has the same diff as there, so the earlier explanation still applies. New stories (3). FormEngine › Saved Values In The Field Menu (+ On A Phone) and Template Badge From The Host. Saved Values ends on Quantity holding 48, after "A pallet" was picked from "Use a saved value". Template Badge ends on Source's Template tab, its picker reading The Text view is 10px shorter (2aa29bd). The DPQL editor had a 48px floor below its 38px text: an empty strip that nothing beside it could match. It has none now, so whatever sits under the Text view moves up by that strip, and nothing else changes.
Addition Picked Fresh (0.0528% → 0.0509%). The lone operand of Addition, which can take more operands, now says its position: |
… value (qorus#646) Found clicking through the review in the IDE: a field picked on a value's Value tab, then the Expression tab opened straight away, was lost - the value was left empty. The text field sends an edit 100ms after it is made, and it went away first, with the edit. It sends a waiting edit as it goes. And the template field reported that text with the expression flag of whichever tab was open by the time it arrived - so the pick, and a template chosen from the saved values of a yes / no, was stored as an expression. On the tabs that text is the Value or the Template tab's: a value.
… badge (qorus#646) reqore's rich text editor draws its chips as compact badges (`asBadge`, `compact`) unless told otherwise: a reference in an expression or a rich text field was a rounder pill (7px corners) beside the tags (3px) of the read row and the read-only chip. Foxhoundn on qlip #392: "Some template chips are pills, some are badges - let's unify them please". REFERENCE_CHIP_PROPS, which every view draws its chips with, now says `asBadge: false` and `compact: false`. `expectOneChipShape` (src/stories/chips.ts) checks a story's chips share one shape; FormEngine · Expression Typed On Value Is Offered With Undo uses it and fails with the badge (7px against 3px).
… under the chip clicked (qorus#646) A mouse's hover was drawn as glass (a flat, transparent popover with a backdrop blur): over an empty page it was its words with nothing under them. Foxhoundn on qlip #392: "It looks like the background is transparent? Is it not using a ReqorePopover / the tags tooltip?" It is now the popover's card, the surface a chip's hover and Details are drawn on. A chip's list was anchored at the chip, then moved to the caret when its request went out: the re-measure meant for typing ran for a chip's request too, and the click left the caret after the chip. Foxhoundn on qlip #392: "The opened list is not under the 2nd chip". A list opened at a chip stays under it, and a list put away is at no chip from then on. DpqlEditor · Hover Says What A Function Is and Hover Over A Chip Says What Its Path Is check the hover's card is opaque; A Click On A Chip Opens Its List And A Second Closes It checks the list is under the chip clicked, from its left edge. All three fail without the fix.
… on its name's line, and an open row with less to read (qorus#646) Foxhoundn's qlip #392 review: - "please right align the question mark to get rid of the stair-case look": in a row's label column the (?) is at the column's right edge, on the name's first line, the same x in every row. - "The required asterisk on a new line is not good, keep it on the same line as the field name": the column counted the help as the 12px icon it was before it became a button (26px, 32px for a finger), so every name within 14px of the column's width put its asterisk on a line of its own. The column is now the widest name plus its chrome at their real widths (`labelAffordanceWidth`), never narrower than the 152px it always was (`LABEL_COLUMN_MIN`: the floor is the column's, so a form of short names keeps its column), and a word joiner holds the asterisk to the name's last character, keeping the name one text for anything that finds it by its words. - "Remove the whole `Moves to` please, move the `Use as an expression` to the right of the field": an open field held in its box no longer has a note saying where it moves when closed, and an expression found in what was typed is offered to the right of the field, on its line, under it only where the row has no room for both. The stories check it (src/stories/labelLayout.ts): Compact Option Depends On Option In Required Group and Or Another Option, Canceling An Edit That Made An Expression Puts Back The Value, and Expression Typed On Value Is Offered With Undo fail without the fix; Compact Row Says Where It Moves is Compact Row Stays In Its Box While Open, and its unit test says nothing is said (__tests__/heldFieldSaysNothingOfItsMove.test.tsx). The rules are in design/FORM_ENGINE_COMPACT.md.
…revert restores (qorus#646) FormEngine · Reverting A Removed Expression Puts It Back held pos + 1 again after the revert, and its Expression tab showed an empty editor. Removing the value emptied the text and left the visit marked as written, so the seeding that writes an expression into the editor never ran for the one the revert put back. An emptied editor is unmarked: a revert or an undo is written into it, as on entering Text. The play checks the editor shows pos + 1, and fails without the fix (Foxhoundn denied this frame on qlip #383; it showed the bug). The Compact Row Cancel Edit plays wait for the row: in Storybook's UI a play starts as soon as the story mounts and the form draws its rows a moment later, so the click went to nothing and the play failed there while passing in the test runner.
…UI too (qorus#646) A scan of every story served in Storybook's UI found plays that pass in the test runner and fail there: - taking a dispatched mouse off the page did nothing (there is no browser mouse to move), so a hover the play waited to close never did (FormEngine · Compact Expression Template Chips). It now sends the out and leave events from what is under the point, taking the elements left before telling any of them. - Sticky Headers On A Phone scrolled before the form had measured itself and taken the narrow layout; it waits for that layout. - Only One Box Header Is Ever Pinned: at a 1080px screen the scroll ended before the last box reached the line it pins at. The last box has 30 rows, longer than the scroller at any screen height.
…e the session can (qorus#646) The editor asked for a hover only as the pointer moved. A read-only summary drawn as a row closes connects its session after it is drawn, so a mouse already resting on a word there showed nothing until it moved again: FormEngine · Compact Expression Template Chips failed in Storybook's UI, whose play rests the mouse once and does not move it. The hook keeps where a hovering pointer rests until it leaves, and asks for its hover when the session comes to answer, once. __tests__/smartEditor/hoverOfAMouseAlreadyResting.test.tsx: answered once the session can, without moving (fails without the fix); not after the pointer left; asked once while the session stays ready (fails without the fix).
|
Foxhoundn's 10 denials on qlip #392, answered (pushed in b62c14a; new frames on build #393):
|
The ticket stories date their messages from a fixed instant, but the components write each date relative to the real clock: TicketThread through ReqoreTimeAgo, TicketReferences through epoch-timeago, both reading Date.now(). A capture read "1 month ago" in one build and "3 months ago" a few weeks later, and qlip reported frames nobody had changed. .storybook/preview.tsx now starts every story at STORY_NOW (30 June 2026, 12:00 UTC, src/stories/storyClock.ts) before it renders, and puts the clock back after it. Components still read the platform's Date; only a story changes it. - The clock runs on in real time from STORY_NOW. A stopped clock stops lodash's debounce and throttle (ControlGroup's resize, Slate's selection sync), so it is not the default. - parameters.mockdate stops the clock at an instant, as it did for the Log stories, and a live story keeps the real clock. - When a story ends, the preview drops the ReqraftQueryClient answers already stale for the request that fetched them. The cache stamps each answer with the time it was fetched, and the clock takes the next story back to STORY_NOW, so the answer read as fetched in the future and was served instead of the story's own mocks (the useStorage stories failed, and useFetch's 409 now and then). Answers still fresh stay, as on a real clock: emptying the cache instead fails four FormEngine stories that only pass after an earlier story has fetched the type catalogue. A request still in flight is left to finish; removing it cancelled the Log stories' current-user request and failed Log/Filterable. - This replaces @netsells/storybook-mockdate. Its decorator put the real Date back on every render and drew a "Mocking date" label in the capture host's time zone, as a keyless list (a React warning in every Log story). - The ticket stories date their data with storyMinutesAgo(); TicketConversation had literal July 2026 dates. - The capture browser runs in UTC and US English, as CI does, in the desktop and the phone projects alike, so dates written in the viewer's zone or language (log timestamps, the StructuredDataView timestamp, date fields) capture the same on every machine. storyTimesIgnoreTheRealClock renders every TicketThread, TicketConversation and TicketReferences story through the project's annotations with the machine clock five years apart; storyClock tests the clock itself, and storyQueryCache that the next story is served what a real clock would serve it.
The Log put its filter in the panel's title bar as a responsive action, and a responsive action that does not fit is folded into the bar's overflow menu. On a phone the filter, a text box, was the one that did not fit: it was only reachable behind an unlabelled menu button, and Log/Filterable's play could not find it at 390 px. The Log's title-bar controls (the filter, Pause, Clear, Scroll to bottom) no longer fold: the panel gives way on the title instead, and the filter narrows (162 px at 390, 92 px at 320). The desktop bar is unchanged. The message actions (copy, delete) and the send box are not title-bar actions and were already in view at 390 px. Log/Filterable On A Phone runs on the phone (touch project, drawn at 390 px from the start): the filter, Pause and Clear sit inside the title bar, a finger taps the filter, and "error" leaves the ERROR line. It fails on the old Log, which has no filter in the bar at that width.
The query cache lives as long as the page, so a story was served what an earlier story had fetched, and could pass only because of it: on develop, four FormEngine plays (Compact Row Cancel Edit and its Affordance, Picking A Rich Text Choice Shows Its Name, Sticky Headers On A Phone) failed when the story was opened on its own, cold. This branch already waits for the form in each of them, and all four now pass opened directly in Storybook, at 1440 and 390 px. With the story clock the leak is also wrong in time: an answer carries the time it was fetched, the clock takes every story back to STORY_NOW, and the previous story's answer reads as fetched in the future. Dropping only the stale answers when a story ended kept the leak; emptying the cache before every story, as the unit tests' setup does, ends it. The cache is emptied before anything mounts, so a request the previous story left in flight is cancelled with nothing waiting for it. storyQueryCache: a request that never caches gets this story's answer, a cached one is fetched again, and the cache starts empty. The last two fail on the previous "drop the stale answers" preview.
Qore batch 6 (28a6bbcb14) gives DPQL's diagnostics and completion details
in plain words that say what to do, and the stories' stand-in server
still said the old ones: "Incomplete template reference: expected
context identifier after '$'", "Unexpected token ')' after expression",
"requires argument", a field's detail as its type code.
The stand-in now gives what the installed DataProvider gives for the
same text: its words, its codes and its ranges, each checked with
parseDpqlExpressionWithInfo:
- a lone `$` (E030), a `$` before something that cannot follow it
(E031, which names that character once, as the server does), a
template with no `:` (E032, new here: it said E031 of the first
letter), one with nothing after its `:` (E033, `$local: ` too), a lone
`@` or one before something that cannot follow it (E003), and a
character that cannot be used (E001);
- a value missing at the end, said where the token before it starts, or
before a token; a `(` left open, in a call or a group; and what
follows a whole value, by what it is (`)`, `,`, and, or, not);
- completion details: "needs a value after ':'" for a template context,
and a field's type as DataTypeCatalogue names it ("Whole number").
The tests and the three story plays that asserted the old words assert
the new ones, and the DpqlEditor stories' own server answers its
completions as batch 6 does ("gives text"). reqraft's code reads none of
these words; a placeholder is still found by its range.
…clicked, wrapped or not (qorus#646) Dpqleditor · A Click On A Chip Opens Its List And A Second Closes It checked that the list had moved to the second chip by putting it right of the first chip's right edge: true on a wide line, where the two chips sit side by side. At 390 px they wrap onto two lines, the second at the start of the next one, and the list opened correctly under it failed the check in Storybook's UI. The play now checks the list against where the chip it clicked is drawn: under it, from its left edge. On a wide line that is past the first chip; on a wrapped one it is below the first chip's line. A list still at the first chip fails either way. The story passes at 1440 and 390 px, in the story run and in Storybook's UI, and its frame is unchanged.
…rus#646) getQorusTypes() gave reqraft's built-in copy of the type catalogue always, and the checks built on it never saw the server's answer to GET /system/qorus-type-info: areQorusTypesCompatible() read `getQorusTypes() || fallbackTypes`, and the first was never empty. A type or a conversion only the server knows was invisible to the expression builder's return-type check and argument check, and to the templates TemplateField offers a field (FormEngine fields included). - areQorusTypesCompatible(), argumentMatchesType() and filterTemplatesByType() decide with the catalogue they are given, getQorusTypes() by default; - the expression builder, its argument labels and TemplateField give them useQorusTypes().value: the server's answer once it has come, the built-in copy until then, and they draw again when it comes; - getQorusTypes() gives the server's answer once a useQorusTypes has it, for code outside a component; stories and unit tests start without one. serverTypeCatalogueDecides: a type only the server lists (an order reference that takes text) fits, keeps its field's text templates, and is seen by getQorusTypes() after the hook's answer. All four fail on the previous code.
…lt-in catalogue, not offered (qorus#646) Qorus gives a field or an option of a DataProvider string format type, or of a file, that type as its UI type (qorus 974643455, approved by David). reqraft's built-in copy of the type catalogue had no entry for them, so before the server answered a form could not name such a field, map a value into it or offer it a template. The copy is the server's catalogue of 974643455 again: - email, uri, uuid, hostname, ipv4, ipv6, phone and file, with the DataProvider module's names and descriptions; a string format takes and exactly matches itself, string and richtext, a file itself and a hash; the server lists them after the offered types, and so does the copy; - "Text or binary data" takes the string formats, and a hash takes a file, as on the server; - every entry has `offered`: true for the types before, false for these. offeredQorusTypes() (and useQorusTypes().getOfferedTypes()) gives what a picker that lists every type offers: the offered types, and the type a value already has. No reqraft picker lists every type; its pickers keep the compact list. The fixture is that commit's QorusDataTypeCatalogue::Info as the endpoint answers it in English, with Qore 28a6bbcb14's DataProvider: no running server has it yet. The recording script requires `offered`. The catalogue tests fail on the previous copy (6 of 8).
…is never named by its code (qorus#646)
A type the type catalogue lists under no name - a user type, a type of a
newer server - was named by its code ("qoretechnologies/order",
"order-status"), said as "a value of type order-status", and fitted
nothing, so its field was offered no template and its argument always
looked wrong.
Qorus gives every field, option and argument its base data type
(`base_type`). qorusTypeEntry() now resolves a type by its own name, an
optional, soft or complex form of one (as before), and else by its base
type, read the same way. Where neither is listed, a user type (a path)
is named by its path and anything else is "Unknown type" / "a value of
an unknown type"; no name is the raw code any more.
- the display name, sentence form and known name take the base type,
and so do the hook's getTypeDisplayName(), getTypeSentenceForm(),
getAcceptedTypes() and getExactMatches(), which now also read the
optional, soft and complex forms;
- areQorusTypesCompatible() resolves a place's type the same way, with
its base type, and a value's type by the name the catalogue lists it
under; argumentMatchesType() uses the argument's base_type;
- TemplateField filters a field's templates by its base_type too, an
argument label names the place by its base_type, an option's words
("Takes key-value pairs (a hash)") use the option's, and the
unsupported-type dialog names a type as the catalogue does.
typeBaseFallback: 6 of its 7 cases fail on the previous code; three
tests that expected a raw name now expect "Unknown type".
…ypeEntry among them (qorus#646) The Qorus IDE keeps its own copy of how a type is found in the type catalogue, which drifts from reqraft's. The package root now exports qorusTypeEntry() (a type by its name, an optional, soft or complex form of one, or its base type), offeredQorusTypes(), the names (qorusTypeDisplayName, qorusTypeSentenceForm, UNKNOWN_TYPE_NAME), the built-in catalogue and getQorusTypes(), and useQorusTypes with its types; the hook's module re-exports the new ones too. typeCatalogueExports fails before the export.
…qorus#646) A form draws a type's name with its own words (TypeSentenceFormProvider), which handed the type on without its base type: an option of a user type the catalogue does not list showed no type at all in its help and beside its name, although the type's base type is listed. The provider now passes the base type on, as the built-in words do. Two FormEngine stories, at desktop width and on a phone, show a field of a user type with its base type (named Text) and one of a type with neither (named by nothing, never by its code). typeBaseFallback's new case fails before the fix (undefined instead of "Key-value pairs (hash)"), and so do both stories' plays.
…, and an unlisted type, are offered (qorus#646) Four TemplateField stories, at desktop width and on a phone, open the list of templates of a field: - of an order reference, a type the server's catalogue lists and the built-in copy does not: the text templates, by the server's answer; - of qoretechnologies/order-id, a user type no catalogue lists, whose base type is a whole number: the whole-number templates. Both lists offered nothing before d61681e and 11a773a: run against 7a22834's sources, all four plays fail (one row instead of the templates).
…e (qorus#646)
The auto field draws an editor for the types it knows and "Unknown
type!" for any other: a field of a string format type such as an email
address, of a user type or of a type a newer server adds could not be
set in a form, although Qorus gives every field the base data type of
its values (base_type: string for an email address). Such a field is now
edited as its base type, as the type catalogue names it by its base type
(qorusTypeEntry). Only a type with no base type that has an editor is
unknown. base_type is read by the editor choice alone and no longer
lands on the editors' DOM.
Two FormEngine stories, at desktop width and on a phone, open a field of
type email (named Email address) and one of a user type on a text field
holding its value.
autoFieldEditsAsBaseType fails before the fix in its three base-type
cases ("Unknown type!" drawn), and so do both stories' plays (no text
field).
… as its base type (qorus#646) Qorus names the base type of every template's values (base_type, Qorus a1e08a293), as it does a field's, so that a client can fit a template of a type it does not know. reqraft dropped it: buildTemplates kept only the type, and the type filter found no entry for a user type, so a template of qoretechnologies/money was offered to no field but an untyped one. buildTemplates keeps the base type in the item's metadata (baseType), filterTemplatesByType hands it to areQorusTypesCompatible, which takes the base type of the value's type as well as the place's (checkBaseType); a type the catalogue lists is still that type. Two TemplateField stories, at desktop width and on a phone, open the list of a number field: it offers the money template, by its base type. typeBaseFallback's new case fails before the change (no template offered), and so do both stories' plays.
…r its code (qorus#646)
A template kept its type only as its list row's badge (buildTemplates:
badge: type), which Reqore draws as it is: every list of templates said
"int", "string" or "qoretechnologies/money" beside a template, and the
badge could not be named, because the field read it back as the
template's type (the value's type when a template is chosen, a lone
template's type, the type filter, a chip's hover).
The type is now a typed member of the template: buildTemplates gives
metadata.type, beside metadata.baseType; templateType() reads it, or the
badge of a row a host builds itself, and every reader of a template's
type uses it. The lists name the type in the badge they draw
(styleTemplateItems: the browse list and the text editor's list) and in
the detail of the list a $ opens (templateCompletionItems, in a text
field and in the DPQL editor), by the catalogue the surrounding form has
(useTypeLabel; the built-in one elsewhere): qorusTypeDisplayName, so a
user type with no base type is named by its path and any other unknown
type is "Unknown type". The package exports templateType and
templateBaseType.
Two TemplateField stories, at desktop width and on a phone, open a list
of templates of every kind of type. templateTypeBadge fails before the
change in 7 of its 9 cases (the other two guard what the field reads
from a named list), and so do both stories' plays ("Count: expected
'CountintExample value: 3' to contain 'Whole number'"). templateSources
and dpqlCompletions now expect the names.
…ged by the place's base type (qorus#646)
The expression builder judged what an expression gives against its
place's type alone: a field of a user type whose values are text
(qoretechnologies/order-ref, base_type string) refused an expression
that gives text ("This expression gives text, which this field does not
take"), and an argument of such a type took nothing, its accepted types
being looked up without the argument's base type.
The builder takes the place's base type (returnBaseType, which
TemplateField passes from the field's base_type through ExpressionField
where the place is the field's own type) and hands it to
areQorusTypesCompatible and to the place's name in the mismatch; an
argument's accepted types are looked up with its base_type.
Two TemplateField stories, at desktop width and on a phone, show a text
field and a date field of user types holding concat(...): the first
takes it, the second says it takes a date and time.
expressionPlaceBaseType fails before the change in its two "fits" cases
(the mismatch drawn); the stories' plays fail on the same assertion.
…named as their base type (qorus#646) Qorus gives a list field the base data type of its elements (element_base_type: string for a list of email addresses). The auto field handed its elements their type alone, so each element of a list of a user type, or of a string format type, showed "Unknown type!", and the list was named "List" with no word for what it holds. The auto field hands each element's field the list's element_base_type as its base_type (a78372e edits it as that), and optionTypeWords names the elements by it: List (Text) for a list of qoretechnologies/order-ref. Two FormEngine stories, at desktop width and on a phone, show a list of email addresses and a list of a user type: named List (Email address) and List (Text), each element on a text field holding its value. autoFieldEditsAsBaseType's list case and typeBaseFallback's list words fail before the change ("Unknown type!" drawn; "List" for "List (Text)"). The test's fields now have a fetch context, which a list's template fields need.
…no type (qorus#646) 1bc4478 read a row's badge as the template's type wherever the row had no typed member, and a group of templates is badged with the app it comes from: the list of a yes/no field, where no template of Context Data fits and the group is drawn with no members, named the group "Unknown type" instead of "Qorus" (TemplateField · Shows Templates List For Boolean, in the r16 story run). templateType() reads the badge only of a row that is a template, one with a value; a group has no type, and keeps its badge. templateTypeBadge's new case fails before the fix ("Qorus" read as a type), and so does Shows Templates List For Boolean's play, which now checks the group's badge.
…dering (qorus#646) The Preview kept the last rendering it had until the next one arrived, and its state compared that rendering with the text now showing, not with the expression it was made from: after an edit, for the pause before the rendering is asked for and the server's answer, the previous expression's rendering was drawn under the new text. r15's capture of FormEngine · The Text Lists What Can Be Written At The Caret On A Phone caught "record: pos" under `bezeichnung == bezeichnung` (ExpressionField.tsx, the Preview effect and previewState). A rendering is now kept with the expression it renders, by content, and drawn only for that expression: until the current one's arrives the field draws nothing (data-preview="pending"). An answer for any other expression is dropped by the key of the one asked for last, however late or out of order it arrives. The caret-list play waits for the field to hold its last expression and have its rendering (data-preview="repeats") before it opens the list it leaves for the capture, so the frame no longer depends on timing: no Preview, and the fit message for the expression shown. expressionPreviewIsCurrent drives a stand-in server that answers when and in the order the test says, with fake timers: both cases fail before the fix (the previous rendering drawn under the new text).
|
Round 16 (7a22834..fb70c1c), stories approved:
Each change has a fail-first test; precheck clean (3087 unit tests); 806 story tests pass, desktop and touch. |
Follow-up to #131, from David's review of qorus#646.
1. A card's name and its actions. An open option card lined up its name with the top of its actions, so "When" sat above the buttons beside it.
2. Number stories.
12abckept as typed, and a decimal point typed before its digits kept. It points to where a number that takes templates is.Related (reqore #708). A typed field that opens with the cursor already in it showed no templates until the user left and came back. The cause is in reqore's focus popover, fixed in qoretechnologies/reqore#708.
3. Operand messages. While a row rule was being built in the Visual view, it said
Value for argument 1 ("Value") is invalid: Missing value, twice.Enter a value for "Value"."Value" is invalid: ....Enter the 4th "Value",The 2nd "Value" is invalid: ...(8b7a187, qlip build 20261008-083118).4. #136. #136 (fetch: a request that was abandoned is not a request that failed) was merged to develop (761550b) and is in this branch (8fbe7b5). It was published in 0.10.61 by #139, so this PR is 0.10.62.
Checks. build:test, build:test:prod and lint pass, with 2056 unit tests and 635 stories passing locally (at 1a6de5f). actionlint 1.7.7 (with shellcheck 0.10.0) is clean on both workflows.
5. An edited value says where it is going. While an option is being edited, it stays under the heading it was opened from (it moves when the editor is closed with ✓). Its open editor now says so:
Moves to "Set" when you close it (✓)(fbe17de).6. The Text view, Preview and summaries of a visual expression (David's review of qorus#646). In a row rule, ">" with its first operand switched to Use Expression (nothing chosen) showed
undefined([object Object]) > [object Object]in the Text view. With a custom Text value holding a field template on each side, it showed[object Object] > [object Object].dpql/serializeanddpql/renderExpressiongot them as they were; the server writes them as hashes ({args=(null)} > {type=int}), and the stories' stand-in server threw.expressionOperandsreads an operand as what it holds. A rich-text value becomes its template (a field is@fieldin DPQL) or its words; an operand not filled in is a hole.dpql/serializeonly for a complete expression, and shows… > …otherwise;renderExpressionToText) writes holes as….expressionOperand,expressionWithOperands,isExpressionComplete,EXPRESSION_HOLE), so an application's summaries need no copy of their own. qorus-ide's row rule line had one, and wrote[object Object] > ….expressionTextOfIncompleteanduseRenderExpressionWaitsboth fail before the fix. New story: ExpressionField › Text Mode Of An Unfinished Expression.7. A value that takes templates is written as text (David's review of qorus#646: the values filled into empty cells, or given to added fields, of non-text columns) (1fabea9, 621b377, 9fb2333, db8c47c).
writtenAsText):12,3.5,true) is that literal; other text in a typed field is kept as written, for the form to flag;nullstays a value; fixed choices (allowed values) keep their list.boolin a condition, and showed "Unknown type!".writtenAsText(the whole-number field and the date ones fail before). New stories: Template › Written As Text (Template Alone, Text Around The Template, Number With A Template, Number Picks A Template, Number Emptied Keeps Its Field, Expression Detected, Null Stays A Value, Allowed Values, Date Keeps Its Control). The template stories that asserted the pick-only control now assert the chip in the text field.expressionUntypedOperandsWrittenAsText(fails before 9fb2333); ExpressionBuilder › With Simple Value checks the 150px width (fails before db8c47c).8. Text around a template in a typed value, and null (David's decisions on qorus#646) (26c485c, b7f95b8).
Text around a template is not a whole number, a number, a yes/no or a date. Validation took anything starting with
$and holding a:for a template, so$record:{pos} Stk.passed in every typed field and the form could be saved with a value its type cannot hold. It is now invalid, with the reason under the field while it is typed:"Stk." makes this text, not a whole number. Delete it to keep the template alone.Templates side by side make this text, not a number. Keep one of them alone.The message says what causes it and how to fix it (4e08f55, qlip build 20261008-083118).
A template alone, a literal and an expression are judged as before; text fields are unchanged.
null in an untyped field shows
null. It fell to the generic custom editor, which drew null as an empty box, the same as a field holding nothing. It is now written in the field's text field asnull, andnullwritten in an untyped field is null. Deleting it leaves the field empty, which is no value (undefined), not null.Tests:
textAroundATemplateandwrittenAsText- null in an untyped field (both fail before). Stories: FormEngine › Whole Number With Text Around A Template (typed, then deleted; fails before), Whole Number Literal And Expression Stay Valid, Number / True Or False / Date With Text Around A Template; Template › Written As Text Null Stays A Value now shows null, is emptied and written null again (fails before).9. A host's own reason; chip and read-row names (David's decisions on qorus#646) (8e3e30a, e198b39, 6303b15).
invalid_reason). The sheet-contract review says what is wrong with a value in its own words, in the user's language, and the field said its generic reason as well ("This field is required", "…around a template"): two messages for one problem. A field's schema can now carryinvalid_reason. While it is set, it is the field's message, in place of the field's own validation reason and "This field is required" (required groups and dependencies still say theirs), and the field is invalid, so the form's validity and "Needs attention" agree with what is said.namingTemplates, defaulting totemplates); TemplateField passes the whole catalogue there and still offers the filtered list.10. "Use Template / Expression" is reverted (2343a15). 5fa0a45 was a paused design, replaced by the tabbed value entry being built separately; the branch is back to bc24dc0's behaviour, stories and tests.
11. A form option's value is entered on tabs (David's design for qorus#646) (86641aa). An option that takes templates and expressions has Value · Expression · Visual in its header row; one that takes templates only has Value · Template; one that takes no templates has no tabs. FormEngine turns the tabs on (
valueTabs); a field outside a form's options (an expression operand, a standalone field) keeps its behaviour.value(12). Without a language server, Expression moves to Visual.value(12)); all fail before. Stories: FormEngine › Value Tab Per Option Type, Value To Expression And Visual (+ On A Phone), Saved Template Opens On Value, Saved Template On A Yes No Opens On Expression, Saved Text With Templates Stays Text, Saved Expression Opens On Expression (+ On A Phone), Template Only Option Has Value And Template (+ On A Phone), Expression Typed On Value Switches With Undo, Template Of Another Type Warns. Option With Any Type and ExpressionField › Field Menu Stays In The Toolbar now assert the tabs; the rest of the branch's stories are unchanged.12. The tabs' words, the numeric keyboard, and a lone operand's position (445d1a3, bc2889b, 07e5333).
valueTabsLabels): the tabs, their tooltips, the cannot-show and replace text, the conversion warning and the undo, with English defaults. A label that holds a value is a function of it, so a host keeps its language's word order. A tab is found by its key (data-tab="value"), not by its label. Tests: valueTabs › the words of the tabs (fail before).inputmodenumeric / decimal), as its own input was. Templates are picked from the list the field opens, and an expression is written on the Expression tab. Test: valueTabs › numeric keyboard (fails before).Enter the 1st "Value"where the call has an "Add value" slot, as the operands that join it will. The position is said when another operand has the same name or the call is variadic. Tests: operandPosition, validation › an empty operand slot (fail before). Story: ExpressionBuilder › Addition Picked Fresh now asserts the message.13. Found verifying the tabs in the IDE (2aa29bd, 5da9d74).
concat(), and what was typed went after it. Tests: valueTabs › an empty value opens the Expression tab empty, writes a value as expression text (fail before).14. A host keeps values for reuse and says where a template comes from (c780b93). Two host seams, for the IDE's Qog options moving onto this field.
savedValues: the field's ⋮ offers "Save this value", which hands the value and the field type to the host (it asks for a name and keeps it), and "Use a saved value", which lists the values saved for the field's type. One is the value at once, on its tab, and can be removed as the host keeps it. Not offered for an empty value, a yes / no, an expression, fixed choices or a read-only field. In a form row the actions join the row's ⋮; elsewhere, the field's own.templateBadge: the badge a template shows in the template picker: where it comes from, as the host knows (in the IDE, the app action that captures it).15. An edit made just before its field goes away is kept, as a value (ab3fc8b). Found clicking through the review in the IDE: a field picked on a value's Value tab, then the Expression tab opened straight away, was lost and the value was left empty.
16. What was typed on the Expression tab is kept when the tab is left at once (d801fb4). Also found clicking through the review: a field picked into a yes / no's Text view, then the Value tab opened straight away, was lost. The Text view reads what is typed 300ms later, and leaving the tab took the editor and the waiting read with it.
ExpressionFieldnow offers its host a way to read it immediately (flushRef); the value tabs read it before they leave the Expression tab, then convert it as they do any expression. Tests: valueTabs › what was typed (and an expression typed) on the Expression tab is kept when the Value tab is opened straight after (fail before). Story: FormEngine › Expression Kept When The Tab Is Left At Once (fails before).17. Fixes from David's review of the tab stories (qorus#646):
18. The Text view waits for its language server, and loses nothing typed (7daa068). Typed text was parsed 300ms after the last key, whether or not the session was up. A parse sent before the session is connected answers
success: false, and nothing asked again, so what was typed never reached the value. Under load, the IDE's test-case story stored nothing for1 + 2. The opening text was written by a retry loop (20 tries, 400ms apart) that then gave up.DpqlEditornow says when its session can parse and write the text (onReady: connected, context bound).ExpressionFieldkeeps typed text pending and reads it when that signal comes, however late, and writes the opening text then too. No retry loop is left.initialText) is not overwritten when the session comes up.expressionFieldWaitsForTheSession(six cases; five fail before). Story: FormEngine › Compact Expressions now checks the seeded text, and checks that the Preview, which reads as that text, is not drawn beside it.19. A lone template written on the Expression tab is kept as the expression
template(...)(027b6ee, from 75b6d8e). Where the option takes expressions, a template written alone is stored as the expression the server parses it into, not as the bare template; a template-only option keeps the bare template. The DPQL mock does what the server does since Qorus's fix for a template in an expression from DPQL text (on Qorus develop). Test:valueTabs› a lone template written on the Expression tab is kept as the expressiontemplate(...).20. An expression's Text view lists the field's templates when it is clicked into (0697284, David's review). The Text view's editor is the same rich-text editor the Value tab writes in, but it was given no templates. So it listed nothing on a click or a tap, only after
@or$was typed, or from the picker beside it.DpqlEditornow handsSmartEditorthe templates it labels its chips with, as the list the editor opens when it is clicked, tapped or tabbed into, and inserts from. The language server's completion is unchanged. The list is built once, inuseTemplateTags, for the rich-text field and the Text view alike. Listing on keyboard focus, and Escape putting the list away and nothing else, come with reqore 0.78.5 (#708). Story: FormEngine › Value To Expression And Visual checks that a click on the Text view lists the row's fields (fails before).21. Two lists, one family: what is typed is completed, and a field's templates are browsed (ccd8647, David's review). David asked for "the same components to the extent possible, and a consistent UX". He then allowed a second component with a justification: "some templates can be large; we need to keep the hierarchical view".
@,$, the language server's completion) is its own component,CompletionList/CompletionMenu, taken out ofSmartEditor. It is a listbox the editor points to (aria-controls,aria-expanded). Its keys come from one hook,useCompletionKeys: the arrows move, Enter takes the row, Escape puts the list away, Tab leaves it. It never takes the focus.$in a value's text completes in that list too: the field's templates, found by what is typed after the$, wherever they sit in the catalogue.completionStyle: its colour, and its rows' kind icon and monospace font). From a control (the Text view's picker, a date, a number's input, a template-only value) it isTemplateBrowser. In a text it is the editor's own list (useTemplateTags).oneCompletionList(no Reqore dropdown lists templates except the browse list; it fails on the code before) andtemplateDropdownDrawsDescriptions. Story: FormEngine › Lists Look And Behave Alike (colour, font, focus, arrows and Escape; it fails with the browse list's old colour).22. A component's own props never reach the DOM (592c7f1, e7d853b; addendum from David's supah session).
ReqraftMenuItemreadactivePathsto decide whether it is active, then spread every prop intoReqoreMenuItem. An item drawn as a router link (as: Link, as qorus-ide's sidebar draws its items) hands the props it does not know to its<a>, so React warned that it does not recognizeactivePathsand wrote it onto the link (seen in qorus-ide's Components/Sidebar stories). Reqore's own fix (#708) cannot hold back a prop that is reqraft's.activePaths,submenuand a divider'sdividernow stop at the item;toandhrefstill go to the link.activePaths. It was memoised onpathalone, so a menu rebuilt with other active paths kept the first answer. It is computed on every render now (the memo was also a hook called after the divider's early return).as={ReqorePanel}):isChild,isAndOr,indexandreadOnly;readonlywas written onto the panel'sdiv. Its styling flags are transient props now.optionsschema,stringTemplatesandskeletonReasonwent to its ReqoreCollection.exactMatchwent to ReqoreModal.readonlywent to ReqorePanel (it still disables the drop area).disabledwent to ReqoreTabs (its buttons and editors still take it).fill,wrap,minimal,rounded,customTheme,hasIcon,isResizableRight). They come from reqore 0.77.2, this repo's dev pin; reqore fixed those leaks in 0.77.7 (7858ab3a), and 0.78.0 is onbeta.menuItemOwnProps(noactivepathsattribute on a link, section item or divider; a collapsed section opens and closes as itsactivePathschange),expressionItemOwnProps,ownPropsNotHandedOn. All fail before the fixes.23. The tests run names only a version npm has, and links the qlip build it uploaded (a21b67d, 9d9d776; addendum from David's supah session, approved).
npm version prerelease --preid=canary.<time>in the runner and posted "install the canary version viayarn add @qoretechnologies/reqraft@{that}". Nothing published it, and thevthatnpm versionprints would not resolve anyway. Run #482 told people to installv0.10.63-canary.20261009190442.0, a 404 on npm..github/scripts/release-version.shworks out the version a run publishes:<base>-pr.<PR>.g<sha7>for a PR, none when package.json's version is already on npm, and package.json's version for develop. publish.yml's Prerelease job stamps and publishes what it says, so the two workflows cannot drift.published-version.sh, which uses the same script, and names the version only ifnpm viewanswers with exactly it. Otherwise the message says why there is nothing to install: "No prerelease was cut: package.json is at X, which is already on npm", or "No prerelease is on npm for this commit yet: see the Publish run" (with a link to the branch's Publish runs). It can never name an unpublished version. The npm registry itself shows why the check is needed: this commit's prerelease was published at 10:39:15 and npm said "may take a few minutes to become available"; it answered 404 for some time after.v. The failure message names no version.::set-outputis gone from both workflows ($GITHUB_OUTPUT).$FROM.uploaded build 20261010-104435: 635 entries …, so the id kept its colon (runs #482 and #483 linked20261009-190109:and20261010-104435:; qlip's API answers 404 for them)..github/scripts/qlip-build-link.shreads it up to the space or colon, after stripping colours (9d9d776).releaseVersionScriptsruns both scripts against a stand-in npm (a PR's prerelease, an all-digit sha, a version already on npm, develop; named only when npm has it) and checks the workflows use them. The workflow checks fail on the workflows before this commit.qlipBuildLinkScriptcovers both of qlip's line formats.prversion match.yarn add @qoretechnologies/reqraft@0.10.62-pr.137.g9d9d776." Its Version field says0.10.62-pr.137.g9d9d776, and its qlip link ishttps://qlip.qoretechnologies.com/builds/20261010-105900.+ @qoretechnologies/reqraft@0.10.62-pr.137.g9d9d776. npm'sprdist-tag:0.10.62-pr.137.g9d9d776.0.10.62-pr.137.ga21b67d, published by Publish #247, and run #485 (80ac509) for0.10.62-pr.137.g80ac509, published by Publish #249.24. No text caret in a story capture (6277dbb; David's decision). The caret blinks, so a story that ends with the cursor in a field was captured with it on or off at random, and qlip flagged the same frame as changed. Template › Template Value came back as pending instead of carried forward as rejected, 9 pixels apart, all of them the caret after its "Interface ID" chip. The Storybook test setup now draws every caret transparent. 84 of the 635 stories end with the cursor in an input, a textarea or a rich-text editor; their captures lose the caret once (listed in my PR comment). Two local runs of every story give byte-identical captures for 83 of them; DpqlEditor › With Templates still differs in 7 pixels, a flat 9×2 patch that is not a caret.
25. The npm package ships the build, its README and LICENSE (80ac509; David's decision).
filesfield, so everything outside .npmignore was published: agent notes (.claude/CLAUDE.md,.tasks/*.md), design docs, the sources and the repo's configs. 0.10.62-pr.137.ga21b67d held 1242 files, 2.1 MB packed and 8.0 MB unpacked. A localnpm packalso took in qlip's screenshot folder (16841 files, 79 MB), which .npmignore did not list.filesisdist,README.mdandLICENSE(npm adds package.json).npm pack --dry-run: 903 files, 1.05 MB packed, 4.3 MB unpacked. dist holds the modules, their types and their source maps..github/scripts/check-packed-files.mjsruns after the build (run #485: "903 files, 1051697 bytes packed, 4285155 unpacked; 900 built files and 34 consumer imports checked"; Publish #249 published 0.10.62-pr.137.g80ac509 with 903 files, 1.1 MB packed, 4.3 MB unpacked). It fails when the packed file list is missing an entry point (main, types, module, exports, bin), any file the build wrote, or a module or its types that a consumer imports. The consumer imports are listed in.github/consumer-imports.txt: qorus-ide's 33 deep imports (dist/components/form/fields/...,dist/helpers/..., …) and the root.vite buildpasses, 8049 unit tests pass, and its Storybook (Sidebar, expression builder, Qog options, compact FormEngine stories) runs on it with no source-map warning.packedFilesCheckruns the check on packages made in the test, with real npm: a missing built file, a consumer import no longer built, a missing entry point and an empty build each fail, naming the path. The real allowlist ships every module qorus-ide imports, and ships no agent notes, design docs or sources; the latter fails on the package.json before this commit.26. What a form completes behind its host's skeleton is emitted when the skeleton clears, and stored options are kept (e1d99ba; regression from 4a9148e, found by qorus-ide's "Interfaces › Installed Release › Existing").
Why the early return exists (4a9148e). A host loading the schema shows the skeleton and hands the form an empty schema, or an earlier one, meanwhile. Read against that, the value is no answer: emitted, it wiped a Qog state's stored options the moment the state was opened (qorus#646). So nothing is emitted while the skeleton is up.
The regression. The emit only ran when the value changed. Hosts show the skeleton for other waits too: qorus-ide shows it while the templates load, with the schema already there. The form completed the value behind it (a required field's default, such as the server's
created_by_user), the skeleton clearing re-ran nothing, and the default was shown but never emitted, so never saved. "Interfaces › Installed Release › Existing" waited for it forever.Why re-running the emit is not enough by itself. A host that clears the skeleton as it hands the schema over still has the form holding the read against the old schema at that render. Every stored field had also already been removed from the form as "not on this instance" (the available fields are pruned against the schema). Emitted, that wiped the options. Not emitted, as at 80ac509, the form still showed them empty, and the next edit would have been emitted without them.
Now the form knows whether its schema is in force: the host is not behind the skeleton, and a schema it hands down has been taken in. Until then it neither prunes the value against the schema nor emits. When the schema comes into force, the emit check runs once; a value that adds nothing still emits nothing.
Tests (
formEngineSkeletonClears):formEngineHostSkeleton's qorus#646 case is unchanged and passes. No FormEngine story drives a host skeleton, so no story assertion was added.27. No story reaches a real server (ca993b1, ab2c9cc).
storyApiUrl.src/stories/realNetworkGuard.tsis installed by the story test setup, after storybook-addon-mock. It blocks any request to another origin than the page's own: the fetch / XHR the addon falls through to when it has no entry, and WebSockets and beacons. A blocked request fails at once as a network error, nothing is sent, and the story fails naming it. Stories meant to reach a real server are tagged!testand never run under it./api/latest/users(and one/api/latest/schemas). With them, all 635 stories pass with the instance pointed nowhere (REACT_APP_QORUS_INSTANCE=https://127.0.0.1:9/).storyMocksFollowTheInstance: no story or story mock names an instance host of its own; it fails on the old storage mocks.realNetworkGuard: covers the guard itself.28. A row summary writes
+and*of more than two operands infix, as the server does (c435ea1; found verifying the Qog options against hq).+and*take any number of operands, and the Visual view builds them that way. qorus-ide's Send Discord Message › Message Content is one+of three."This Qog was run at by " + format_number(…) + " and that's it". The collapsed read-first row wrote+("This Qog was run at by ", format_number(…), " and that's it")instead: the same value read two ways.renderExpressionToTextnow writes+and*infix with any number of operands, each in the context the server gives it: a later operand that binds as loosely is parenthesised (1 + 2 + (3 + 4) + 5).DPQL-SERIALIZE-ERROR: operator "-" takes 2 arguments; the expression has 3). The summary still writes every operand, as a call:-(10, 3, 2).expressionParentheses. The three-operand, nested and multiplied cases fail before the fix.29. A check group's choices each have their own key (1a6de5f).
item.value?.toString(). A choice whose value is not resolved yet had no key, and every object value keyed as"[object Object]".allowedValuesCheckGroupKeys, which fails before the fix.30. A read row's reason starts on its value's line, beside an empty value's dash (9db1a1d, qorus#646, from David's review of qorus-ide's Fields/Test Cases Guided Help › Field Refused)
Symptom: the locked Expected value row drew its value, the empty dash, on one line. The reason ("This field is disabled because some dependencies are not fulfilled: …") went on the next line, so the dash read as a bullet with its text below it.
Root cause: this is reqraft, not the IDE.
StyledRowValue(compactRowStyles.ts) lays the value and its reasons out as flex items, meant to "share the line, wrapping the reason below only when it doesn't fit".Fix: the reason that follows the value takes the room beside it (
flex: 1 1 16em; min-width: 0) and wraps within its own box. It goes below only when the value leaves it less than 16em. Later reasons and the full-width companions (message panels, a choice's description) are unchanged.Story: Form/Engine/FormEngine › Compact Locked Reason Beside Its Dash uses the guided check with Exists picked. The reason wraps and starts on the dash's line, to its right. It fails before the fix, where the reason starts below the dash.
Checks: typecheck (test and prod) and lint pass; 227 files, 2390 tests pass. All 185 stories of the FormEngine story file pass locally.
31. An expression's type is the server's, and the condition verdicts are judged by it (from #140, closed: dc4fc3f, cherry-picked from ca0abc5; and 4c989ac; qoretechnologies/qorus#713, qorus#646)
Symptom: the builder worked out an expression's type itself, from the catalogue's
ui_return_type, one level deep. That disagreed with the server:1 + 2.5was "any" here and a float there.From #140:
useExpressionTypehook asks the server over the LSP custom methoddpql/expressionType, on the shared connection, debounced and cached. The server side is qorusaa5d066e2.return_type_first_arg, the catalogue'sui_return_type) is gone.Folded into this PR (4c989ac): the qorus#646 condition verdicts now take that server type.
inferred_type), as the Text view's verdicts do: "evaluates to a whole number", where the UI type of every number isnumber.ui_type, since the types a place takes are UI types.+as a number, so the refusal came out as a warning.dpql/expressionTypeas the server'sgetExpressionTypeInfodoes (string→richtext, a number →numberfor the UI). Every story starts with a fresh expression-type client, as it already did for the render client.Tests:
useExpressionTypeandexpressions(from fix(expressions): the builder shows the type the server gives an expression (qorus#713) #140).expressionConditionVerdictServerType: text is refused, also in a field that declares no type. A number is warned with its own name, although the catalogue says text. No verdict while the type is unknown, or known only at run time, and none for true or false. Three of these fail with the client-side type.dpqlMockLanguage: the mock'sdpql/expressionType.Checks:
Earlier failed CI run (37601004195), With Markdown Docs. One typed key was lost under parallel load (
arr.frinstead ofarr.for). The cause is in reqore's rich-text editor: a value set by a passive effect overwrote a key typed between commit and effects. It is fixed in qoretechnologies/reqore#708, and the test there fails without the fix. This PR does not change that story, and its re-run passed.32. A field is drawn as its type from its first render, and a rich-text field holding a string draws its chips (1c78382, 07576d2, qorus#646; found by qorus-ide's
[object Object]gate in four Components/Expression Builder stories)Symptom: in the Qorus IDE, the rich-text "List of Values" operand of a nested
in(...)showed[object Object]in its text field until the field's next render.Root cause:
anyand only took its declared type in a mount effect, after its first render.anyeditor is the text field with the field's templates as chips, and it is handedString(value). A rich-text value is a Slate document, soString()turned it into[object Object].textformat it turned a string into a document with chips, and RichTextFormField kept that document when the type switched torichtext.$local:some-richtextspelled out where its chip belongs.Fix:
resolveAutoFieldTypesresolves the declared type when the state is created (or, for an untyped field holding a value, that value's type), using the rules the mount effect applied.textformat does, and compares it as the document it stands for. Nothing is sent back until the field is edited. A click into a field holding''no longer turns it into no value.Tests:
autoFieldDrawnAsDeclared: rich text holding a document is drawn in the rich-text editor holding that document on every render, with or without templates. Long text is never drawn as the one line an untyped field takes. Also cases forresolveAutoFieldTypes. The three render cases fail before the fix.richTextStringValue: a reference drawn as its chip; text around a chip; nothing sent until edited; nothing sent when the editor reports the document it was handed, or''as empty text; the document sent once edited; a new string from outside; documents unchanged. Four fail before the fix.true in <rich text>nested in a Logical And, the rich text holding a template. It watches the document from before it renders, including nodes taken out again, and must never show[object Object]. It fails before the fix. Theinentry is in this story's own catalogue, so no other picker's count changes.Checks:
The first CI run at 1c78382 failed two stories. AutoField renders in neither, and both passed in the next run (07576d2):
nulfor typednull. A trace shows the field is TemplateField's own text editor, mounted once and never reset. Locally with reqore 0.77.2 it failed 6 of 11 runs (old and new code alike). With reqore 0.78.5-pr.708.g87239b9 it passed 8 of 8. This is the same lost-key cause as the earlier failed run above, fixed in reqore #708.33. A hover's popover dismisses its own hover, never the next one (c528388, qorus#646; the one-off timeout of SmartEditor › Hover Details On A Touch Screen at 1c78382)
Where it was stuck: Qlip's error capture shows "forEach" typed and the Details button on screen, with no hover. The play had tapped Details and was waiting for the hover. That wait was as long as the test's 30s, so the report said only that the test timed out. It is not the lost-key bug (the text was all there), and not AutoField (the SmartEditor does not use it).
Root cause:
Fix:
clearHover()without an id (the mouse leaving the editor) still clears whatever is shown.Tests:
smartEditor/hoverDismissedAlone: an older hover's popover leaves the hover shown; a new id for each hover, even at the same place; no id clears the hover shown; nothing shown where the server says nothing. Two fail before the fix.34. A tap opens no hover; only a pointer that hovers does (51bc71b, 8444150, qorus#646)
Symptom:
Root cause:
mousemoveandmouseenteramong them, at the tap), and nothing in them tells them from a mouse's.mousemove, and CompactRow formouseenter.Fix:
isHoveringPointer(src/helpers/hoveringPointer.ts): a mouse, or a pen held above the screen (buttons0); never a finger.pointermove/pointerleave, and CompactRow's three group highlights foronPointerEnter. Each acts only for a hovering pointer, and leaving still clears.useCoarsePointer/isHoverCapableask about the device), so a touch laptop's mouse still hovers, and so does a mouse plugged into a tablet.hoverDelayMs(default 300, the delay it always had).hoverhandler opens onmouseenter, so on a touch laptop whose primary pointer is fine, a tap on the screen still opens a tooltip until the click closes it.Tests:
hoveringPointer: mouse, hovering pen, pressed pen, finger.smartEditor/hoverIsAMouses: a resting mouse asks for the hover and its leaving clears it. A hovering pen asks; a pressed one does not. A finger's tap, with its emulatedmousemove, asks for nothing. A finger lifted leaves a Details hover. Four fail before the fix, the finger's tap among them.formEngineGroupHighlightIsAMouses: the chip and a row light the group for a mouse, and not for a finger or an emulatedmouseenter. All four fail before the fix.hoverDelayMs: 0makes a hover started by a tap go out before Details' own, on the same connection, so it is seen however soon Details is tapped.pointermovewithpointerType: 'mouse'.Checks:
design/SMART_EDITOR_UX.md§5 anddesign/COMPACT_PARITY.md.35. A host editor is never handed its own report back as a change (b04990a, qorus#646)
Symptom: under CPU load, the Qorus IDE's test-case Value (a
componentOverrideseditor, the IDE's own rich text) lost what was typed after a reference was erased, and Slate threw "Cannot resolve a DOM point from Slate point {path:[0,0],offset:1}".Root cause, traced in the IDE:
"", which came back as""and, once the form had stored it, again asundefined.1was typed and before it was reported, and replaced it.Fix:
""andundefinedread as one empty value (useHostEditorValue).onChangeis now stable. A new function on each render had restarted the host editor's pause before it reports.isHoveringPointertreats a pointer of no named type as hovering, as reqore's does.Not fixable here: the first echo of a report, arriving after the next key, is a genuine change of
valuefrom the editor's point of view. Only the editor can recognise it as its own; reqraft's own rich text does, usingEmittedValues.Tests:
hostEditorValue(10 cases).36.
EmittedValues/useEmittedValuesare public (4086837, qorus#646)They are exported from the package root, with the
TEmittedValuesEqualstype, for a host's own debounced field. The Qorus IDE's rich text uses them in qorus-ide f1c58c2d0.Documented:
recordeach report;isEchoonce where an incoming value is committed;includesfrom a render;settledfor what the parent will hold.Sameness is the caller's
equals, kept from the first render. By default it isObject.is, under which'',undefined,nulland an empty document all differ.Tests:
emittedValuesExport, 6 cases, importing from the root.37. Foxhoundn's review on qlip build #361 (58c58df, 4688fc1)
smallon a touch screen, BRAND_DESIGN §5's minimum for a finger. The story checks each ⓘ is as tall as its choice.narrowActions='menu'). This PR picks that up through a reqore bump once #717 is released. Until then those frames still show the fold.38. A required field that arrives with the schema is the row the first-required scan opens (aac66a1; a regression the Qorus IDE's live stories found on
g8612725)expandFirstRequired/autoFocusFirstRequired) ran once and was spent even when it opened nothing. It read the form's value before the sync-down had added the field the schema brings. The flaw is older than this PR. b1c83fb exposed it in the IDE, which hands its value throughfixOptions: before that commit the value was taken whole a render early, and since then it is recognised as the form's own report, so the form adds the field itself a render after the scan.39. The colour field's problem message (b222dc9): it was spaced with a rule on
.reqore-message. The field's own wrapper spaces it now (FRONTEND.md). No visible change. Found auditing this PR; out of its scope and filed as #143: SelectCollection'sstyled(ReqoreModal)with!importantmin sizes, on develop.40. develop merged in (2b7614e): #141, the rich-text choice chip.
41. #142 folded in (merge 64a5173, at David's direction; #142 is closed and its review continues here)
REFERENCE_CHIP_PROPS, atinychip whose label takes the font of the text it sits in (effect.textSize: 'inherit') (e4fc552, e31bae1).namingKey). Story: RichText › Chip Drawn When Its Catalog Arrives.textWidthprop and the panel'scontentStyle, with no rules on Reqore's classes (824e27b, 0b675cd, 260083a).modeLabels,expressionModeLabels) (51b5020).endsReferencesays which characters end a reference (David's change module type to commonjs #11) (64952bb). Test: typedReferencesBecomeChips. Story: ExpressionField › Text Mode Reference Typed Out Is A Chip.42. A WebSocket whose handshake hangs is closed and replaced at its deadline (a2967c0; develop merged in by f6758f1)
ReqraftWebSocket.connect()set no handshake deadline and replaced a socket only after a close, so a hung socket was never replaced. Its server-status probe before each attempt (GET system/pid) had no time limit either, so a server that hangs REST too also stalled the replacement. The IDE fixed the same gap in its own socket layer on qorus-ide #281.src/utils/websocket.ts):handshakeTimeoutoption, defaultREQRAFT_WS_HANDSHAKE_TIMEOUT_MS, the same as the IDE'sWS_HANDSHAKE_TIMEOUT_MS(0sets no limit). A socket still connecting at the deadline is taken out of the pool, detached and closed. Its users get what a failed handshake gives them, an error and then a close with code 1006, through their own handlers, and they reconnect throughmaybeReconnectwith the backoff and attempt cap of a close: the first attempt at once, then one everyreconnectInterval, thenonReconnectFailed. A hang is never read as anything else. The deadline is cleared on open, error and close, and when the caller closes. An open socket has no deadline.probeTimeout,REQRAFT_WS_PROBE_TIMEOUT_MS), and then aborts its request through anAbortController.query()andqueryRaw()take asignalfor this.remove()andclose()call off a reconnect waiting to run and its probe. A socket closed while it is still connecting reports 1006, not 4999, so a socket closed on purpose is marked and never reconnected. A user removed between sockets no longer counts down a socket another user has dialed since.closeAll()takes its sockets out of the pool at once; it deleted them under the wrong key, so a connection made right after it joined the socket that was closing.onCloseoronReconnectingthat throws no longer stops the reconnect.ReqraftWebSocketsection in the README (options, the deadline, the probe, closing), JSDoc for the new options and constants, and the WebSocket notes in.claude/CLAUDE.md.HangingWebSocketfixture that opens only when a test opens it and closes as a browser does):handshakeTimeout, and0;onCloseoronReconnecting;remove()andclose()during the hang, while the next attempt waits, and while the probe is in flight leave no timer, no socket and no later attempt;closeAll()on a connecting socket.queryandfetch, the probe's request is aborted at its limit and when the caller lets go, andquery()passes itssignaltofetch.useReqraftWebSocketsays it is reconnecting at the deadline and is OPEN once the replacement opens, says "Reconnect failed" when every attempt hangs, and leaves nothing behind on unmount.ReqraftLspClientconnects on the replacement, and leaves nothing behind when its last client goes during the hang or while the replacement waits.remove()that does not stop the reconnect (6),close()that does not (2), no on-purpose mark (1), nofinallyaroundonClose(1), handlers removed from the pool's socket (10).package.jsonandyarn.lock. Inyarn.lock, develop's entries are kept where both sides changed (js-cookie 3.0.8, lodash 4.18.1), andyarn installdropped what only react-color needed, which this PR removes. reqore stays at the released 0.78.5.The combined push (62fceccb), approved by David as a story table:
yarn install --frozen-lockfilepasses).A click on a compact row opens or closes it by what was drawn (b1aff2d8), approved by David as a story table.
expandOptionandcollapseOptionreplace the toggle, and each does nothing to a row already in its state.expandOptionstill spends the first-required scan and keepsexpandMode='single'.@qoretechnologies/reqraft@0.10.65-pr.137.gb1aff2d.Checks at 64a5173: typecheck (test and prod) and lint pass, with 265 files and 2685 tests passing. Every story file, run one at a time, passes: 734 stories.
Version 0.10.65 (develop is 0.10.64 since #146 and #147).
🤖 Generated with Claude Code
https://claude.ai/code/session_01T3k69KvVFmLA6bq4gwXtA9
https://claude.ai/code/session_01EvsXdPibtvZARTVPZm9L2a