Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
271 commits
Select commit Hold shift + click to select a range
8f31807
test(form-engine): Compact Row Says Where It Moves is not snapshotted…
davidnich Oct 8, 2026
8b7a187
fix(expressions): an operand named like another in its call is said b…
davidnich Oct 8, 2026
b7a3298
fix(form-engine): where an open field moves is said once per form, on…
davidnich Oct 8, 2026
4ac092c
chore: 0.10.62 - 0.10.61 was published by #139
davidnich Oct 8, 2026
bf948c6
Merge develop into bugfix/typed-fields-templates-alignment: #139 (0.1…
davidnich Oct 8, 2026
86641aa
feat(form): a form option's value is entered on tabs - Value · Expres…
davidnich Oct 8, 2026
445d1a3
feat(form): the value tabs' words are the host's to give - English wh…
davidnich Oct 8, 2026
bc2889b
feat(form): a whole number or a number on the Value tab is typed on a…
davidnich Oct 8, 2026
07e5333
fix(expressions): a lone operand of a call that can take more says it…
davidnich Oct 8, 2026
2aa29bd
fix(expressions): the Text view's template picker is as tall as the t…
davidnich Oct 8, 2026
5da9d74
fix(form): an empty value opens the Expression tab empty, not on conc…
davidnich Oct 8, 2026
c780b93
feat(form): a host keeps values for reuse and says where a template c…
davidnich Oct 8, 2026
ab3fc8b
fix(form): an edit made just before its field goes away is kept, as a…
davidnich Oct 8, 2026
d801fb4
fix(form): what was typed on the Expression tab is kept when the tab …
davidnich Oct 8, 2026
1fe361b
fix(form): a value written as text on the Value tab takes its row's w…
davidnich Oct 8, 2026
a2fee7a
fix(form): a yes / no that holds no value shows no answer (qorus#646)
davidnich Oct 8, 2026
886cd26
fix(form): an expression opened in the Text view is checked against t…
davidnich Oct 8, 2026
7daa068
fix(form): the Text view waits for its language server's own signal a…
davidnich Oct 8, 2026
027b6ee
feat(form): a lone template written on the Expression tab is kept as …
davidnich Oct 8, 2026
0697284
fix(form): an expression's Text view lists the field's templates when…
davidnich Oct 9, 2026
ccd8647
feat(form): two lists, one family - what is typed is completed, a fie…
davidnich Oct 9, 2026
46bf4ab
fix(dpql-editor): a chip that starts or ends a line has text beside i…
davidnich Oct 9, 2026
ce7d1f7
test(smart-editor): With Markdown Docs types one key at a time, after…
davidnich Oct 9, 2026
056660c
fix(form): an edit undone while its emit is in flight reaches the par…
davidnich Oct 9, 2026
80b9c2d
docs(template): the template stories say what a typed field takes and…
davidnich Oct 9, 2026
94dcae3
fix(template): the Expression tab's Text view shows the expression th…
davidnich Oct 9, 2026
15c07b1
fix(template): an expression with no value tabs opens on its Text vie…
davidnich Oct 9, 2026
4a8cfad
fix(template): an expression editor mounted again opens on the view t…
davidnich Oct 9, 2026
4a9148e
fix(form-engine): a form whose host is still loading its schema emits…
davidnich Oct 9, 2026
91971c6
fix(expressions): leaving the Text view without an edit never changes…
davidnich Oct 9, 2026
a0f70cf
fix(expressions): a value the server will not write as text is built …
davidnich Oct 9, 2026
8f9aa65
feat(form-engine): a collapsed row says what its editor reports, wher…
davidnich Oct 9, 2026
592c7f1
fix(menu): a menu item's own props never reach the DOM, and its activ…
davidnich Oct 10, 2026
e7d853b
fix: reqraft components hand the Reqore components they draw only tho…
davidnich Oct 10, 2026
a21b67d
ci: the tests run names only a version npm has, worked out as publish…
davidnich Oct 10, 2026
9d9d776
ci: the Discord message's qlip link goes to the build the run uploade…
davidnich Oct 10, 2026
6277dbb
test(stories): no text caret in a story capture (qorus#646)
davidnich Oct 10, 2026
80ac509
build(package): the npm package ships the build, its README and LICEN…
davidnich Oct 10, 2026
e1d99ba
fix(form-engine): what a form completes behind its host's skeleton is…
davidnich Oct 10, 2026
ca993b1
test(stories): every request a story makes is mocked, for whatever in…
davidnich Oct 10, 2026
ab2c9cc
test(stories): a story that requests what no mock answers fails, and …
davidnich Oct 10, 2026
c435ea1
fix(expressions): a row summary writes `+` and `*` of more than two o…
davidnich Oct 10, 2026
1a6de5f
fix(allowed-values): a check group's choices each have their own key …
davidnich Oct 10, 2026
678248c
fix(color): the colour field's picker is @uiw/react-color-sketch, whi…
davidnich Oct 11, 2026
9f48147
fix(expressions): a condition that is not plainly true or false says …
davidnich Oct 11, 2026
0ab22e1
fix(expressions): an expression built in the Visual view shows the se…
davidnich Oct 11, 2026
6e2612d
fix(expressions): each part of an AND, an OR or a NOT in a condition …
davidnich Oct 11, 2026
159b6d0
fix(forms): an optional list field holding an empty list is not set a…
davidnich Oct 11, 2026
98c1489
fix(color): the colour field explains its values, works by finger, re…
davidnich Oct 12, 2026
56a559f
fix(templates): a description's card is drawn as markdown and opens o…
davidnich Oct 12, 2026
3239b6a
test(templates): a tap opens a template chip's card where the touches…
davidnich Oct 12, 2026
a201809
test(stories): an app's logo is served from the story itself, and a s…
davidnich Oct 12, 2026
cf50380
test(stories): a finger dragged in Storybook's own UI moves what it d…
davidnich Oct 12, 2026
3c8f45e
fix(form-engine): a read row draws what a chosen option means, and sa…
davidnich Oct 12, 2026
5d1fcd8
test(stories): a tap in Storybook's UI moves the focus as a finger do…
davidnich Oct 12, 2026
0ee200c
fix(descriptions): without a host renderer, a description reads as pl…
davidnich Oct 12, 2026
8559a70
fix(descriptions): a description's card is as wide as what it says, u…
davidnich Oct 12, 2026
d325959
fix(color): labels are centred over their inputs, values are drawn as…
davidnich Oct 12, 2026
c2913e4
fix(descriptions): on a touch screen, a tap on a choice or a section …
davidnich Oct 12, 2026
25a6bbf
fix(expressions): a condition and a value of the wrong type are said …
davidnich Oct 12, 2026
70787a8
test(expressions): the server-words phone story runs its desktop play…
davidnich Oct 12, 2026
0452813
fix(expressions): the editor, value tabs and validation say what to d…
davidnich Oct 12, 2026
88a8014
fix(types): the bool type is called "True or false" (qorus#646)
davidnich Oct 12, 2026
87b675d
test(stories): FormEngine › Option With Any Type picks the bool type …
davidnich Oct 12, 2026
399f0d1
feat(smart-editor): a completion's documentation opens from its ⓘ or …
davidnich Oct 12, 2026
bf311a9
test(stories): a simulated finger taps what has come to rest, and the…
davidnich Oct 12, 2026
9c4e1a5
fix(types): a type is called what the server calls it: Text, Rich tex…
davidnich Oct 12, 2026
7b04038
docs(stories): the type picker story's description lists the types by…
davidnich Oct 12, 2026
052ba87
test(stories): story files run one at a time, so a touch story's emul…
davidnich Oct 12, 2026
f3f5d3f
test(stories): a touch story runs in a page of its own, because Chrom…
davidnich Oct 12, 2026
0cd60c1
fix(form-engine): a read row says reqraft's own reasons as they are, …
davidnich Oct 13, 2026
8b305d2
fix(smart-editor): a completion answer the list is no longer waiting …
davidnich Oct 13, 2026
8bbfbb0
test(stories): the touch stories' browser is a phone from the start, …
davidnich Oct 13, 2026
063db3f
docs(stories): Shows Templates List For Boolean says why a yes/no tak…
davidnich Oct 13, 2026
465c228
fix(form-engine): a template or an expression on a colour or a file o…
davidnich Oct 13, 2026
3af3187
fix(types): a type Qore also names another way is called what the cat…
davidnich Oct 13, 2026
c57f96f
feat(color-value): ReqraftColorValue - a stored colour, read-only, as…
davidnich Oct 13, 2026
de1a47d
test(stories): Compact Colour Rows - a compact form's colour rows in …
davidnich Oct 13, 2026
9ce621f
fix(color-value): a colour is as wide as what it shows, even as the i…
davidnich Oct 13, 2026
79448e2
fix(markdown): markdown read as text keeps what a code span holds and…
davidnich Oct 13, 2026
0b52c5e
fix(color-value): in a control group, a colour takes none of the grou…
davidnich Oct 13, 2026
890d2ea
fix(array-field): the entry input's key goes on an element, not in th…
davidnich Oct 13, 2026
e588c39
fix(menu): the filter holds text from the start, so the first key doe…
davidnich Oct 13, 2026
e8d9b3f
fix(attachments, lightbox): Enter and Escape let a rename input go of…
davidnich Oct 13, 2026
9928cbe
feat(types): a type named inside a sentence is named by its sentence …
davidnich Oct 13, 2026
224098f
fix(types): reqraft's copy of the type catalogue is the server's, fie…
davidnich Oct 13, 2026
2245323
fix(types): no type has no name, and a sentence about it names none (…
davidnich Oct 13, 2026
be8e269
fix(spelling): reqraft says color, as the product and the server do (…
davidnich Oct 13, 2026
dd718c0
fix(color): a stored color is { r, g, b, a? }, and the validator says…
davidnich Oct 13, 2026
987a7d9
fix(color): one hex reader for every hex path, and a 4-digit hex code…
davidnich Oct 13, 2026
5bc2acc
fix(color): a stored color has no field beside its own, and the valid…
davidnich Oct 13, 2026
0e16acb
fix(spelling): reqraft writes American English throughout, by the IDE…
davidnich Oct 13, 2026
a1eac0c
fix(color): the color field's value is typed as what it reads, so a h…
davidnich Oct 13, 2026
17bb2cc
test(spelling): americanSpelling reads by exactly the IDE's rules, a …
davidnich Oct 13, 2026
ad3c3ff
test(color): a null opacity is an opaque color, valid, read and shown…
davidnich Oct 13, 2026
79f62fe
test(spelling): a story's name is read as Storybook builds it, as the…
davidnich Oct 14, 2026
9db1a1d
fix(form-engine): a row's reason starts on its value's line, beside a…
davidnich Oct 14, 2026
4d347d1
fix(color): a picked swatch's ring and the picker's markers are drawn…
davidnich Oct 14, 2026
47f2409
fix(templates): a template picked after an @ takes its place, and is …
davidnich Oct 14, 2026
5e13253
feat(form): a field whose dependency does not hold is not in the form…
davidnich Oct 14, 2026
2f67052
test(stories): the reason beside a dash is a host's reason, as a fiel…
davidnich Oct 14, 2026
9501e58
fix(types): the hash type is "Key-value pairs (hash)", as the server'…
davidnich Oct 14, 2026
dc4fc3f
fix(expressions): the builder shows the type the server gives an expr…
davidnich Oct 13, 2026
4c989ac
fix(expressions): a condition's verdict is judged by the type the ser…
davidnich Oct 14, 2026
1c78382
fix(auto-field): a field is drawn as the type it is declared from its…
davidnich Oct 14, 2026
07576d2
fix(rich-text): a rich-text field holding a string draws its referenc…
davidnich Oct 14, 2026
c528388
fix(smart-editor): a hover's popover dismisses its own hover, never t…
davidnich Oct 14, 2026
51bc71b
fix(smart-editor, form-engine): a tap opens no hover; only a pointer …
davidnich Oct 14, 2026
8444150
test(stories): a story that rests the browser's mouse puts it back of…
davidnich Oct 14, 2026
84e1860
fix(form): a value picked from the allowed values is not an expressio…
Oct 14, 2026
33ebb03
test(stories): an auto field set to null gives a value, not an expres…
Oct 14, 2026
2652b8f
fix: reqraft's own words and marks read on what they are drawn on (qo…
Oct 15, 2026
b04990a
fix(auto-field): a host editor is never handed its own report back as…
davidnich Oct 15, 2026
4086837
feat(form): what a debounced field has reported and not yet heard bac…
davidnich Oct 15, 2026
b597e26
fix(form): Done keeps what was typed straight before it; Cancel and E…
Oct 15, 2026
480e01c
fix(form): a float field reports a decimal as the number it is (qorus…
Oct 15, 2026
e138a59
fix(form): autoFocusFirstRequired waits for the user's first move, no…
Oct 15, 2026
b04b670
fix(form): a field opened and left alone reports nothing (qorus#646)
Oct 15, 2026
b1c83fb
fix(form): a host change and an edit in the same commit are merged, n…
Oct 15, 2026
d6aa02c
feat(reference-picker): a kind the user may not list says why, in wor…
Oct 15, 2026
21d2718
chore(deps): reqraft is developed against the released reqore 0.78.5 …
Oct 15, 2026
9cd612c
fix(form): a value edited back to one reported before is reported aga…
Oct 16, 2026
aee0b91
fix(form): an opened field not set yet shows its messages (qorus#646)
Oct 16, 2026
9d31b11
fix(expression-builder): a value added takes the focus, and opens no …
Oct 16, 2026
4d4121d
fix(form): a required list with no items says it is required (qorus#646)
Oct 16, 2026
a1b6d69
fix(form): what the host changes beside a value is taken, its type in…
Oct 16, 2026
8612725
fix(form): everything the host changes beside a value is taken, not a…
Oct 16, 2026
e4fc552
fix(template): a reference reads as one chip in every view, drawn as …
Oct 16, 2026
2e33946
chore: 0.10.63
Oct 16, 2026
62c577f
chore: 0.10.62 (develop base + 1)
Oct 16, 2026
a117763
Merge #137 (bugfix/typed-fields-templates-alignment) into #142, so on…
Oct 16, 2026
780381d
Merge develop (#141, the rich-text choice chip) into #142
Oct 16, 2026
ac80cc8
chore: 0.10.63 (develop is 0.10.62)
Oct 16, 2026
824e27b
fix(expressions): an operand's text value gives way to its row with a…
Oct 16, 2026
0b675cd
fix(expressions): an operand's text value is as wide as what it holds…
Oct 16, 2026
58c58df
fix(descriptions): a touch screen's ⓘ is the size of the choice besid…
Oct 17, 2026
4688fc1
fix(smart-editor): Details sits beside the text and shows its card un…
Oct 17, 2026
2b7614e
Merge develop (#141, the rich-text choice chip) into the typed-fields…
Oct 17, 2026
abc5f44
chore: 0.10.63 - develop is 0.10.62 since #141
Oct 17, 2026
aac66a1
fix(form): a required field that arrives with the schema is the row t…
Oct 17, 2026
b222dc9
fix(color): the problem under the inputs is set apart by its own wrap…
Oct 17, 2026
51b5020
fix(expressions): the Text / Visual toggle is as wide as its words, a…
Oct 17, 2026
260083a
fix(expressions): an operand's text is as wide as what it holds throu…
Oct 17, 2026
e31bae1
refactor(template): one reference chip for every view, its label in t…
Oct 17, 2026
ccb9fc2
fix(dpql): an expression's text is drawn at the field's size, as its …
Oct 17, 2026
64952bb
fix(smart-editor): a reference typed out in full becomes its chip onc…
Oct 17, 2026
639dd1f
test(stories): a reference is drawn as its chip when its catalog arri…
Oct 17, 2026
64a5173
Merge #142 (bugfix/template-chip-rendering, 639dd1f9) into #137, at D…
Oct 17, 2026
efb2eb0
fix(expressions): an expression that does not parse is the field's dr…
Oct 17, 2026
249280b
fix(dpql-editor): a quoted field is its chip, and a reference ends wh…
Oct 17, 2026
38d812a
fix(smart-editor): an expression is written in reqore's mono stack, a…
Oct 17, 2026
6211b79
fix(templates): a template's braced value ends where Qorus ends it: b…
Oct 18, 2026
0b6a6df
feat(dpql-editor): a field's chip takes the accessors after it: @crea…
Oct 18, 2026
a65477e
feat(expressions): a field path is drawn as the path; an operation fl…
Oct 18, 2026
3986382
fix(stories): a story's description is in American English: catalog (…
Oct 18, 2026
a549da3
test(stories): a template whose braced value holds braces is one chip…
Oct 18, 2026
68b3c8b
fix(dpql-editor): a chip takes the accessors as the server now writes…
Oct 18, 2026
5ea1b30
fix(smart-editor): a chip is hovered as the text it replaces, and a h…
Oct 18, 2026
92078d8
test(stories): the stories' stand-in servers answer hovers as the liv…
Oct 18, 2026
a68c62d
fix(form-engine): Cancel, Escape and Revert put an expression back as…
Oct 18, 2026
b9b8891
fix(template-field): the Value tab draws an expression that is one te…
Oct 18, 2026
44c9a2b
test(stories): the stories' server writes a record field as the Qorus…
Oct 19, 2026
1fe0d10
test(stories): the stories' server writes a record template as the Qo…
Oct 19, 2026
d570b90
fix(dpql-editor): a record field is one chip: the template chip, labe…
Oct 19, 2026
9171f12
feat(form-engine): a host's tall editors open in the card; no preview…
Oct 19, 2026
efca71f
feat(form-engine): a compact row stacks where its value would be narr…
Oct 19, 2026
a58e9f3
Merge develop (0.10.63, #146's dependency refresh) into the #137 work
davidnich Oct 20, 2026
90fab5b
chore: 0.10.64 (develop is at 0.10.63)
davidnich Oct 20, 2026
f6758f1
Merge develop (0.10.64: #146 and #147's dependency updates) into #137…
davidnich Oct 21, 2026
a2967c0
fix(websocket): a socket whose handshake hangs is closed and replaced…
davidnich Oct 21, 2026
8fd61f3
chore(deps): reqore 0.78.10 (qorus#646)
davidnich Oct 21, 2026
3de4475
test(stories): the experiment card's stories read a version's actions…
davidnich Oct 20, 2026
dd113c0
test(stories): the compact array field's empty story finds its confir…
davidnich Oct 21, 2026
6439892
test(stories): the sheet contract stories wait for the contract's row…
davidnich Oct 21, 2026
b89f99f
fix(expression-field): a record field compared as it is drawn: no Pre…
davidnich Oct 21, 2026
62fcecc
Merge #137's remote head (the WebSocket handshake deadline, develop 0…
davidnich Oct 21, 2026
b1aff2d
fix(form-engine): a click on a compact row opens or closes it by what…
davidnich Oct 21, 2026
72a07c8
fix(smartEditor): a hover is of the character under the pointer, and …
davidnich Oct 22, 2026
7f8feb9
fix(smartEditor): Details is its icon alone in an editor row a phone'…
davidnich Oct 22, 2026
815ca13
fix(templates): a reference chip longer than its line wraps inside it…
davidnich Oct 22, 2026
071d417
test(dpqlEditor): the hover stories are captured with their hover ope…
davidnich Oct 22, 2026
43719ae
fix(smartEditor): a chip's list closes on any click outside it, on a …
davidnich Oct 22, 2026
999c0db
fix(smartEditor): the signature help is of the caret as it was when t…
davidnich Oct 22, 2026
5a27f9f
fix(smartEditor): the Details card opens under the editor's row, clea…
davidnich Oct 22, 2026
4cfeeb3
fix(templates): the one reference chip is small, big enough to press,…
davidnich Oct 22, 2026
3395362
test(form-engine): the late-fields story is named and described for w…
davidnich Oct 22, 2026
ac42be9
feat(form-engine): an editable row's long value wraps to three lines,…
davidnich Oct 22, 2026
a4ac76f
test(expressions): a text value is no narrower than 150px, and a temp…
davidnich Oct 22, 2026
76923a3
chore(deps): reqore ^0.78.11, where a rich text editor chip is the si…
davidnich Oct 22, 2026
718c27b
test(stories): where there is no mouse to move, a story's mouse sends…
davidnich Oct 22, 2026
67ff13a
fix(form-engine): a row lights up under a pointer that hovers, not af…
davidnich Oct 22, 2026
d27dd1d
fix(smartEditor): a hover is of what the pointer is on in the editor,…
davidnich Oct 22, 2026
3c8cb21
fix(fields): a one-line text field shows all its text and offers no r…
davidnich Oct 22, 2026
2ad9230
fix(template-field): an expression typed as a value is offered again …
davidnich Oct 22, 2026
49ef816
fix(template-field): an expression's Text view offers the fields of t…
davidnich Oct 22, 2026
be8c31f
fix(expressions): a function in a read-only expression says what it i…
davidnich Oct 22, 2026
4561055
fix(expressions): a template picked after a typed $ takes its place, …
davidnich Oct 22, 2026
c19aa42
fix(templates): a reference's hover says what kind of value it is, in…
davidnich Oct 22, 2026
336e0f7
test(stories): an option in A Value That Mentions A Template Reads As…
davidnich Oct 22, 2026
a05534c
feat(expressions): one completion list for what can be written at the…
davidnich Oct 22, 2026
6d90440
feat(form-engine): an open option says what it takes in words, and it…
davidnich Oct 22, 2026
b4c0b84
fix(expressions): every pick from the one list makes a value where it…
davidnich Oct 22, 2026
486122c
fix(expressions): a field written alone is that field in every view, …
davidnich Oct 22, 2026
7e91d0f
fix(template-field): an expression typed as a value is offered, never…
davidnich Oct 22, 2026
a56d3a8
fix(form-engine): an option's type is in its hover and its help, and …
davidnich Oct 22, 2026
c748294
test(form-engine): the opening-floor story's warning is about the nam…
davidnich Oct 22, 2026
a0d9358
fix(allowed-values): a choice is read however it is written, and a su…
davidnich Oct 22, 2026
35887ef
fix(form-engine): an option compared by an operator says so in names …
davidnich Oct 22, 2026
04865d7
fix(template-field): a condition field's tab is called Expression, an…
davidnich Oct 22, 2026
5541f4b
fix(template-field): a text option that takes templates only is its v…
davidnich Oct 22, 2026
0d4ff25
feat(form-engine): every option with a description or a type in words…
davidnich Oct 22, 2026
33c17eb
fix(validation): a value a pattern refuses says why, in words the aut…
davidnich Oct 22, 2026
de40ed4
feat(templates): a reference nothing names says so, and a host can na…
davidnich Oct 22, 2026
8a19474
test(stories): help and descriptions in the author's words, not paths…
davidnich Oct 22, 2026
68ee317
feat(form-engine): code is shown in the host's editor read-only, and …
davidnich Oct 22, 2026
a2a0cd4
test: a template-only text field has no tabs, and a pick-one row's fi…
davidnich Oct 22, 2026
9c83dc8
test(stories): the plays the full run caught follow this round's chan…
davidnich Oct 22, 2026
6eb1b8c
fix(templates): a template the host names a source for can be picked …
davidnich Oct 22, 2026
477ccc1
fix(expressions): an empty expression says what it lacks once, and th…
davidnich Oct 22, 2026
88a2c8d
fix(templates): a reference is called unlisted only by a catalogue th…
davidnich Oct 22, 2026
b127ff5
fix(form-engine): a chosen choice is marked without color, and help s…
davidnich Oct 23, 2026
728695c
test(form-engine): the host-names story is captured at its size, so t…
davidnich Oct 23, 2026
a64a874
fix(form-engine): a choice's picture is a contained image, so a tall …
davidnich Oct 23, 2026
882299c
fix(form-engine): a picture choice is laid out evenly with the others…
davidnich Oct 23, 2026
20c3bec
fix(form-engine): the allowed values table is as wide as what it says…
davidnich Oct 23, 2026
3ddcb45
fix(templates): a reference chip in an editor is the one tag, never a…
davidnich Oct 23, 2026
b1c2b61
fix(smartEditor): a hover is drawn on a card, and a chip's list opens…
davidnich Oct 23, 2026
98e20d5
fix(form-engine): an option's help at its column's edge, its asterisk…
davidnich Oct 23, 2026
a8582cc
fix(expressions): an emptied Text editor takes back the expression a …
davidnich Oct 23, 2026
30ae986
test(stories): the mouse and sticky header plays pass in Storybook's …
davidnich Oct 23, 2026
b62c14a
fix(smartEditor): a mouse already resting on the text is answered onc…
davidnich Oct 23, 2026
748602b
fix(stories): every story starts at the same instant
davidnich Oct 24, 2026
b8834ae
fix(log): the filter stays in the title bar on a phone
davidnich Oct 24, 2026
b77b347
test(stories): every story starts with an empty query cache
davidnich Oct 24, 2026
3bb54f9
test(dpql): the stand-in server says what Qore batch 6 says (qorus#646)
davidnich Oct 24, 2026
7a22834
test(dpqlEditor): the chip list's play measures the list at the chip …
davidnich Oct 24, 2026
d61681e
fix(types): the server's type catalogue decides what fits a field (qo…
davidnich Oct 24, 2026
bbc1875
feat(types): the string format types and the file type are in the bui…
davidnich Oct 24, 2026
11a773a
fix(types): a type the catalogue does not list is its base type, and …
davidnich Oct 24, 2026
9f3b767
feat(types): the package exports the type catalogue's lookups, qorusT…
davidnich Oct 24, 2026
6c79735
fix(form): a form names an unlisted field type by its base type too (…
davidnich Oct 24, 2026
640515a
test(template): stories of the templates a type only the server lists…
davidnich Oct 24, 2026
a78372e
fix(form): a field of a type with no editor is edited as its base typ…
davidnich Oct 24, 2026
f1e5de7
fix(templates): a template of a type the catalogue does not list fits…
davidnich Oct 24, 2026
1bc4478
fix(templates): every list of templates names a template's type, neve…
davidnich Oct 24, 2026
a903744
fix(expressions): an expression in a place of an unlisted type is jud…
davidnich Oct 24, 2026
a6aa12b
fix(form): a list's elements of a type with no editor are edited and …
davidnich Oct 24, 2026
8909de2
fix(templates): a group of templates keeps its app's badge, which is …
davidnich Oct 24, 2026
fb70c1c
fix(expressions): the Preview never shows an earlier expression's ren…
davidnich Oct 24, 2026
ecc845e
fix(expressions): an expression is written as text once for both view…
davidnich Oct 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
51 changes: 49 additions & 2 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ Two classes work together:

**Pooling:** By default (`pooled` not set to `false`), sockets with the same URL share one underlying `WebSocket`. Use `pooled: false` for dedicated connections. The pool key becomes `${url}::${nanoid()}` for non-pooled.

**Reconnect:** Triggered on close (unless code `4999` = intentional). Immediate first try, then `reconnectInterval` (default 5s) up to `maxReconnectTries` (default 10).
**Reconnect:** Triggered on close (unless code `4999` = intentional, or the caller closed it: a socket closed while still connecting reports 1006) and when a handshake has not opened after `handshakeTimeout` (default 15s, `REQRAFT_WS_HANDSHAKE_TIMEOUT_MS`): a hung handshake sends no event at all, and Chromium queues every other socket to the same host and port behind it, so the socket is closed, its users get error + close 1006, and it is replaced. Immediate first try, then `reconnectInterval` (default 5s) up to `maxReconnectTries` (default 10). Each try first probes `system/pid`, aborted after `probeTimeout` (default 10s); the probe never gates the try. `remove()`/`close()` call off a pending try and its probe.

**Heartbeat:** Sends `"ping"` every 3s; ignores `"pong"` in message handlers.

Expand Down Expand Up @@ -127,7 +127,7 @@ Shared form field components built on ReQore, exported from `src/components/form
| Component | Field Type |
|---------------|-----------------------------------|
| `Boolean` | Toggle/checkbox |
| `Color` | Color picker (`react-color`) |
| `Color` | Colour picker (`@uiw/react-color-saturation`, `-hue`, `-alpha`) with a preview, validated hex/RGB/opacity inputs, help and swatches |
| `Cron` | Cron expression editor |
| `File` | File upload (`react-dropzone`) |
| `LongString` | Multi-line text |
Expand Down Expand Up @@ -196,6 +196,14 @@ yarn build:test # Type-check without emit
- Wrap unit tests with `ReqraftProvider` (or at minimum `QueryClientProvider`)
- Use `mock-socket` for WebSocket testing
- Run: `yarn test` (unit) · `yarn test:stories [file-substring]` (stories) · `yarn precheck` (lint + unit + `build:test:prod`)
- **The type catalogue comes from the server.** A data type's names (its display name, a label, and its
sentence form, its name inside a sentence), descriptions and conversions (`types_accepted`, `exact_match`) are
the Qorus server's (`QorusDataTypeCatalogue::Info`, served at `/system/qorus-type-info`): `defaultQorusTypes`
(`src/helpers/qorusTypeCatalogue.ts`) is reqraft's copy until the server's list arrives, and the condition and
type messages are worded with it. `__tests__/qorusTypeCatalogue.test.ts` compares it, field for field, with
`__tests__/fixtures/qorusTypeInfo.json`, a recorded answer of the server. When the server's catalogue changes,
record its answer again and fix the copy until the test passes:
`qrest -j system/qorus-type-info | yarn -s record:type-info`

### Storybook Stories & visual testing

Expand All @@ -222,6 +230,45 @@ yarn build:test # Type-check without emit
- CI reaches the live hq instance with a valid token, so a story that leaks a real
request can pass in CI and hang locally. When a story passes in CI but times out on
your machine, look for an unmocked request before anything else.
- The guard (`src/stories/realNetworkGuard.ts`) covers what an element loads as well:
an `<img>`, `<source>` or `srcset`, a frame, a script, a stylesheet, a medium. Anything
else loaded from another origin, such as a CSS `url()` or a font, is caught from the
browser's resource timing. A story image is a local asset or a `data:` URI, never a
server's URL. The template fixtures keep the URL the server gives an app's logo
(`<instance>/api/public/apps/<app>/logo`) and swap in a local copy
(`__fixtures__/templateFixtures.ts`, `src/stories/Data/qorusAppLogo.svg`), so import
the fixtures from there, not from the JSON.
- **Touch stories carry `tags: ['touch']`.** A story whose play calls `withTouchScreen`
(`src/stories/touchScreen.ts`), directly or through a helper such as
`playTapIsDrawnOnAPhone`, is tagged `touch`. `yarn test:stories` runs those stories in their
own Vitest project (`storybook-touch`), whose browser context is a phone from the start
(Playwright's `isMobile` and `hasTouch`, 390×844), and before the others; an untagged one fails
with the reason. Never turn Chromium's touch emulation on or off over the DevTools protocol:
turning it off leaves the page with the machine's hover and pointer, not the mouse Playwright
launched it with, and a Linux CI runner has none, so every later story in the page finds
`(hover: none)` (it never shows on a Mac, which always reports a mouse).
`.storybook/vitest.setup.ts` checks every story's browser is its project's:
`(hover: hover) and (pointer: fine)`, or on the phone `(hover: none) and (pointer: coarse)`.
- **Story clock — every story starts at the same instant.** Components write dates
relative to the clock ("3 hours ago"), so a story dated against the real clock captured
"1 month ago" in one build and "3 months ago" a few weeks later.
- `.storybook/preview.tsx` starts every story at `STORY_NOW` (30 June 2026, 12:00 UTC,
`src/stories/storyClock.ts`). Date story data from it with `storyMinutesAgo(n)`,
never with `Date.now()`, `new Date()` or a literal date of its own.
- The clock runs on in real time from `STORY_NOW`, because a stopped clock stops lodash's
`debounce` and `throttle` (ControlGroup's resize, Slate's selection sync). A story is
captured well inside the minute the shortest "… ago" takes to change.
- `parameters.mockdate` stops the clock at an instant, for a story whose every reading
must be the same (log lines stamped to the millisecond). `parameters.live` stories keep
the real clock.
- Every story also starts with an empty `ReqraftQueryClient`, as every unit test does: a
story must pass when it is opened on its own, not only after an earlier story fetched
what it needs. With the clock it is required too: an answer carries the time it was
fetched, and the clock takes each story back to `STORY_NOW`, so the previous story's
answers would read as fetched in the future and be served instead of its own mocks.
Anything else a page keeps from story to story with a timestamp needs the same reset.
- The capture browser runs in UTC and US English (`vitest.config.ts`), as CI does, so a
date written in the viewer's zone or language captures the same on every machine.
- **Visual regression via Qlip** (not Chromatic): the story run captures snapshots through the `qlip` Vitest plugin. Upload is gated on `QLIP_UPLOAD_TOKEN` (`vitest.config.ts`), so **local runs capture but never upload** — PNGs land in `qlip/screenshots/<ts>/stories/auto/<StoryId>.png` (gitignored); read the PNG to verify a visual change. CI (`.github/workflows/tests.yml`) sets the token and uploads a build for dashboard review. The full visual-change flow + the `qlip` MCP review skill live in the imported `stacks/frontend/FRONTEND.md`.

## Code Patterns & Conventions
Expand Down
39 changes: 39 additions & 0 deletions .github/consumer-imports.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# The reqraft modules its consumers import. check-packed-files.mjs fails CI when one of them is not in the
# npm package (package.json `files`), so a deep import cannot break in a consumer after a release.
#
# qorus-ide (wip/646-phase-b, 2026-10-10). Regenerate from a qorus-ide checkout with:
# grep -rhoE "['\"]@qoretechnologies/reqraft(/[^'\"]*)?['\"]" src __tests__ .storybook | tr -d "'\"" | sort -u
@qoretechnologies/reqraft
@qoretechnologies/reqraft/dist/components/Description/markdownRendererContext
@qoretechnologies/reqraft/dist/components/form/engine/FormEngine
@qoretechnologies/reqraft/dist/components/form/engine/FormFieldsSkeleton
@qoretechnologies/reqraft/dist/components/form/engine/_structuredData/structuredData
@qoretechnologies/reqraft/dist/components/form/engine/optionActions
@qoretechnologies/reqraft/dist/components/form/engine/rendererTypes
@qoretechnologies/reqraft/dist/components/form/engine/rowMenuContext
@qoretechnologies/reqraft/dist/components/form/expressions/useRenderExpression
@qoretechnologies/reqraft/dist/components/form/fields/allowed-values/AllowedValues
@qoretechnologies/reqraft/dist/components/form/fields/binary/Binary
@qoretechnologies/reqraft/dist/components/form/fields/emittedValues
@qoretechnologies/reqraft/dist/components/form/fields/file/File
@qoretechnologies/reqraft/dist/components/form/fields/object/Object
@qoretechnologies/reqraft/dist/components/form/fields/select/SelectCollection
@qoretechnologies/reqraft/dist/components/form/fields/template/TemplateField
@qoretechnologies/reqraft/dist/contexts/FetchContext
@qoretechnologies/reqraft/dist/contexts/StorageContext
@qoretechnologies/reqraft/dist/helpers/allowedValues
@qoretechnologies/reqraft/dist/helpers/common
@qoretechnologies/reqraft/dist/helpers/optionUiTypes
@qoretechnologies/reqraft/dist/helpers/options
@qoretechnologies/reqraft/dist/helpers/providerValue
@qoretechnologies/reqraft/dist/helpers/singleLineString
@qoretechnologies/reqraft/dist/helpers/templateItems
@qoretechnologies/reqraft/dist/helpers/templateText
@qoretechnologies/reqraft/dist/helpers/templates
@qoretechnologies/reqraft/dist/helpers/validations
@qoretechnologies/reqraft/dist/hooks/useQorusTypes
@qoretechnologies/reqraft/dist/hooks/useTemplates
@qoretechnologies/reqraft/dist/providers/ReqraftProvider
@qoretechnologies/reqraft/dist/stores/currentUser/currentUser
@qoretechnologies/reqraft/dist/utils/fetch
@qoretechnologies/reqraft/dist/utils/lspClient
110 changes: 110 additions & 0 deletions .github/scripts/check-packed-files.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env node
// Checks that the npm package ships what its consumers load: run from the package root after `yarn build`.
//
// usage: node .github/scripts/check-packed-files.mjs [consumer imports file]
//
// The package's contents are package.json's `files` allowlist; before it, everything not in .npmignore was
// published, agent notes and design docs included. An allowlist can just as easily leave something out, and a
// consumer's deep import (qorus-ide imports `@qoretechnologies/reqraft/dist/components/...` directly) would then
// fail only in the consumer. So the packed file list (`npm pack --dry-run`) must hold:
// - every entry point package.json names (main, types, module, exports, bin);
// - every file the build wrote to dist/;
// - for each import in the consumer imports file, the module (`.js`) and its types (`.d.ts`).
import { execFileSync } from 'node:child_process';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join, posix } from 'node:path';

const normalize = (path) => posix.normalize(path.replace(/^\.\//, ''));

const targetsOf = (field) => {
if (!field) return [];
if (typeof field === 'string') return [field];
return Object.values(field).flatMap(targetsOf);
};

const builtFiles = (dir) => {
let entries;
try {
entries = readdirSync(dir);
} catch {
return [];
}
return entries.flatMap((name) => {
const path = join(dir, name);
return statSync(path).isDirectory() ?
builtFiles(path)
: [normalize(path.split('\\').join('/'))];
});
};

const pkg = JSON.parse(readFileSync('package.json', 'utf8'));
const [pack] = JSON.parse(
execFileSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
);
const packed = new Set(pack.files.map(({ path }) => normalize(path)));
const problems = [];

for (const entry of [
pkg.main,
pkg.types,
pkg.typings,
pkg.module,
...targetsOf(pkg.exports),
...targetsOf(pkg.bin),
]) {
if (entry && !packed.has(normalize(entry))) {
problems.push(`${entry}: an entry point in package.json, not in the package`);
}
}

const built = builtFiles('dist');
if (!built.length) {
problems.push('dist/ is empty: run `yarn build` first');
}
for (const file of built) {
if (!packed.has(file)) {
problems.push(`${file}: built, not in the package`);
}
}

const importsFile = process.argv[2];
const imports =
importsFile ?
readFileSync(importsFile, 'utf8')
.split('\n')
.map((line) => line.trim())
.filter((line) => line && !line.startsWith('#'))
: [];
for (const specifier of imports) {
if (specifier === pkg.name) {
continue; // the package root: main and types, checked above
}
if (!specifier.startsWith(`${pkg.name}/`)) {
problems.push(`${specifier}: not an import of ${pkg.name}`);
continue;
}
const path = normalize(specifier.slice(pkg.name.length + 1));
const has = (...candidates) => candidates.some((candidate) => packed.has(candidate));
if (!has(path, `${path}.js`, `${path}/index.js`)) {
problems.push(`${specifier}: imported by a consumer, no module for it in the package`);
}
if (
!has(`${path}.d.ts`, `${path}/index.d.ts`) &&
!path.endsWith('.js') &&
!path.endsWith('.json')
) {
problems.push(`${specifier}: imported by a consumer, no types for it in the package`);
}
}

console.log(
`${pkg.name}@${pkg.version}: ${pack.entryCount} files, ${pack.size} bytes packed, ${pack.unpackedSize} unpacked; ` +
`${built.length} built files and ${imports.length} consumer imports checked`
);
if (problems.length) {
console.error(problems.map((problem) => `- ${problem}`).join('\n'));
process.exit(1);
}
44 changes: 44 additions & 0 deletions .github/scripts/published-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/bin/sh
# What a tests run may tell people to install: the version this commit published, and only once npm has it.
#
# publish.yml publishes in parallel with the tests, and it may publish nothing (package.json's version is
# already on npm), be superseded by a newer push, or fail. So the version is worked out by the script
# publish.yml uses (release-version.sh), then named only if npm answers with it. Run #482 told people to
# `yarn add @qoretechnologies/reqraft@v0.10.63-canary.…`, a version the tests job made up and never published.
#
# usage (from the repository root):
# published-version.sh push <publish runs url>
# published-version.sh pull_request <PR number> <head sha> <publish runs url>
#
# Prints GITHUB_OUTPUT lines:
# version=<the published version> (only when npm has it)
# field=<what the Discord embed's Version field says>
# note=<the end of the PR success message: how to install it, or why there is nothing to install>
set -eu

here=$(dirname "$0")
event=${1:?usage: published-version.sh push <url> | pull_request <PR number> <head sha> <url>}
if [ "$event" = pull_request ]; then
computed=$("$here/release-version.sh" pull_request "${2:?}" "${3:?}")
runs=${4:?the publish runs url is required}
else
computed=$("$here/release-version.sh" "$event")
runs=${2:?the publish runs url is required}
fi
value() { printf '%s\n' "$computed" | sed -n "s/^$1=//p"; }
name=$(value name)
base=$(value base)
publish=$(value publish)
version=$(value version)

if [ "$publish" = true ] && [ "$(npm view "$name@$version" version 2>/dev/null || true)" = "$version" ]; then
echo "version=$version"
echo "field=$version"
echo "note=, install the prerelease via \`yarn add $name@$version\`."
elif [ "$publish" = true ]; then
echo "field=none on npm yet"
echo "note=. No prerelease is on npm for this commit yet: see the [Publish run]($runs)."
else
echo "field=none ($base is already on npm)"
echo "note=. No prerelease was cut: package.json is at $base, which is already on npm."
fi
29 changes: 29 additions & 0 deletions .github/scripts/qlip-build-link.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/sh
# The qlip build a story run uploaded, for the Discord message's "Qlip visual tests" link.
#
# usage: qlip-build-link.sh <story run log>
#
# qlip prints one line after uploading, e.g.
# [qlip] uploaded build 20261010-104435: 635 entries from 54 contexts (…) → https://qlip.qoretechnologies.com
# (older qlip: `uploaded build 20260629-123456 (12 fragments, …) → …`). The id is server-assigned, so it is read
# from that line. It ends at the first space or colon: taking everything up to the space kept the colon, and the
# link went to build `20261010-104435:`, which does not exist.
#
# Prints GITHUB_OUTPUT lines `build_id=` and `url=` (the dashboard's builds are global: /builds/<id>), or nothing
# when no build was uploaded.
set -eu

log=${1:?usage: qlip-build-link.sh <story run log>}
# ANSI colours are stripped first: one between `[qlip]` and `uploaded` would hide the line
line=""
if [ -f "$log" ]; then
line=$(sed -E 's/\x1b\[[0-9;]*m//g' "$log" | grep -aE '\[qlip\] uploaded build ' | tail -1 || true)
fi
build_id=$(printf '%s' "$line" | sed -nE 's/.*uploaded build ([^ :]+).*/\1/p')
server=$(printf '%s' "$line" | sed -nE 's/.* → (https?:[^ ]+).*/\1/p')
server=${server:-https://qlip.qoretechnologies.com}
echo "Resolved qlip build='${build_id:-<none>}' server='${server}'" >&2
if [ -n "$build_id" ]; then
echo "build_id=$build_id"
echo "url=$server/builds/$build_id"
fi
57 changes: 57 additions & 0 deletions .github/scripts/release-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/sh
# The version a workflow run publishes, worked out in ONE place: publish.yml publishes it, and tests.yml names
# it in Discord. Two copies of this logic drifted once already: tests.yml made up a canary that nothing
# published and told people to install it.
#
# usage (from the repository root):
# release-version.sh push
# release-version.sh pull_request <PR number> <head sha>
#
# Prints GITHUB_OUTPUT lines:
# name=<the package's name>
# base=<package.json's version, without any prerelease part>
# publish=true|false
# version=<the version this run publishes> (only when publish=true)
#
# push (develop): package.json's version as it is, which publish.yml's Publish-and-release releases under `beta`.
#
# pull_request: a prerelease OF THE VERSION THIS PR IS RELEASING, the one already in package.json (every PR is
# required to bump it), under the `pr` dist-tag: <base>-pr.<PR>.g<sha7>.
# - Not the next patch: a prerelease of 0.10.35 for a PR releasing 0.10.34 previews a version nobody is merging,
# and sorts ABOVE the real 0.10.34, so a consumer pinned to the preview never moves forward on upgrade.
# Semver puts a prerelease below its release and above the previous one:
# 0.10.33 < 0.10.34-pr.92.gabc123 < 0.10.34
# - The sha is prefixed with `g` (git-describe style) so an all-digit short sha can never become an invalid
# leading-zero numeric identifier.
# - None when <base> is already on npm: this PR has not bumped, because it need not (a CI-, docs- or test-only
# change) or because it forgot. A prerelease of a released version sorts BELOW that release and would hand
# consumers something older than they have. Skipped, not failed: a PR that forgot its bump is caught at merge,
# where the release cannot publish over an existing version.
set -eu

event=${1:?usage: release-version.sh push | pull_request <PR number> <head sha>}
name=$(node -p "require('./package.json').name")
base=$(node -p "require('./package.json').version.split('-')[0]")
echo "name=$name"
echo "base=$base"

case "$event" in
push)
echo "publish=true"
echo "version=$(node -p "require('./package.json').version")"
;;
pull_request)
pr=${2:?the PR number is required}
sha=$(printf '%s' "${3:?the head sha is required}" | cut -c1-7)
if npm view "$name@$base" version >/dev/null 2>&1; then
echo "publish=false"
else
echo "publish=true"
echo "version=$base-pr.$pr.g$sha"
fi
;;
*)
echo "release-version.sh: unknown event '$event'" >&2
exit 2
;;
esac
Loading
Loading