@tai42/studio-sdk 10.0.0 → 11.0.0

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 (72) hide show
  1. package/dist/components/components.css +18 -0
  2. package/dist/components/dirty-guard.d.ts +23 -10
  3. package/dist/components/dirty-guard.d.ts.map +1 -1
  4. package/dist/components/dirty-guard.js +22 -12
  5. package/dist/components/dirty-guard.js.map +1 -1
  6. package/dist/components/field.d.ts.map +1 -1
  7. package/dist/components/field.js +1 -1
  8. package/dist/components/field.js.map +1 -1
  9. package/dist/components/fleet-report.d.ts +12 -3
  10. package/dist/components/fleet-report.d.ts.map +1 -1
  11. package/dist/components/fleet-report.js +28 -9
  12. package/dist/components/fleet-report.js.map +1 -1
  13. package/dist/components/icons.d.ts +4 -0
  14. package/dist/components/icons.d.ts.map +1 -1
  15. package/dist/components/icons.js +5 -0
  16. package/dist/components/icons.js.map +1 -1
  17. package/dist/hooks/useApi.d.ts +348 -2
  18. package/dist/hooks/useApi.d.ts.map +1 -1
  19. package/dist/host.d.ts +1 -1
  20. package/dist/host.d.ts.map +1 -1
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -2
  24. package/dist/index.js.map +1 -1
  25. package/dist/navigation/context.d.ts +6 -2
  26. package/dist/navigation/context.d.ts.map +1 -1
  27. package/dist/navigation/context.js +10 -2
  28. package/dist/navigation/context.js.map +1 -1
  29. package/dist/navigation/types.d.ts +1 -0
  30. package/dist/navigation/types.d.ts.map +1 -1
  31. package/dist/plugin/host-state.d.ts +16 -0
  32. package/dist/plugin/host-state.d.ts.map +1 -1
  33. package/dist/plugin/host-state.js +1 -0
  34. package/dist/plugin/host-state.js.map +1 -1
  35. package/dist/plugin/types.d.ts +17 -7
  36. package/dist/plugin/types.d.ts.map +1 -1
  37. package/dist/schema-form/SecretRefField.d.ts.map +1 -1
  38. package/dist/schema-form/SecretRefField.js +3 -2
  39. package/dist/schema-form/SecretRefField.js.map +1 -1
  40. package/dist/schema-form/default-value.d.ts +12 -0
  41. package/dist/schema-form/default-value.d.ts.map +1 -1
  42. package/dist/schema-form/default-value.js +34 -3
  43. package/dist/schema-form/default-value.js.map +1 -1
  44. package/dist/schema-form/field-node.d.ts +7 -0
  45. package/dist/schema-form/field-node.d.ts.map +1 -1
  46. package/dist/schema-form/field-node.js +3 -2
  47. package/dist/schema-form/field-node.js.map +1 -1
  48. package/dist/schema-form/object-fields.d.ts +3 -1
  49. package/dist/schema-form/object-fields.d.ts.map +1 -1
  50. package/dist/schema-form/object-fields.js +2 -2
  51. package/dist/schema-form/object-fields.js.map +1 -1
  52. package/dist/schema-form/union-field.d.ts +5 -2
  53. package/dist/schema-form/union-field.d.ts.map +1 -1
  54. package/dist/schema-form/union-field.js +68 -15
  55. package/dist/schema-form/union-field.js.map +1 -1
  56. package/package.json +2 -2
  57. package/src/components/components.css +18 -0
  58. package/src/components/dirty-guard.tsx +23 -13
  59. package/src/components/field.tsx +10 -5
  60. package/src/components/fleet-report.tsx +44 -13
  61. package/src/components/icons.tsx +19 -0
  62. package/src/host.ts +5 -1
  63. package/src/index.ts +3 -1
  64. package/src/navigation/context.tsx +16 -1
  65. package/src/navigation/types.ts +3 -0
  66. package/src/plugin/host-state.ts +18 -0
  67. package/src/plugin/types.ts +17 -7
  68. package/src/schema-form/SecretRefField.tsx +3 -2
  69. package/src/schema-form/default-value.ts +36 -3
  70. package/src/schema-form/field-node.tsx +21 -2
  71. package/src/schema-form/object-fields.tsx +4 -0
  72. package/src/schema-form/union-field.tsx +85 -19
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * The union field renderer: a variant picker plus the active variant's fields.
3
3
  * An object variant renders its properties inline (skipping the discriminator
4
- * tag, which the picker owns); a scalar/enum variant renders as a single nested
5
- * {@link FieldNode}. Selecting a variant seeds a concrete value for it.
4
+ * tag, which the picker owns), with the variant's required fields carrying the
5
+ * design system's required marker; a scalar/enum variant renders as a single
6
+ * nested {@link FieldNode}. Selecting a variant seeds a concrete value for it,
7
+ * preserving the fields the outgoing value shares with the new variant (see
8
+ * {@link switchVariantValue}).
6
9
  */
7
10
  import type { ReactNode } from 'react';
8
11
 
9
12
  import { Field } from '../components/field';
10
13
  import { Select } from '../components/select';
11
- import { classifySchema, type FieldModel } from './classify';
12
- import { defaultValueForSchema } from './default-value';
14
+ import { classifySchema, type FieldModel, type UnionVariant } from './classify';
15
+ import { skeletonValueForSchema } from './default-value';
13
16
  import { FieldGroup } from './field-group';
14
17
  import { FieldNode } from './field-node';
15
18
  import { ObjectFields } from './object-fields';
@@ -18,28 +21,87 @@ import { groupClass } from './styles';
18
21
  import { activeVariantIndex } from './union';
19
22
  import type { JsonSchema, SchemaFormErrors } from './types';
20
23
 
24
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+
21
28
  /**
22
- * A concrete seed for a chosen union variant. Object variants get their
23
- * required-field skeleton; a scalar/enum variant that has no explicit default
29
+ * A concrete seed for a chosen union variant. Object variants get their full
30
+ * skeleton (every required field minted, so the seed validates against the
31
+ * variant's own schema); a scalar/enum variant that has no explicit default
24
32
  * still needs a non-`undefined` value so the selection sticks (otherwise
25
33
  * `activeVariantIndex` reads it back as "no variant" and the picker snaps empty).
26
34
  */
27
35
  function seedVariantValue(schema: JsonSchema, root: JsonSchema): unknown {
28
- const seeded = defaultValueForSchema(schema, root);
36
+ const seeded = skeletonValueForSchema(schema, root);
29
37
  if (seeded !== undefined) return seeded;
30
38
  const resolved = resolveRef(schema, root);
31
39
  if (resolved.const !== undefined) return resolved.const;
32
40
  if (resolved.enum !== undefined && resolved.enum.length > 0) return resolved.enum[0];
33
- switch (classifySchema(schema, root).model.kind) {
34
- case 'string':
35
- return '';
36
- case 'number':
37
- return 0;
38
- case 'boolean':
39
- return false;
40
- default:
41
- return undefined;
41
+ return undefined;
42
+ }
43
+
44
+ /**
45
+ * The value committed when the user switches to `target`. Typed values SURVIVE
46
+ * the switch: the target's skeleton is minted first, then every top-level field
47
+ * of the current value whose NAME exists in the target variant's properties is
48
+ * carried over wholesale — the merge happens at the top level only, each carried
49
+ * field keeping its nested contents as-is — so fields both variants share are
50
+ * never silently discarded. Only fields absent from the current value keep
51
+ * their freshly minted seed. The discriminator tag is never carried: it always
52
+ * comes from the target's seed (the old tag would re-select the old variant).
53
+ * A non-object side (scalar variant, or a non-object current value) has no
54
+ * fields to carry, so it reseeds wholesale.
55
+ */
56
+ function switchVariantValue(
57
+ current: unknown,
58
+ target: UnionVariant,
59
+ discriminator: string | undefined,
60
+ root: JsonSchema,
61
+ ): unknown {
62
+ const seeded = seedVariantValue(target.schema, root);
63
+ if (!isPlainObject(current) || !isPlainObject(seeded)) return seeded;
64
+ const { model } = classifySchema(target.schema, root);
65
+ if (model.kind !== 'object') return seeded;
66
+ const targetNames = new Set(model.properties.map(([name]) => name));
67
+ const merged: Record<string, unknown> = { ...seeded };
68
+ for (const [name, held] of Object.entries(current)) {
69
+ if (name === discriminator) continue;
70
+ if (targetNames.has(name)) merged[name] = held;
71
+ }
72
+ return merged;
73
+ }
74
+
75
+ /** Title-case a property name: `kind` → "Kind", `content_type` → "Content Type". */
76
+ function titleCaseName(name: string): string {
77
+ return name
78
+ .split(/[_\-\s]+/)
79
+ .filter((part) => part.length > 0)
80
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
81
+ .join(' ');
82
+ }
83
+
84
+ /**
85
+ * The variant picker's label. A discriminated union names the picker after the
86
+ * discriminator property it sets — the property's schema `title` when a variant
87
+ * declares one, else the property name title-cased — so the picker reads as the
88
+ * field it is ("Kind", "Format"). Only a union without a discriminator falls
89
+ * back to the generic "Variant".
90
+ */
91
+ function pickerLabel(
92
+ discriminator: string | undefined,
93
+ variants: readonly UnionVariant[],
94
+ root: JsonSchema,
95
+ ): string {
96
+ if (discriminator === undefined) return 'Variant';
97
+ for (const variant of variants) {
98
+ const property = resolveRef(variant.schema, root).properties?.[discriminator];
99
+ if (property === undefined) continue;
100
+ const title = property.title ?? resolveRef(property, root).title;
101
+ if (typeof title === 'string' && title.length > 0) return title;
42
102
  }
103
+ const cased = titleCaseName(discriminator);
104
+ return cased.length > 0 ? cased : 'Variant';
43
105
  }
44
106
 
45
107
  export function UnionField({
@@ -74,15 +136,18 @@ export function UnionField({
74
136
  const position = Number(key);
75
137
  const variant = model.variants[position];
76
138
  if (variant === undefined) return;
77
- onChange(seedVariantValue(variant.schema, root));
139
+ onChange(switchVariantValue(value, variant, model.discriminator, root));
78
140
  };
79
141
 
80
142
  const active = index === -1 ? undefined : model.variants[index];
81
143
  const activeClassified = active === undefined ? undefined : classifySchema(active.schema, root);
82
144
 
83
145
  return (
84
- <FieldGroup heading={heading} description={description} error={error} atRoot={false}>
85
- <Field label="Variant">
146
+ // At the form root the union sits inside a field the HOST already labels
147
+ // and describes — a synthetic heading there is noise, so it is suppressed
148
+ // exactly as a root object's is; nested unions keep theirs.
149
+ <FieldGroup heading={heading} description={description} error={error} atRoot={path === ''}>
150
+ <Field label={pickerLabel(model.discriminator, model.variants, root)}>
86
151
  <Select
87
152
  options={options}
88
153
  value={index === -1 ? '' : String(index)}
@@ -102,6 +167,7 @@ export function UnionField({
102
167
  errors={errors}
103
168
  idPrefix={idPrefix}
104
169
  skip={model.discriminator}
170
+ markRequired
105
171
  />
106
172
  </div>
107
173
  ) : active !== undefined ? (