Repository navigation
Conversation
… number that takes templates is shown as it is used — 0.10.61 David's review of qorus#646: - An open option card lined its name up with the top of its actions, so "When" sat above the buttons beside it. A heading of one line is centred on the actions; one with a description or tags under its name still lines up by its top. Injected Option Actions Edit Card checks it. - Number > Typed Freely typed a template into the bare number editor, which takes none, and read as a field that should offer templates and did not. It is the bare editor (12abc kept as typed, a decimal point typed before its digits kept), and says where a number that takes templates is. Template > Number Typed Freely is that field: the first click lists the templates, what is typed is kept as typed, and a chosen template is the value, named in the template picker.
|
📦 Prerelease published for this PR: Install it in a consumer (updates yarn add @qoretechnologies/reqraft@0.10.62-pr.137.gf3f5d3fPublished under the |
qlip visual regression🔍 32 changed, 68 new of 698 snapshots. |
…ame; a wrong one says which, by its name
David's review of qorus#646: a row rule built in the Visual view said
"Value for argument 1 ("Value") is invalid: Missing value" twice while it
was still being built. An empty operand now says "Enter a value for
"Value"", and a wrong one ""Value" is invalid: ..." - by the name the
editor shows, not its position in the call.
…abandoned request is not a failure) ships in 0.10.61
…hen it is closed An open field is held in the box it was opened in, so finishing an edit does not remount it in another box and take the focus. Held, it said nothing: a value already right sat under "Needs attention" until ✓ was pressed, with no reason given (David's review of qorus#646). It now says where it goes - 'Moves to "Set" when you close it (✓)' - under its editor while it is held, and nothing once it is closed or where it belongs. Compact Row Says Where It Moves fills a required field in Needs attention: the note appears, the editor stays open, and it goes once the field is closed.
|
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 |
…ot hashes, and custom Text values as the templates they are
From 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,
`[object Object] > [object Object]`.
The Text view handed `dpql/serialize` the AST as the Visual view stores
it, and two operand shapes are not DPQL: an operand whose operation is not
chosen yet, or that is not filled in, and a custom Text value, which is a
rich-text document. The server writes them as hashes
(`{args=(null)} > {type=int}`), and a stand-in as `[object Object]`.
- An expression with a part not filled in is not serialized: the Text view
shows a hole for each such part, `… > …`, as the row summary does.
- A rich-text operand is written as what it holds: a lone template as that
template (a field becomes `@field`), words as their string.
Tests: expressionTextOfIncomplete (the two Text view cases fail before
the fix); story ExpressionField · Text Mode Of An Unfinished Expression.
…by the Text view and the read-first summary, and exported The Text view was not the only writer that read operands as stored: the read-first summary (renderExpressionToText) printed a custom Text value as its rich-text document and an unfilled operand as null, and an application's own summary (qorus-ide's row rule line) printed `[object Object] > …` for the same rule. expressionOperands reads an operand as what it holds - a rich-text value as its template or words, an operand not filled in as nothing yet - and is the one place that knows the shapes the Visual view stores. The Text view and renderExpressionToText use it (a part not filled in is written `…`), and it is exported (expressionOperand, expressionWithOperands, isExpressionComplete, EXPRESSION_HOLE) so an application's summaries need no copy of their own.
…hat is filled in, with its operands as they hold CI caught it: the Text view of an unfinished expression also asked dpql/renderExpression for its Preview, with the AST as stored, and the stories' stand-in server threw on the operand with no operation (Cannot read properties of null). The real server writes such operands as hashes. renderRich asks only for a complete expression, read through expressionOperands, as the Text view's serialize does.
|
Qlip build 20261007-164411 (f7424c7): 13 pending, explained, not reviewed. I have not accepted, flagged or denied any of them.
|
…n template is a chip, with text around it; the value's type is read from the text
From David's review of qorus#646: an operand of type any opened on a
pick-only "Select Template Or Field" control, and a template chosen into a
whole-number, number or yes/no field replaced it with a control that held
the template and nothing else, so no text could be written around it.
- A field that takes templates and custom values is one text field for
text, untyped (any/auto) and scalar (int, number, bool) types: a chosen
template is a chip in it, named as the catalogue names it (braced
references too - the picker exception for them predates the chip
editor).
- What the field holds is read from the text (writtenAsText):
- a template standing alone is that template, and in an untyped field
the value takes the template's own type;
- text around it makes an untyped value text; deleting that text makes
it the template, of its type, again;
- a scalar's literal (12, 3.5, true) is that literal; other text in a
typed field is kept as written for the form to flag;
- an expression written in it is detected as before (offered, or
switched to where it cannot be a literal of the type).
- A date keeps its date control, with the templates offered beside it
($): chosen, the template is the value in the template control, whose x
returns to the date. Choosing it no longer hands the reference to the
date picker first, which threw on it.
- Fixed choices (allowed values) keep their list.
- An expression's untyped operands are written as text from the start.
- The text field shrinks in a narrow operand row (no 150px minimum there).
Tests: writtenAsText (the whole-number field and the date ones fail
before), expressionNullSurvivesTemplates updated to the decided rule.
Stories: Template - Written As Text - Template Alone, Text Around The
Template, Number With A Template, 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.
… text field, the cursor in it
Deleting the template chip in a whole-number, number or yes/no field
emptied its text, and an empty text switched the field to its own control
(a number input, a checkbox): a different editor took the text field's
place, the cursor was lost, and what was typed next went nowhere. Found in
qorus#646's fill value of a whole-number column: the template deleted and
0 written instead left the value empty.
An emptied field written as text now stays the text field, as an untyped
one already did; its own control is one menu item away ("Use Custom
Value").
Stories: Template - Written As Text Number Emptied Keeps Its Field (new;
fails before), Template Value Can Be Removed (now the rule above, and the
checkbox from the menu), Written As Text Number Picks A Template (new).
…e declares for it A row rule's condition in qorus#646 (the builder's type bool) with ">" chosen showed its first value as "Unknown type!" and only its second as the text field an untyped value is now written in. An empty first operand took the builder's own type - what the whole condition gives (bool), or `context` in an untyped builder - while every other operand falls back to the type the catalogue declares for it. Once an untyped operand took custom values, that wrong type picked a custom editor that does not exist. An empty untyped first operand is now of its declared type, like the rest. Test: expressionUntypedOperandsWrittenAsText (both cases fail before).
…h room, and narrower only where its row is The previous commit let the text field a template is written in shrink below a free text field's 150px, so that an operand in a narrow column keeps its clear button and its menu inside its row. It did so by removing the minimum, which made the field as narrow as what it held in every row: a "contains" operand holding $local:input went from 150px to 132px, and qlip showed the same in a dozen expression stories. The field now has a preferred width of 150px that grows with what it holds and shrinks only where its row has less room, as a number's input does. Story: ExpressionBuilder - With Simple Value checks that each text value is 150px wide (fails before: 132px); Operands Fit Narrow Column and Nested Fits Narrow Column still pass.
…ses one
qlip showed the story's choices as two radio buttons with no names: it
rendered without a component and gave its allowed values no display
names, and its values were not in the shape a form's allowed values
carry ({type, value}), so choosing one held nothing. It only checked that
no text field was shown, so it passed anyway.
It now renders through auto, as a form does, with Open and Closed named,
and checks that both are shown and that choosing Closed holds closed.
|
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. |
…und a template is not valid, and says why
From David's decision on qorus#646: "$record:{bestellnummer} Stk." in a
whole-number field is not a whole number, and the form must say so while
it is typed and not be saved until it is fixed.
It was valid. Validation took anything starting with `$` and holding a `:`
for a template, so text around a template passed as one in every typed
field. Text with a template in it that is not the template alone now makes
a value of these types invalid, with the reason under the field:
- int: "A whole number can't have text around a template"
- number: "A number can't have text around a template"
- bool: "True or false can't have text around a template"
- date: "A date can't have text around a template"
A template alone, a literal and an expression are judged as before; text
fields are unchanged.
Tests: textAroundATemplate (fails 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.
From David's decision on qorus#646: null and no value must be told apart by the author and stay distinct in what is sent. An untyped field holding null fell to the generic custom editor, which drew it as an empty box: the same as a field holding nothing. It is now written in the field's text field as `null`, as an untyped field's other values are, and `null` written in an untyped field is null. Deleting it leaves the field empty, which is no value at all (undefined), not null. Text with null in it, and null in a text field, are text. The written-as-text stories' harness now says "(no value)" for an emptied field rather than printing it as null. Tests: writtenAsText - null in an untyped field (fails before). Story: Template - Written As Text Null Stays A Value shows null, is emptied and written null again (fails before).
|
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):
|
…, not from the list the field offers From David's review of qorus#646: in a yes/no field, the chip of the whole-number field pos read "record: pos" where every other chip reads "pos". The text field named its chips from the templates it offers, and a typed field offers only the fields of its type. A field not on that list - pos in a yes/no field, any field around which text is written - had no name to find, and its chip fell back to the reference's path. It showed in every typed field, not only yes/no: the other stories only ever chose a field of the field's own type. The rich-text field takes the catalogue chips are named from (`namingTemplates`, defaulting to `templates`); TemplateField passes the whole catalogue there and keeps offering the filtered list. Tests: writtenAsText - a chip of a field not of the value's type, in bool, number and int fields (fail before). Story: Template - Written As Text Chip Of Another Type (fails before).
…place of the field's own message
From David's decision on qorus#646: where the sheet-contract review has
its own words for what is wrong with a value ("Enter a value or an
expression", "A whole number can't have text around a field", in the
user's language), the field said its generic reason as well ("This field
is required", "…around a template"): two messages for one problem, one of
them in English.
A field's schema can carry `invalid_reason`, the host's sentence. While
it is set:
- it is the field's message, in place of the field's own validation
reason and its "This field is required" (required groups and
dependencies still say theirs);
- the field is invalid, whatever it makes of its value - the host knows
things the field cannot (a field named that is not in the record) - so
the form's validity and "Needs attention" agree with what is said.
Tests: hostInvalidReason (messages and FormEngine validity; fail before).
Stories: FormEngine - Host Reason In Place Of The Field's Own, Host
Reason In Place Of Required (fail before).
…ot as Yes Found verifying the chip fix in qorus#646: an added yes/no field holding the field pos read "Yes" in its collapsed row. The collapsed row's yes/no branch came before its template branch, and the summary called any value that is not empty "Yes". A template is not true; it is the field it names, and reads as that field's chip, as it does in every other type. Only a value that is true or false reads as Yes or No. Tests: readRowTemplateInTypedField (the summary and the collapsed row; fail before). Story: FormEngine - Yes No Holding A Template Read (fails before).
|
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):
|
…shows empty From David's review of qorus#646: "Clear value" on an added yes/no holding the field pos had no effect. The value was cleared; the field went on showing pos. The text field a value is written in keeps its own copy of the text (templateValue), set by the editor and by a template arriving from outside. Nothing set it when the value was EMPTIED from outside - the card's "Clear value", an undo, a host's reset - so the field kept showing what it had held. An outside empty value now empties the text; the editor's own emptying already did. Tests: writtenAsText - a value written as text, cleared from outside its editor (bool, int, string; fail before). Story: Template - Written As Text Cleared By Its Host (fails before).
|
Qlip build 20261008-065308 (bc24dc0): 44 pending (22 changed, 22 new), explained, not reviewed. I have not accepted, flagged or denied any of them.
|
… own control, one action opens the expression Text view From David's decision on qorus#646 (with both amendments of the analysis): - Typed options open on their own control: a number input for a whole number or a number, a checkbox for a yes/no, the date picker, the choices for fixed values. This reverses typed values defaulting to a text field with chips; text and untyped values keep it. - One field-menu action, "Use Template / Expression", replaces "Use Template" and "Use Expression" where the field takes expressions written as text: it opens the expression editor's Text view, seeded with what the field holds (a template, a number, a yes/no), the Visual view one click away and starting from that value as its first operand. Nothing is emitted until the text is edited. A template-only option (no expressions) keeps "Use Template", the picker and the bare template. - The Text view offers the field's templates as its catalogue names them ($, inserted as chips), next to the server's own `$` completion. - A saved template in a typed option that takes expressions opens in the Text view, as the template; nothing is rewritten. Saved expressions open in the Text view too. - A lone template or a lone value written in the Text view is stored as itself - the bare template, the literal - not as an expression wrapping it: the server parses a lone template into the `template` operation, whose UI form it does not read back as the template's value, and a `value(...)` expression is one the Visual view cannot show. - Without a language server (connecting fails) the Text view falls back to the Visual view and says why (DpqlEditor `onUnavailable`, from the LSP session's new `isUnavailable`). - The date's own template button goes: the action covers it. Kept: chip naming from the whole catalogue, null shown as null, the cleared-from-outside fix, the host's own reason, the read-row fix and the text-around guard (for saved values). The stories' mock language server wraps a lone template in `template(...)` as the server's parser does. Tests: templateOrExpression (the action, the Text view seed, the saved template, a lone template and a lone value stored as themselves, the text kept while held as a value, the template picker, the fallback without a language server; all fail before), writtenAsText retargeted to the decided rule. Stories: FormEngine - Typed Options Open On Their Own Control, Use Template Or Expression Opens The Text View (+ phone), Saved Template Opens In The Text View (+ phone), Saved Text With Templates Stays Text, Saved Expression Opens In The Text View, Template Only Option Keeps The Picker, Lone Template Written Is Stored Bare; the Template and ExpressionField stories that asserted the reversed rules follow the decided ones.
|
Two more fixes, found verifying the Qog options against hq: c435ea1, 1a6de5f. Nothing accepted.
CI: run #487 passed on its re-run. Its first attempt failed Template › Written As Text Null Stays A Value with qlip: 20261010-143734 is the same as 20261010-133232: 627 unchanged, the same 5 new, the same 2 pending since the caret change, and 1 carried-forward rejection. No story shows a collapsed |
…ch React 19 renders as designed (qorus#646)
react-color's Sketch picker is a function component that sets its
defaults (its width, its preset colours, whether it shows transparency)
with defaultProps, which React 19 no longer applies to function
components; React 18.3 already warns about it (for Sketch and
Checkboard) on every page that shows an rgbcolor field.
The field now uses @uiw/react-color-sketch 2.10.3 and still stores
{ r, g, b, a }:
- a colour the picker sent is handed back to it as it sent it, so a
grey keeps the hue it was chosen from (the hue slider no longer jumps
to red), and a hue moved on a grey stores nothing
- a value stored as hex (bare or { hex }) is shown, as readFirst reads it
- without its slider the transparency is kept, not reset by a hex or a
swatch
- disabled and read-only now take effect: the picker is inert (pointer,
keyboard and assistive technology), and disabled is dimmed; Field and
AutoFormField pass both instead of spreading every field prop into
the picker
- theme text colours, flush with the field, the compact 100px colour
area; on a coarse pointer the hue bar is 24px and the swatches and
inputs 44px, with 16px input text
- @uiw/color-convert and @babel/runtime (a peer of the @uiw packages)
are declared; react-color and @types/react-color are gone
Tests: __tests__/colorFormField.test.tsx (the defaultProps warning test
fails on react-color), stories With Value, Red, Empty, Written As Hex,
Chosen From A Swatch, With Transparency, Disabled, Read Only, On A Light
Theme and On A Touch Screen (390px, coarse pointer, a tapped swatch).
…what it means, in the Visual and the Text view (qorus#646) David's verdict on a condition that does not give true or false, as qorus-ide's condition editors say it: the Qorus server reads a condition - a Qog transition's, an if state's, a while or for block's - as true unless its value is zero or empty (Fsm.qc calls .toBool()), so: - true or false, or a type it cannot tell (any, auto): nothing - text, a date or a time: refused, "A condition can't give Text: compare it explicitly, for example @status == "open" or @note != """ - any other known type (a number, a list, a hash): accepted, with a warning: "This condition is true whenever its value isn't zero or empty. To say what you mean, compare it explicitly." A condition is a field the server marks as one: its schema's metadata.condition (the Qog transition's value, the built-in if's value, the while and for blocks' condition; qorus "feat(qog): the options the runtime reads as conditions say so in their schema", FsmConditionOptions). A value of type bool is not one: the server checks it as a bool (applyTargetTypeConstraint) and an int does not fit it, so it keeps its type check - which d7f3fc3 had relaxed for every bool field. - TemplateField reads the mark (isConditionOption) and hands ExpressionField and the builder `condition` - Text view: a condition is asked as one - dpql/parse with `condition: true` and no target type (qorus "fix(dpql): a condition is judged as true or false, and a lone field gives its own type") - and the answer's condition_reading is shown, in the server's own words (NOT_A_CONDITION / CONDITION_BY_TRUTHINESS diagnostics, in its presentation language); a server that does not know the question answers with the inferred type, which is read the same way - Visual view: under the condition itself, the warning or the refusal; the operations offered narrow to those that decide a condition only for a condition; each part of an AND or an OR keeps d7f3fc3's reading - the parse API takes { condition } (IDpqlParseOptions), the result carries condition_reading; the mock language server answers the question as the server does - conditionVerdict, conditionErrorText, CONDITION_BY_TRUTHINESS_TEXT and isConditionOption in helpers/expressions; isConditionType takes softbool too Tests: __tests__/expressions.test.ts (the verdict), expressionFieldTypeFitMessage (the question asked, the server's words, the reading of an older server, true or false, a bool value's type check; the condition cases fail on the old code), and expressionFieldSeededTypeCheck. Stories: ExpressionBuilder > Condition That Gives A Number (and On A Phone), Condition That Gives Text, Bool Value That Gives A Number; ExpressionField > Text Mode Condition That Gives A Number (and On A Phone), Text, True Or False, Bool Value That Gives A Number.
…rver's errors, as the Text view does - a hash plus a number, say (qorus#646) David's decision: `@create + 1` (a hash plus a number) keeps Qore's run-time result but is an error in the editor. Qore's parser refuses it (DPQL-E050), and professor's language server (Qore 8417bde52) says so for the text with severity 1, in its words: "Operator '+' cannot be applied to '@create' (a hash) and a number: it adds numbers, or a number and a date; ...". - Text view: already shown as an error - the language server publishes it, and the editor draws it under the text at the danger intent and as a red underline. Each diagnostic message now says what it is (`data-severity`: danger, warning or info). - Visual view: said nothing, as no check on this side can tell what + combines. It now asks the server about the expression it builds - dpql/serialize, then dpql/parse, on a document bound to the record's fields as the Text view's is - and shows the server's errors under the builder (.expression-server-errors), at the danger intent. Only while it shows a whole expression and has the record's fields or a provider to type its operands by, so a form of untyped expressions opens no document for it. - The stories' language server answers as professor's does: it reads `@field` (as `$record:{field}`), refuses a hash and a number with DPQL-E050, and publishes it on every open and change. Stories (Components/Form/Expressions/ExpressionField): Text Mode Operands The Operator Cannot Combine; Visual Operands The Operator Cannot Combine (and On A Phone), which fail without the change; Visual Operands That Combine guards that a sound expression shows nothing. Tests: __tests__/dpqlMockLanguage.test.ts.
…says what the server says of it (qorus#646)
David's decision (A): each part of && || ! is read as true or false by its value, the same everywhere in Qorus,
and the server judges each part by the rule it judges a whole condition by. The server side is "fix(dpql): each
part of && || ! is read as a condition, and judged as one (#646)": && || ! take any value, and dpql/parse with
condition: true answers `parts` ({type, reading}, in its walk order) with a NOT_A_CONDITION error or a
CONDITION_BY_TRUTHINESS warning for each part that is not true or false.
- The Visual view asks the server about a condition it builds as a condition (`condition: true`), with or without
the record's fields, and draws each part's verdict on that part's card, in the server's words: a warning for a
number, an error for text. `conditionPartCards` places the parts by the server's walk (a nested AND or OR stands
for its own parts; the part a NOT reads goes on the NOT's card), and places nothing when the answer is not about
the expression on screen. `conditionPartsWithMessages` gives each part what the server said of it.
- No part is checked as a bool here: a part of an AND or an OR is typed by its operator's arguments in the
catalogue (any value, as the server now declares them) instead of `bool`, and a part can still be grouped into
another AND or OR.
- The error list under the Visual view leaves out the condition verdicts, which are on the cards.
- The mock language server judges the parts as the server does, its fields' types known; the mock catalogue's &&
and || take any value, as the server's do.
Stories: ExpressionField VisualConditionWithANumberPart (and OnAPhone), VisualConditionWithATextPart and
VisualConditionWithPartsThatAreTrueOrFalse failed before the change (no part was read as a condition) and pass;
the whole story suite (658 stories) passes, and the unit tests (2098), lint and the type builds. Unit tests for the card placement, the
server's words and the mock's parts.
…nd does not block a save, and a Save that refuses says why on screen (qorus#646)
Found through the IDE's API schema stories: a schema opened for editing
held `author: []` in an optional select-array field. The form filed the
field under Optional - to its status an empty list is "not set" - while
validation checked `[]` as a value ("At least one value is required").
The form was invalid, Submit refused, and nothing on screen said why.
- One rule says what is set: `isOptionValueEmpty` (no value, null, empty
text, an empty list) moves to helpers/validations.ts, and validation
reads it as the read-first status does - the options check, and the
form engine's field and form validity. An optional field that is not
set is not validated; a required one still is, so an empty required
list still blocks.
- The batched form's Save, while it refuses, shows why beside it as text
(`describeBlockedSave`): the first field that blocks it by the name the
form shows, what is wrong with it, and how many more. The reason used
to ride only on the disabled button's tooltip, which a disabled button
never raises and a phone cannot hover.
Stories: CompactBatchedCommitWithAnEmptyOptionalList (Save stayed
disabled) and CompactBatchedCommitInvalid / ...OnAPhone (no reason on
screen) failed before the change and pass; all 660 stories, the unit
tests (2105), lint and the type builds pass.
Qlip: Compact Batched Commit Invalid now shows the reason beside Save;
the two new stories are new snapshots.
…fuses what is not a colour and previews it (qorus#646) The Sketch picker gave no explanation of hex, red, green, blue or alpha, its bars and swatches were too small for a finger, its inputs accepted any text and stored whatever came of it, and nothing showed the chosen colour on its own. The field is now built from the @uiw picker's parts (saturation square, hue and opacity bars) plus its own: - a preview of the chosen colour, with a checkerboard under it so that transparency shows; while no colour is set, the preview is struck through; - labelled Hex code, Red, Green, Blue and Opacity % inputs. They accept only the characters a value can hold, keep values in range, say what was wrong under the inputs, and never store an invalid colour. A three-digit hex code is short for six, the arrow keys step a number, and Shift steps by ten; - a "What do these mean?" button that explains the square, the hue bar, the hex code, the channels (0 to 255) and opacity (0% to 100%) in plain words. Nothing is shown only on hover; - swatches that are buttons. On a coarse pointer, every target is at least 44px, and the square, the hue bar and the opacity bar can be dragged with a finger. The touch stories drive them with real touch events: Chromium's touch emulation through CDP, at phone size. The value is still { r, g, b, a }. A grey keeps the hue it was chosen from, disabled and read-only fields are inert, and with disableAlpha the stored opacity is kept.
…n a tap, on every template chip and server description (qorus#646) A template chip's hover printed its description raw (`**`, backticks) beside a template list that draws the same string through the host's markdown renderer. `templateTooltip` passed the description as plain content, and so did ReadOnlyTemplateTag, TemplateText and RichText's text chips. The same was true of the other hovers that show a server description: a choice's (allowed-value checkboxes), a schema section's (panels and tabs), and a DPQL editor's field and template references. None of them could be opened by a finger. Reqore opens a hover card on mouseenter and closes it on any click. A tap fires both, so the card closed as soon as it opened. Every description's card is now made by one helper, `descriptionTooltip`, fed by `useDescriptionTooltips`: - it draws the description with the host's renderer (compact), as the template list does. Without a host renderer the description stays as written, which is the existing contract: reqraft does not draw in a dialect the host never chose; - where the pointer is coarse (`useCoarsePointer`), the card opens on a tap and closes on the next tap. `templateTooltip` and the DPQL tag renderer take these options as an argument, so no caller can leave out the renderer or the pointer. `MarkdownRendererContext` is exported from the barrel. A host can then supply its renderer to a chip or editor mounted outside a FormEngine, and the IDE checklist names the seam. Stories (ReadOnlyTemplateTag, TemplateText, RichText) hover with a mouse and tap with a real finger at phone width. All six fail without this change, and the stories without a host renderer pass both before and after.
… are dispatched too (qorus#646) Storybook's own UI has no DevTools protocol, so its "Tapped On A Phone" plays dispatch the touches on a desktop browser, where the pointer is not coarse. The card stayed a hover card, the tap's click closed it, and the play failed there. The colour field's touch stories avoid this with `withCoarsePointer`, and the tap stories now use it too. A unit test opens and closes the card with taps on a coarse pointer. It fails with a mouse pointer, where a click closes a hover card.
…tory that loads an image from a server fails (qorus#646) Every Qorus logo on a template chip or template-list row was an empty slot in every capture. The template fixtures named the logo https://hq.qoretechnologies.com:8092/api/public/apps/Qorus/qorus-logo.svg, which the server never served: it answers 404 and names the path `.../apps/Qorus/logo` (DataProviderAppsRestClass::doGetLogo). The request went to the live server anyway, because the story network guard wrapped only fetch, XHR, WebSocket and beacons, and an <img> loads its src by itself. The Select stories loaded a GitHub avatar the same way. - The fixtures keep the URL the server gives an app's logo, with its path corrected. `__fixtures__/templateFixtures.ts` swaps in a local copy of that logo (src/stories/Data/qorusAppLogo.svg), matched by the server's path on any instance, and the stories import the fixtures from there. The Select stories' items use the same local image. - The guard also covers what an element loads. An <img>, <source> or srcset, a frame, a script, a stylesheet or a medium whose URL is on another origin is pointed nowhere and named. It is caught where it is set: setAttribute, which React uses, and the element's URL property. Anything else loaded from another origin, such as a CSS url() or a font, is named from the browser's resource timing. A story that does either fails and names the load. Before this change, Select fails 2 stories and TemplateField fails 11 under the extended guard, each naming the image. After it, all 56 story files pass (673 stories).
…rags, as in the test run (qorus#646) Without the DevTools protocol, a story's touches are dispatched events (`withTouchScreen` in Storybook's own UI). A drag sent its touchstart and every touchmove in one burst. The colour picker's drag handler (@uiw/react-drag-event-interactive) starts listening for moves in an effect after the touch begins, so it heard none of them. Only the starting point registered, and Color › On A Touch Screen and With Transparency On A Touch Screen failed their plays in Storybook while passing in the test run, where each touch waits for a protocol round trip. A dispatched drag now lets the page draw a frame after the touch starts, after each move and after the release, as a finger's events arrive a frame or more apart.
…ys its other descriptions without their markdown (qorus#646)
A read-only row printed the chosen option's description as plain text,
while the server writes it in markdown: the IDE's AI endpoint and ML model
rows read "Each `POST /conversations` ...", backticks included. The same
was true of the two other read-row slots that show a server description:
the field's short description under its name, and the reasons under the
value, among them a default's description ("Default: thirty - Falls back
to `30` seconds").
- The chosen option's description has a line of its own, so the host's
markdown renderer draws it (compact), as it draws the schema messages
below it. Without a host renderer it reads as the prose its markdown
renders to.
- The field's short description (clamped to three lines in the label's
type) and the reasons (one line in the row's type) cannot hold the
blocks a renderer draws, so they read as that prose
(`markdownToProse`: `summariseMarkdown` without its cap).
Stories: Compact Read Only Choice Description Drawn (and On A Phone), and
Without Renderer. All three fail without this change.
…es, so a chip's card is not shown twice (qorus#646) RichText › Template Tapped On A Phone showed the description twice in Storybook's own UI: in the chip's card and in the editor's template list, opened beside it. Only the UI did this; with a real finger (the test run's DevTools touch) the list stays closed. The simulated tap focused the element under the finger only when that element was itself in the tab order. Under the finger was the chip's label, a span, so nothing took the focus. A browser moves the focus to the nearest focusable ancestor, here the chip. When the chip handed the caret to the editor, the editor saw the focus come from outside the field and opened its template list. The simulated tap now moves the focus as a browser does: to the nearest element that a click can focus, or nowhere, and not at all when the page cancels the press. The card stories now also check that the description appears once: nothing else open beside the card repeats it.
…ain text, never as markdown source (qorus#646) TemplateText › Without A Host Renderer showed "The **customer's** full name ... `customer.name`" with its marks. Where no host renderer was supplied, a server description was left "as written": the description cards (template chips, choices, schema sections, DPQL references), the template list's rows, a read row's schema messages and chosen-option description, and a field description's collapsed line. Each of these places now shows the plain text its markdown renders to: the words, with paragraph breaks and list items on their own lines, and no marks (`markdownToPlainText`; `markdownToProse` for a one-line slot). The stripping moved out of readFirst into helpers/markdownText, so the cards, the lists and the rows share one implementation. It keeps the CommonMark-shaped rules that leave an intraword underscore and a lone asterisk alone. Without a host renderer, reqraft still draws in no dialect of its own. The read row's one-line summary still drops fenced blocks. The Without A Host Renderer stories now expect the plain text. Both fail without this change.
…p to a readable line, so one sentence takes one line (qorus#646) ReadOnlyTemplateTag › Hover Draws The Description broke its one-sentence description onto two lines on a wide screen. That was deliberate, but too narrow: every description card had a fixed 360px max-width, and the sentence needs 462px. Cards still size to their text (a popover's content is fit-content). They now grow up to min(560px, the screen less 24px), about 80 characters of the card's 12px text, and never wider than a phone. All description cards share this width (template chips, choices, schema sections, DPQL references). The hover stories now check that the description takes one line at 1280px. The check measures the text's height against its line height, because `waitFor` runs again on every change to the page: a check that added a measuring copy to the page ran itself forever and hung the story run. It fails with the old 360px.
… data, and each story shows one thing happen (qorus#646) From David's review of the Color stories: - The labels (Hex code, Red, Green, Blue, Opacity %) sat at the left of inputs whose values are centred. Each label is now centred over its input. - The help and the messages under the inputs quoted values as plain text (6495ED, “G”). Both are now markdown, with each value as a code span, drawn in the data style by the host's markdown renderer, or by reqraft's own where there is none (this is the field's own text). The help's four paragraphs keep their spacing. - Hex Takes Only A Hex Code re-rendered visibly about three times before settling. A mount and render trace of the served story shows one mount and one render, so this was not a render loop, an effect or a remount. It was the story's play: one story typed three scenarios in turn (a wrong character, a code too short, a three-character code), and each changed the colour, the field and the message. Each scenario is now a story of its own: Hex Takes Only Hex Characters, Hex Of The Wrong Length Is Not Stored, Hex Of Three Is Short For Six. The other colour stories that chained scenarios are split the same way. Numbers Stay In Their Range becomes Red Past Its Range Is Not Stored, Green Takes Only Digits, Opacity Left Empty Shows Its Value Again and Arrow Keys Step A Number. On A Touch Screen becomes four stories, one per gesture. The help story opens the help once. Ten of the stories check centred labels and data-styled values on desktop, touch and phone, and all ten fail on the previous component.
…does only what it does; its description opens from an ⓘ beside it (qorus#646) On a touch screen a description card opened on a tap of what it describes. For a template chip that is right: the chip does nothing else. For a control that does something, it was not: a tap on a choice's checkbox ticked it and opened the card, and a tap on a schema section's tab, or the header of a collapsible section, switched or opened it and opened the card too. Such a control now shows its description the way the pointer allows (`describedControl`): - with a mouse, as its hover card, unchanged; - on a touch screen, never on the control itself. A finger-sized ⓘ (Reqore's 48px control) beside it opens the card, titled with the control's name: beside each checkbox (`besideItsInfo`), in the content of a section tab, and among the header actions of a collapsible section (`describedPanelProps`). It applies to the allowed-value checkboxes (both FormField sets and the check group), the schema editor's table-section tabs, and its collapsible sections. Stories: Field › Choices Described On Hover and On A Touch Screen, and SchemaDefinition › Sections Described On A Touch Screen. Both touch stories fail without this change.
…in the server's words, the Visual view too (qorus#646)
The condition and type-mismatch texts follow David's approved rewrite: "condition"
where the value is used as one, "expression" elsewhere, plain words.
- A condition that gives text, a date or a time: "The condition evaluates to
{type}, but it must evaluate to true or false. Compare the value instead, for
example {example}", with an example for its type (@status == "open",
@shipped_on > @ordered_on, @starts_at < @ends_at). conditionErrorText takes
the display name and the base type.
- A condition read by its value is said by its type: a number (true unless 0),
a list (unless empty), a set of fields (unless it has none), or a type not
known. conditionByTruthinessText replaces CONDITION_BY_TRUTHINESS_TEXT.
- These are the server's words, a fallback for when it said none. The mock
language server says them too.
- The Visual view showed its own fallback for the whole condition and dropped
the server's message for it. The server's words for the whole now reach the
builder (conditionWholeMessage, the builder's new conditionMessage prop), as
they reach the Text view and the part cards. The mock server now types a
condition's whole by the record's fields, as the real server does; it left a
field's sum untyped and judged nothing.
- A value of the wrong type: "This expression gives {X}, but this field takes
{Y}. Choose an expression that gives {Y}, or put this one inside an
expression that converts it." In the Text view, "The expression gives {X},
and this field takes {Y}." plus "Qorus converts the value when it runs, and
checks it then." Display names, not raw type names, in both. The mock's
TYPE_MISMATCH says the same.
New stories: ExpressionField › Visual Condition Says The Servers Words (and On
A Phone) and Visual Text Condition Says The Servers Words. Their mock server
answers in German, and all three fail without this change.
… as the other phone stories do (qorus#646) Lint requires a story's play to call another story's play with its own context directly, not from inside a callback. The phone check now follows the desktop play, as TextModeConditionThatGivesANumberOnAPhone does.
…o in plain words, and a condition field calls itself one (qorus#646)
David's approved rewrite for the expression editor:
- Builder: "Choose what this expression does.", "Qorus could not check this
expression.", "This expression runs outside Qorus", "Put this expression
inside another one", "Remove this expression and keep what is inside it" /
"... and everything inside it", group labels "If" / "All of these are true
(AND)" / "At least one of these is true (OR)", "Add a condition".
- Type modals: "This value needs one of these types:", "Use this type
anyway?", "These values have other types than expected. Qorus converts them
when it runs.", "Converted from X to Y", "Cleared, not converted", "No
conversion needed".
- Argument labels, with display names joined by " or ": "This value is not
{t}. Put it inside an expression that converts it" (which also fixes "has to
wrapped"), "The type fits; Qorus may convert the value", "{list} is needed
here".
- Validation: "Fill in this expression.", "Choose what this expression does.",
"Qorus has no operation {x}.", "The expression in {operand} has an error".
- TemplateField: "This field takes an expression.", and the detected-expression
offer reads "{text} could be an expression. It is kept as text; use it as an
expression instead?"
A condition field (isConditionOption) now says "condition" where a value field
says "expression". The Expression tab is "Condition", its tooltips are "Write
the condition - it also takes templates" and "Build the condition visually",
and the Value tab says "This condition can't be shown as a value." These are
new IValueTabsLabels keys a host can translate: condition, conditionTooltip,
conditionVisualTooltip and cannotShowCondition. The menu item is "Write a
condition", without the value's "Run a function on this value" tooltip, which
would only repeat it.
New stories, all of which fail without this change: FormEngine › Condition
Option Tabs Say Condition (and On A Phone) and ExpressionField › Write A
Condition In Form Engine (and On A Phone).
David approved "True or false" as the bool type's name, to replace "True/False",
"Boolean" and "A yes / no value", wherever reqraft names the type:
- the AutoFormField type picker;
- the template conversion text, which lowercases it ("... converted to true or
false");
- useQorusTypes' fallback until /system/qorus-type-info loads (display name,
short_desc and desc). The server's name is changing to match.
- the mock catalogue's convert-to-boolean, now "Convert to true or false". Its
descriptions will follow the server's new text once that lands.
The two validation texts David did not approve ("Value must be a True or False
value", "...must be a boolean") are unchanged.
…by its new name, True or false (qorus#646) The field menu's Set Custom Value lists the types by their display names, and the bool type is now "True or false". The play clicked "Boolean"; its description says the new name too.
…Ctrl+Space, and a finger gets the editor's hover from a Details button (qorus#646) A completion row's documentation, and what the server says of a word in the text, were hover pop-ups only. A keyboard couldn't reach them, and a finger couldn't either: a tap on a row takes the completion, and a finger has no hover. - The row the keyboard is on has an ⓘ when it has documentation. A click or Ctrl+Space (VS Code's key) shows the documentation under the row, inside the list, and the same again puts it away. The ⓘ's press neither takes the completion nor moves the focus from the text, so Enter still takes the row. On a coarse pointer every row with documentation has its ⓘ, since a finger cannot move the keyboard's row; each is 44px, and the rows drop their hover there. On desktop the hover stays. - On a coarse pointer, where the server offers hover (`hoverProvider`), a Details button under the editor shows what it says of the word at the caret, in the pop-up a mouse gets by hovering (useLspHover.showAtCaret). The signature help already follows the caret, so a finger sees it. Stories (SmartEditor): Completion Docs From Its Info Button, From The Keyboard, On A Touch Screen, and Hover Details On A Touch Screen, on a phone with real touch. All four fail without this change.
… section story waits for the table it opened (qorus#646)
SchemaDefinition › Sections Described On A Touch Screen passed in the
test run and failed in Storybook's own UI. There a tap is simulated: it
fired right after the "customers" table was tapped open, at the tab the
page was still building, and the tab never switched. A real tap, through
the DevTools protocol, comes after the protocol's round trips, by which
time the page has settled. The same simulated tap on the settled page
switches the tab.
The story now waits for the opened table's columns ("Add column") before
reaching for its tab, as a person would. A simulated tap or drag also
waits for any running animation that will end, and aims at where things
come to rest; an animation that never ends, such as a spinner, is not
waited for.
…t, Whole number, Number, Exact number, True or false, Date (qorus#646)
David's names. The server's type catalogue (QorusDataTypeCatalogue::Info,
served at /system/qorus-type-info) is the one source of a type's display
name, and the PO agent is changing it to these names. reqraft draws with
its own copy, `defaultQorusTypes`, until the server's list arrives, and
words its condition and type messages with it. That copy said "String"
for a string, so the fallback said "The condition evaluates to String"
where the server says "Text".
- The fallback list uses the names: string Text, richtext Rich text, int
Whole number, float Number, number Exact number, bool True or false,
date Date. The condition and type-fit messages take them from it
("The condition evaluates to Text ...", "The expression gives Whole
number ...").
- The type picker of an untyped value uses the same names for the types
David named: Whole number for int, Number for float.
- __tests__/fixtures/qorusTypeCatalogueNames.json is a copy of the
catalogue's names, and __tests__/qorusTypeNames.test.ts compares the
fallback list with it. It fails on the old list. To refresh the copy
when the server changes:
`yarn refresh:type-names ~/src/Qorus/git/qorus/Classes/QorusDataTypeCatalogue.qc`.
The copy holds David's names, which develop's catalogue gets with the
PO agent's change, and "Job" for job, where the catalogue's lowercase
"job" is to be fixed with that change.
… their new names (qorus#646) AutoFormField's type-picker story still listed Decimal and Integer, the names the picker gave float and int before 9c4e1a5.
|
25 new commits (1a6de5f → 7b04038), from David's #646 review. Nothing accepted in qlip. There is no open GitHub review or inline comment. Foxhoundn's three qlip rejections are answered on their threads in qlip (see the end of this comment). Colour field (
Descriptions (template chips, choices, schema sections, DPQL references)
Smart editor: 399f0d1
Condition and expression texts (the approved #646 rewrite)
Type names: 88a8014, 87b675d, 9c4e1a5, 7b04038
Forms: 159b6d0. An optional list field holding an empty list does not block a save, and a Save that refuses says why on screen. Story harness (a201809, cf50380, 5d1fcd8, bf311a9)
Foxhoundn's qlip rejections (answered in qlip as well)
|
…ation never reaches another file (qorus#646) The same race reqore's CI hit on #708. A touch story turns on Chrome's touch emulation through the DevTools protocol (`withTouchScreen`), and that emulation belongs to the whole page, so a story file running beside it, in another frame, sees a coarse pointer and no hover: a hover story would find a tap-to-open card, and a mouse-sized control its touch size. reqraft's last run happened not to overlap. The storybook project now runs one story file at a time (`fileParallelism: false`).
…ium cannot give a page its mouse back (qorus#646) Turning Chromium's touch emulation off does not restore the hover and pointer the browser was launched with: it takes the ones the machine reports. Playwright launches its headless Chromium as a mouse (--blink-settings=primaryHoverType=2,...,primaryPointerType=4), and a Linux CI runner reports no mouse, so after the first touch story every later story in the same page found (hover: none). 052ba87 ran the story files one at a time, which put every file in that page, and FormEngineRemote's Injected Compact Option Actions failed in CI: its hover-only action was drawn at rest. A Mac reports a mouse, so the same run passes there: a Chromium launched with blink-settings that say no mouse comes back from the emulation with the Mac's mouse, not with its launch settings. The stories that call withTouchScreen, directly or through playTapIsDrawnOnAPhone, are tagged 'touch' and run in a Vitest project of their own (storybook-touch): their own browser page, after the other stories, one file at a time. The other stories run in parallel again, in pages that never were phones. Both projects share one qlip plugin, so a run is still one qlip build. withTouchScreen fails, with the reason, anywhere but storybook-touch, so an untagged touch story cannot bring the problem back.
|
2 new commits (7b04038 → f3f5d3f): the story run's touch screen no longer leaks into other stories. Nothing accepted in qlip.
CI at f3f5d3f
|
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.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.Version 0.10.62.
🤖 Generated with Claude Code