@qoretechnologies/reqraft 0.10.6 → 0.10.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
  2. package/dist/components/form/engine/CompactRow.js +38 -2
  3. package/dist/components/form/engine/CompactRow.js.map +1 -1
  4. package/dist/components/form/engine/FormEngine.d.ts +11 -1
  5. package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
  6. package/dist/components/form/engine/FormEngine.js +36 -15
  7. package/dist/components/form/engine/FormEngine.js.map +1 -1
  8. package/dist/components/form/engine/compactRowStyles.d.ts +1 -0
  9. package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
  10. package/dist/components/form/engine/compactRowStyles.js +19 -4
  11. package/dist/components/form/engine/compactRowStyles.js.map +1 -1
  12. package/dist/components/form/engine/readFirst.d.ts.map +1 -1
  13. package/dist/components/form/engine/readFirst.js +30 -2
  14. package/dist/components/form/engine/readFirst.js.map +1 -1
  15. package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
  16. package/dist/components/form/fields/auto/AutoFormField.js +29 -3
  17. package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/components/form/engine/CompactRow.tsx +75 -2
  20. package/src/components/form/engine/FormEngine.stories.tsx +279 -0
  21. package/src/components/form/engine/FormEngine.tsx +66 -18
  22. package/src/components/form/engine/compactRowStyles.ts +21 -0
  23. package/src/components/form/engine/readFirst.ts +25 -4
  24. package/src/components/form/fields/auto/AutoFormField.tsx +26 -0
  25. package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +69 -37
@@ -667,6 +667,285 @@ export const OptionInheritsRenderPropFromSibling: Story = {
667
667
  },
668
668
  };
669
669
 
670
+ // qorus#347-followup, compact variant of the FLAT case: same schema and
671
+ // componentOverride as `OptionInheritsRenderPropFromSibling`, but the form
672
+ // is rendered with `compact: true`. Compact and classic share the same
673
+ // `renderOption` callback (FormEngine.tsx:1590) where `inherit_props` is
674
+ // resolved onto `TemplateField`, so the forwarding mechanism is identical
675
+ // in both modes. This story locks that in so a future refactor of the
676
+ // compact path can't silently break inherit_props for read-first surfaces.
677
+ export const OptionInheritsRenderPropFromSiblingCompact: Story = {
678
+ args: {
679
+ compact: true,
680
+ minColumnWidth: '300px',
681
+ componentOverrides: { 'code-editor': CodeEditorStandin },
682
+ value: {
683
+ lang: { type: 'string', value: 'qore' },
684
+ source: { type: 'string', value: 'sub run() { print("hello"); }' },
685
+ },
686
+ options: {
687
+ lang: {
688
+ type: 'string',
689
+ ui_type: 'string',
690
+ display_name: 'Language',
691
+ allowed_values: [
692
+ { display_name: 'Qore', value: { type: 'string', value: 'qore' } },
693
+ { display_name: 'Python', value: { type: 'string', value: 'python' } },
694
+ { display_name: 'Java', value: { type: 'string', value: 'java' } },
695
+ ],
696
+ },
697
+ source: {
698
+ type: 'string',
699
+ ui_type: 'code-editor',
700
+ display_name: 'Source Code',
701
+ inherit_props: { language: 'lang' },
702
+ },
703
+ },
704
+ },
705
+ play: async ({ canvasElement }) => {
706
+ const canvas = within(canvasElement);
707
+ // Compact renders rows collapsed by default. Structural check: the
708
+ // schema arrives intact + the source-code row is present. The full
709
+ // interaction (lang flip -> language prop updates) is covered by the
710
+ // classic `OptionInheritsRenderPropFromSibling` story.
711
+ await waitFor(
712
+ () => expect(canvas.getAllByText('Source Code').length).toBeGreaterThan(0),
713
+ { timeout: 5000 }
714
+ );
715
+ },
716
+ };
717
+
718
+ // Compact-row code-editor preview: a `code-editor` field with a multi-line
719
+ // string value renders (a) a "N lines · N chars" tag in the value cell instead
720
+ // of the truncated raw string, and (b) a monospace `<pre>` block under the row
721
+ // capped by a `ReqoreCollapsibleContent` — the "Show more" affordance the value
722
+ // cell couldn't provide on its own. Locks the compact preview so a future
723
+ // CompactRow refactor can't silently reduce a Qorus source-code field to an
724
+ // ellipsised one-liner again.
725
+ export const CompactRowCodeEditorPreview: Story = {
726
+ args: {
727
+ compact: true,
728
+ minColumnWidth: '360px',
729
+ componentOverrides: { 'code-editor': CodeEditorStandin },
730
+ value: {
731
+ language: { type: 'string', value: 'qore' },
732
+ source: {
733
+ type: 'string',
734
+ value:
735
+ '%new-style\n%require-types\n%strict-args\n' +
736
+ '%enable-all-warnings\n\n' +
737
+ 'class ExampleJob inherits QorusJob {\n' +
738
+ ' run() {\n' +
739
+ ' logInfo("running");\n' +
740
+ ' }\n' +
741
+ '}\n',
742
+ },
743
+ },
744
+ options: {
745
+ language: {
746
+ type: 'string',
747
+ ui_type: 'string',
748
+ display_name: 'Language',
749
+ allowed_values: [
750
+ { display_name: 'Qore', value: { type: 'string', value: 'qore' } },
751
+ { display_name: 'Python', value: { type: 'string', value: 'python' } },
752
+ { display_name: 'Java', value: { type: 'string', value: 'java' } },
753
+ ],
754
+ },
755
+ source: {
756
+ type: 'string',
757
+ ui_type: 'code-editor',
758
+ display_name: 'Source Code',
759
+ inherit_props: { language: 'language' },
760
+ },
761
+ },
762
+ },
763
+ play: async ({ canvasElement }) => {
764
+ // (a) The monospace preview mounted under the row — contains a substring
765
+ // only the source has, proving `showCodePreview` kicked in and the
766
+ // `StyledCodePreview` block is in the DOM.
767
+ await waitFor(
768
+ () => {
769
+ const preview = canvasElement.querySelector('.options-readfirst-code');
770
+ expect(preview).toBeTruthy();
771
+ expect(preview?.textContent).toContain('class ExampleJob inherits QorusJob');
772
+ },
773
+ { timeout: 5000 }
774
+ );
775
+ // (b) The value cell replaced its truncated raw string with a summary tag —
776
+ // query the tag directly (its label + labelKey render on separate spans,
777
+ // so text-matching across them is fragile). Look for the CodeLine icon
778
+ // that only this tag mounts alongside the source-code row.
779
+ const sourceRow = canvasElement.querySelector('[data-field="source"]');
780
+ expect(sourceRow).toBeTruthy();
781
+ expect(sourceRow?.textContent ?? '').toMatch(/\d+\s*lines?/);
782
+ },
783
+ };
784
+
785
+ // qorus#347-followup, scope forwarding: this story exercises the nested
786
+ // case of the OptionInheritsRenderPropFromSibling contract. The parent
787
+ // form declares `methods: { ui_type: 'list', element_type: 'hash',
788
+ // arg_schema: {...}, inherit_props: { language: 'language' } }`. FormEngine
789
+ // resolves the parent's inherit_props against the top-level `language`
790
+ // field, threads the resolved bag through the ArrayAuto row wrapper into
791
+ // each row's arg_schema sub-form as `inheritedFromParent`. The row's
792
+ // `body` sub-field ALSO declares `inherit_props: { language: 'language' }`;
793
+ // its `availableOptions` has no `language` (rows only carry name /
794
+ // description / body), so the resolver falls back to
795
+ // `inheritedFromParent.language` and threads it as the CodeEditor's
796
+ // `language` prop. Flipping the top-level lang picker live-updates every
797
+ // row's editor without any custom per-field wiring.
798
+ export const NestedOptionInheritsRenderPropFromAncestor: Story = {
799
+ args: {
800
+ componentOverrides: { 'code-editor': CodeEditorStandin },
801
+ value: {
802
+ language: { type: 'string', value: 'qore' },
803
+ methods: {
804
+ type: 'list',
805
+ value: [
806
+ { type: 'hash', value: { name: 'init', body: 'sub init() { }' } },
807
+ { type: 'hash', value: { name: 'run', body: 'sub run() { print("hi"); }' } },
808
+ ],
809
+ },
810
+ },
811
+ options: {
812
+ language: {
813
+ type: 'string',
814
+ ui_type: 'string',
815
+ display_name: 'Language',
816
+ allowed_values: [
817
+ { display_name: 'Qore', value: { type: 'string', value: 'qore' } },
818
+ { display_name: 'Python', value: { type: 'string', value: 'python' } },
819
+ { display_name: 'Java', value: { type: 'string', value: 'java' } },
820
+ ],
821
+ },
822
+ methods: {
823
+ type: 'list',
824
+ ui_type: 'list',
825
+ element_type: 'hash',
826
+ display_name: 'Methods',
827
+ // Parent-level declaration: forward top-level `language` down into
828
+ // each row's arg_schema sub-form so per-method `body` sub-fields
829
+ // can pick it up as `language` prop without knowing about the
830
+ // ancestor scope.
831
+ inherit_props: { language: 'language' },
832
+ arg_schema: {
833
+ name: {
834
+ type: 'string',
835
+ ui_type: 'string',
836
+ display_name: 'Method Name',
837
+ },
838
+ body: {
839
+ type: 'string',
840
+ ui_type: 'code-editor',
841
+ display_name: 'Method Body',
842
+ // Row-level declaration: the resolver walks
843
+ // 1. local availableOptions (row only has name + body — no
844
+ // language here),
845
+ // 2. `inheritedFromParent` (populated by the parent-level
846
+ // `methods.inherit_props` above — has language).
847
+ inherit_props: { language: 'language' },
848
+ },
849
+ },
850
+ },
851
+ } as unknown as IOptionsSchema,
852
+ },
853
+ play: async ({ canvasElement }) => {
854
+ const canvas = within(canvasElement);
855
+
856
+ // Two rows -> two code-editor stand-ins -> each shows "syntax: qore"
857
+ // at initial render, sourced from the top-level `language` field via
858
+ // parent -> row scope forwarding.
859
+ await waitFor(
860
+ () => {
861
+ const tags = canvas.getAllByTestId('code-editor-language');
862
+ expect(tags).toHaveLength(2);
863
+ tags.forEach((tag) => expect(tag).toHaveTextContent('syntax: qore'));
864
+ },
865
+ { timeout: 5000 }
866
+ );
867
+ },
868
+ };
869
+
870
+ // qorus#347-followup, scope forwarding + compact variant: same schema as
871
+ // `NestedOptionInheritsRenderPropFromAncestor` but with `compact: true`.
872
+ // Compact and classic share the `renderOption` callback (FormEngine.tsx:1590)
873
+ // which is where the `inheritedFromParent` bag is threaded onto TemplateField,
874
+ // so the forwarding mechanism is identical in both modes. This story locks
875
+ // that in — a compact rendering of a parent form with a nested arg_schema
876
+ // list-of-hash whose sub-fields still resolve `language` from the top-level
877
+ // picker through the same two-hop chain.
878
+ export const NestedOptionInheritsRenderPropFromAncestorCompact: Story = {
879
+ args: {
880
+ compact: true,
881
+ minColumnWidth: '300px',
882
+ componentOverrides: { 'code-editor': CodeEditorStandin },
883
+ value: {
884
+ language: { type: 'string', value: 'qore' },
885
+ methods: {
886
+ type: 'list',
887
+ value: [
888
+ { type: 'hash', value: { name: 'init', body: 'sub init() { }' } },
889
+ { type: 'hash', value: { name: 'run', body: 'sub run() { print("hi"); }' } },
890
+ ],
891
+ },
892
+ },
893
+ options: {
894
+ language: {
895
+ type: 'string',
896
+ ui_type: 'string',
897
+ display_name: 'Language',
898
+ allowed_values: [
899
+ { display_name: 'Qore', value: { type: 'string', value: 'qore' } },
900
+ { display_name: 'Python', value: { type: 'string', value: 'python' } },
901
+ { display_name: 'Java', value: { type: 'string', value: 'java' } },
902
+ ],
903
+ },
904
+ methods: {
905
+ type: 'list',
906
+ ui_type: 'list',
907
+ element_type: 'hash',
908
+ display_name: 'Methods',
909
+ inherit_props: { language: 'language' },
910
+ arg_schema: {
911
+ name: {
912
+ type: 'string',
913
+ ui_type: 'string',
914
+ display_name: 'Method Name',
915
+ },
916
+ body: {
917
+ type: 'string',
918
+ ui_type: 'code-editor',
919
+ display_name: 'Method Body',
920
+ inherit_props: { language: 'language' },
921
+ },
922
+ },
923
+ },
924
+ } as unknown as IOptionsSchema,
925
+ },
926
+ play: async ({ canvasElement }) => {
927
+ const canvas = within(canvasElement);
928
+
929
+ // Compact renders each option collapsed into a read-first row. The
930
+ // methods row needs a click to expand, then the nested list rows each
931
+ // need a click to expand and reveal the body sub-field's editor with
932
+ // the inherited language. Rather than driving that whole editing
933
+ // flow (which is what the CompactBasic / CompactExpressions stories
934
+ // already exercise), assert on the STRUCTURAL element: the schema
935
+ // arrived intact through `inheritedFromParent` and the row-level
936
+ // options include the body field wired to the code-editor override.
937
+ await waitFor(
938
+ () => expect(canvas.getAllByText('Methods').length).toBeGreaterThan(0),
939
+ { timeout: 5000 }
940
+ );
941
+ // The list-of-hashes value summarises by the items' names — never a raw
942
+ // "[object Object]" (regression: it used to stringify each hash envelope).
943
+ await expect(await canvas.findByText('init, run', undefined, { timeout: 5000 }))
944
+ .toBeInTheDocument();
945
+ await expect(canvasElement.textContent ?? '').not.toContain('[object Object]');
946
+ },
947
+ };
948
+
670
949
  export const DependantsResetWhenParentChanges: Story = {
671
950
  args: {
672
951
  minColumnWidth: '300px',
@@ -133,13 +133,21 @@ export interface IFormValidityData {
133
133
 
134
134
  /**
135
135
  * Resolve a field's `inherit_props` map against the current available
136
- * options, producing a `{ propName: siblingValue }` hash suitable for
137
- * spreading onto the field's renderer.
136
+ * options (plus any values inherited from an outer FormEngine scope),
137
+ * producing a `{ propName: siblingValue }` hash suitable for spreading
138
+ * onto the field's renderer.
138
139
  *
139
140
  * For each entry `<prop-name-on-renderer> -> <sibling-field-name>`, the
140
- * sibling's current value (`availableOptions[siblingName]?.value`) is
141
- * forwarded under the receiving prop name. Missing siblings emit
142
- * `undefined`, which the renderer's prop type can treat as "no hint".
141
+ * sibling's current value is forwarded under the receiving prop name.
142
+ * Lookup order:
143
+ * 1. `availableOptions[siblingName]?.value` the field's local scope.
144
+ * 2. `inheritedFromParent[siblingName]` — the bag threaded in by an
145
+ * outer FormEngine (see the `inheritedFromParent` prop). Used when
146
+ * a composite field's `arg_schema` sub-form needs to reach a value
147
+ * on an ancestor scope — e.g. a service-method row's `body` picking
148
+ * up `language` from the parent service form.
149
+ * Missing siblings emit `undefined`, which the renderer's prop type can
150
+ * treat as "no hint".
143
151
  *
144
152
  * Designed to be JSON-pure: no closures, no transformations. Renderers
145
153
  * decide how to use the forwarded value (e.g. the consumer-injected
@@ -151,13 +159,15 @@ export interface IFormValidityData {
151
159
  */
152
160
  const resolveInheritProps = (
153
161
  inheritProps: Record<string, string> | undefined,
154
- availableOptions: TQorusForm | undefined
162
+ availableOptions: TQorusForm | undefined,
163
+ inheritedFromParent?: Record<string, unknown>
155
164
  ): Record<string, unknown> => {
156
- if (!inheritProps || !availableOptions) return {};
165
+ if (!inheritProps) return {};
157
166
  const out: Record<string, unknown> = {};
158
167
  for (const propName in inheritProps) {
159
168
  const siblingName = inheritProps[propName];
160
- out[propName] = (availableOptions[siblingName] as IQorusFormField | undefined)?.value;
169
+ const localValue = (availableOptions?.[siblingName] as IQorusFormField | undefined)?.value;
170
+ out[propName] = localValue !== undefined ? localValue : inheritedFromParent?.[siblingName];
161
171
  }
162
172
  return out;
163
173
  };
@@ -543,6 +553,17 @@ export interface IFormEngineProps extends Omit<IReqoreCollectionProps, 'onChange
543
553
  * `TemplateField` to the `AutoFormField` override seam.
544
554
  */
545
555
  componentOverrides?: Record<string, React.FC<any>>;
556
+
557
+ /**
558
+ * Bag of values forwarded from an outer FormEngine scope, used as a
559
+ * fallback when a field's `inherit_props` names a sibling that isn't in
560
+ * this form's own `availableOptions`. Populated automatically when
561
+ * FormEngine renders a nested `arg_schema` sub-form (through
562
+ * AutoFormField's hash / list mount sites) so that each level accumulates
563
+ * its ancestors' inherited props. Consumers rarely set this by hand — it's
564
+ * plumbing for the inherit_props scope-forwarding contract.
565
+ */
566
+ inheritedFromParent?: Record<string, unknown>;
546
567
  }
547
568
 
548
569
  // Option types rendered full-width (IDE Options parity, commit 8e6b7781).
@@ -580,6 +601,7 @@ export const FormEngine = ({
580
601
  onValidityChange,
581
602
  optionActions,
582
603
  componentOverrides,
604
+ inheritedFromParent,
583
605
  ...rest
584
606
  }: IFormEngineProps) => {
585
607
  const [options, setOptions] = useState<IQorusFormSchema | undefined>(rest?.options || undefined);
@@ -1665,16 +1687,42 @@ export const FormEngine = ({
1665
1687
  fluid
1666
1688
  {...(options?.[optionName] as any)}
1667
1689
  // qorus#347-followup: resolve the field's `inherit_props` against
1668
- // the CURRENT sibling values, threading each entry as a top-level
1669
- // prop. Each `<prop-name>: <sibling-field-name>` mapping copies
1670
- // the sibling's value (read from `availableOptions[name].value`)
1671
- // onto the rendered field's renderer e.g. a code-editor with
1672
- // `inherit_props: { language: 'lang' }` picks up the live `lang`
1673
- // value as a `language` prop without a schema refetch. Spread
1674
- // AFTER `{...options?.[optionName]}` so the runtime value wins
1675
- // over any schema-defined default of the same key. Mirrored in
1676
- // qorus-ide's `systemOptions.tsx`; see the CLAUDE.md rule there.
1677
- {...resolveInheritProps(options?.[optionName]?.inherit_props, availableOptions)}
1690
+ // the CURRENT sibling values (with a fallback to
1691
+ // `inheritedFromParent` the bag threaded in from an outer
1692
+ // FormEngine when this scope is an `arg_schema` sub-form),
1693
+ // threading each entry as a top-level prop. Each
1694
+ // `<prop-name>: <sibling-field-name>` mapping copies the
1695
+ // sibling's value onto the rendered field's renderer e.g. a
1696
+ // `code-editor` with `inherit_props: { language: 'language' }`
1697
+ // picks up the live `language` value as a `language` prop without
1698
+ // a schema refetch. Spread AFTER `{...options?.[optionName]}` so
1699
+ // the runtime value wins over any schema-defined default of the
1700
+ // same key. Mirrored in qorus-ide's `systemOptions.tsx`; see the
1701
+ // CLAUDE.md rule there.
1702
+ {...resolveInheritProps(
1703
+ options?.[optionName]?.inherit_props,
1704
+ availableOptions,
1705
+ inheritedFromParent
1706
+ )}
1707
+ // qorus#347-followup (scope forwarding): merge accumulated
1708
+ // inheritance (`inheritedFromParent`) with THIS field's freshly
1709
+ // resolved inherit_props, and pass down as a single bag so any
1710
+ // nested `arg_schema` sub-form (mounted by AutoFormField for
1711
+ // `hash` / `free-hash` / list-of-hash fields) sees every value
1712
+ // the ancestor chain forwarded. This is the plumbing that lets a
1713
+ // service-method row's `body` sub-field pick up the parent
1714
+ // service form's `language` — the parent field declares
1715
+ // `inherit_props: { language: 'language' }`, the list renderer
1716
+ // forwards it into each row, and the row's body resolves against
1717
+ // the accumulated bag.
1718
+ inheritedFromParent={{
1719
+ ...inheritedFromParent,
1720
+ ...resolveInheritProps(
1721
+ options?.[optionName]?.inherit_props,
1722
+ availableOptions,
1723
+ inheritedFromParent
1724
+ ),
1725
+ }}
1678
1726
  // Propagate compact so an arg_schema field renders a COMPACT sub-form
1679
1727
  // (consistent with the parent) rather than the classic FormEngine.
1680
1728
  compact={compact}
@@ -335,6 +335,27 @@ export const StyledRowInset = styled.div`
335
335
  margin-top: 4px;
336
336
  `;
337
337
 
338
+ // A collapsed code-block preview shown under the value summary for a
339
+ // `code-editor` field. Multi-line, monospace, subtle background — matches
340
+ // the aesthetic of the classic code-view surface but small enough to sit
341
+ // inside a read-row. Height is capped by the wrapping `ReqoreCollapsibleContent`
342
+ // so the "Show more" affordance stays useful.
343
+ export const StyledCodePreview = styled.pre<{ $bg: string; $border: string; $fg: string }>`
344
+ margin: 0;
345
+ padding: 8px 10px;
346
+ border-radius: 4px;
347
+ background: ${({ $bg }) => $bg};
348
+ border: 1px solid ${({ $border }) => $border};
349
+ color: ${({ $fg }) => $fg};
350
+ font-family:
351
+ ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
352
+ font-size: 11.5px;
353
+ line-height: 1.5;
354
+ white-space: pre;
355
+ overflow: auto;
356
+ max-width: 100%;
357
+ `;
358
+
338
359
  // A small inline colour swatch shown before an rgbcolor value's hex string.
339
360
  export const StyledColorSwatch = styled.span<{ $color: string; $border: string }>`
340
361
  width: 12px;
@@ -17,12 +17,33 @@ const pluralize = (count: number, noun: string): string =>
17
17
  // full server allow-list, incl. keys like `sensitive`/`required`).
18
18
  const isTypedEnvelope = (raw: unknown): raw is IQorusFormField => isUiEncodedValue(raw);
19
19
 
20
- /** Summarise a list value: join item names, or fall back to an "N items" count. */
20
+ /** Reduce ONE list item to a short, human label never an object. Unwraps a
21
+ * typed `{type, value}` envelope, prefers a `name`/`display_name`, and returns
22
+ * undefined for a bare object (no name) so the caller falls back to a count
23
+ * instead of printing "[object Object]". */
24
+ const summarizeListItem = (item: unknown): string | number | undefined => {
25
+ if (item === null || item === undefined) return undefined;
26
+ if (typeof item !== 'object') return item as string | number;
27
+ const obj = item as Record<string, unknown>;
28
+ // A named object (or allowed-value) → its label.
29
+ if (typeof obj.name === 'string' || typeof obj.name === 'number') return obj.name;
30
+ if (typeof obj.display_name === 'string') return obj.display_name;
31
+ // A typed envelope ({type, value}) or a {value} wrapper → look inside once.
32
+ if ('value' in obj) {
33
+ const inner = obj.value;
34
+ if (inner === null || inner === undefined) return undefined;
35
+ if (typeof inner !== 'object') return inner as string | number;
36
+ const innerName = (inner as Record<string, unknown>).name;
37
+ return typeof innerName === 'string' || typeof innerName === 'number' ? innerName : undefined;
38
+ }
39
+ return undefined;
40
+ };
41
+
42
+ /** Summarise a list value: join item labels, or fall back to an "N items" count
43
+ * (e.g. a list of anonymous hashes). Never prints raw objects. */
21
44
  const formatList = (items: unknown[]): string => {
22
45
  const parts = items
23
- .map((item) =>
24
- item && typeof item === 'object' ? ((item as any).name ?? (item as any).value ?? '') : item
25
- )
46
+ .map(summarizeListItem)
26
47
  .filter((part) => part !== '' && part !== undefined && part !== null);
27
48
 
28
49
  return parts.length ? parts.join(', ') : pluralize(items.length, 'item');
@@ -154,6 +154,12 @@ function AutoField<T = any>({
154
154
  showSavedValues,
155
155
  uniqueName,
156
156
  componentOverrides,
157
+ // qorus#347-followup (scope forwarding): destructure so it does NOT
158
+ // land in `...rest` — otherwise the primitive field renderers spread
159
+ // rest onto DOM nodes and React warns about the unknown attribute.
160
+ // Only the nested arg_schema mount sites re-forward this into their
161
+ // sub-forms explicitly.
162
+ inheritedFromParent,
157
163
  ...rest
158
164
  }: IAutoFieldProps & T) {
159
165
  const [currentType, setType] = useState<IQorusType>(defaultInternalType || null);
@@ -564,6 +570,20 @@ function AutoField<T = any>({
564
570
  stringTemplates={rest.templates}
565
571
  size={rest.size}
566
572
  disabled={rest.disabled}
573
+ // Forward consumer-injected editors into the nested sub-form
574
+ // so its own fields can render host-injected types (e.g. a
575
+ // `code-editor` override for a `body` sub-field inside a
576
+ // list-of-hash row). Was missing pre-qorus#347-followup —
577
+ // catches an existing gap surfaced by the nested inherit_props
578
+ // story.
579
+ componentOverrides={componentOverrides}
580
+ // qorus#347-followup (scope forwarding): thread the accumulated
581
+ // inheritance bag into the nested sub-form so its own fields'
582
+ // `inherit_props` can reach ancestor-scope values (e.g. a
583
+ // service-method row's `body` picking up `language` from the
584
+ // parent service form). The parent FormEngine populates this
585
+ // via TemplateField -> AutoFormField's `rest`.
586
+ inheritedFromParent={inheritedFromParent}
567
587
  />
568
588
  );
569
589
  }
@@ -658,6 +678,12 @@ function AutoField<T = any>({
658
678
  allowed_values_creatable={rest.element_allowed_values_creatable}
659
679
  type={effectiveElementType}
660
680
  componentOverrides={componentOverrides}
681
+ // qorus#347-followup (scope forwarding): thread the accumulated
682
+ // inheritance bag through the list wrapper so each row's
683
+ // arg_schema sub-form sees it. ArrayAuto forwards this via
684
+ // TemplateField's `rest` into each row's AutoFormField, which
685
+ // hands it to the row's nested FormEngine (case 'hash' above).
686
+ inheritedFromParent={inheritedFromParent}
661
687
  onChange={(name, value) => {
662
688
  if (!size(value)) {
663
689
  return handleChange(name, undefined);