@tai42/studio-sdk 20.0.0 → 22.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.
- package/dist/components/chip-group.d.ts +42 -0
- package/dist/components/chip-group.d.ts.map +1 -0
- package/dist/components/chip-group.js +85 -0
- package/dist/components/chip-group.js.map +1 -0
- package/dist/components/components.css +16 -3
- package/dist/components/execution-key-picker.js.map +1 -1
- package/dist/components/extension-grouping.d.ts +8 -12
- package/dist/components/extension-grouping.d.ts.map +1 -1
- package/dist/components/extension-grouping.js +12 -15
- package/dist/components/extension-grouping.js.map +1 -1
- package/dist/components/extension-picker.d.ts +3 -4
- package/dist/components/extension-picker.d.ts.map +1 -1
- package/dist/components/extension-picker.js +10 -10
- package/dist/components/extension-picker.js.map +1 -1
- package/dist/components/fleet-report.d.ts +10 -5
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +20 -3
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/json-tree-model.d.ts +19 -7
- package/dist/components/json-tree-model.d.ts.map +1 -1
- package/dist/components/json-tree-model.js +32 -8
- package/dist/components/json-tree-model.js.map +1 -1
- package/dist/components/json-tree.d.ts +10 -1
- package/dist/components/json-tree.d.ts.map +1 -1
- package/dist/components/json-tree.js +7 -4
- package/dist/components/json-tree.js.map +1 -1
- package/dist/components/select.d.ts +6 -0
- package/dist/components/select.d.ts.map +1 -1
- package/dist/components/select.js +3 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/served-media.d.ts +50 -4
- package/dist/components/served-media.d.ts.map +1 -1
- package/dist/components/served-media.js +69 -19
- package/dist/components/served-media.js.map +1 -1
- package/dist/env-markers.d.ts +25 -0
- package/dist/env-markers.d.ts.map +1 -0
- package/dist/env-markers.js +63 -0
- package/dist/env-markers.js.map +1 -0
- package/dist/hooks/interactions-stream.d.ts.map +1 -1
- package/dist/hooks/interactions-stream.js +9 -7
- package/dist/hooks/interactions-stream.js.map +1 -1
- package/dist/hooks/useApi.d.ts +2264 -912
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.d.ts +5 -9
- package/dist/hooks/useCapabilities.d.ts.map +1 -1
- package/dist/hooks/useCapabilities.js +8 -33
- package/dist/hooks/useCapabilities.js.map +1 -1
- package/dist/host.d.ts +2 -0
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +8 -0
- package/dist/host.js.map +1 -1
- package/dist/index.d.ts +12 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/plugin/coverage.d.ts +14 -0
- package/dist/plugin/coverage.d.ts.map +1 -0
- package/dist/plugin/coverage.js +14 -0
- package/dist/plugin/coverage.js.map +1 -0
- package/dist/plugin/registry.d.ts +11 -1
- package/dist/plugin/registry.d.ts.map +1 -1
- package/dist/plugin/registry.js +33 -4
- package/dist/plugin/registry.js.map +1 -1
- package/dist/plugin/types.d.ts +43 -1
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.d.ts +11 -1
- package/dist/schema-form/SchemaForm.d.ts.map +1 -1
- package/dist/schema-form/SchemaForm.js +2 -2
- package/dist/schema-form/SchemaForm.js.map +1 -1
- package/dist/schema-form/classify.d.ts.map +1 -1
- package/dist/schema-form/classify.js +54 -2
- package/dist/schema-form/classify.js.map +1 -1
- package/dist/schema-form/date-range-field.d.ts +44 -0
- package/dist/schema-form/date-range-field.d.ts.map +1 -0
- package/dist/schema-form/date-range-field.js +72 -0
- package/dist/schema-form/date-range-field.js.map +1 -0
- package/dist/schema-form/enum-field.d.ts +3 -1
- package/dist/schema-form/enum-field.d.ts.map +1 -1
- package/dist/schema-form/enum-field.js +8 -2
- package/dist/schema-form/enum-field.js.map +1 -1
- package/dist/schema-form/field-model.d.ts +20 -1
- package/dist/schema-form/field-model.d.ts.map +1 -1
- package/dist/schema-form/field-node.d.ts +10 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +6 -3
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/index.d.ts +3 -1
- package/dist/schema-form/index.d.ts.map +1 -1
- package/dist/schema-form/index.js +1 -0
- package/dist/schema-form/index.js.map +1 -1
- package/dist/schema-form/multiselect-field.d.ts.map +1 -1
- package/dist/schema-form/multiselect-field.js +11 -0
- package/dist/schema-form/multiselect-field.js.map +1 -1
- package/dist/schema-form/object-fields.d.ts +9 -1
- package/dist/schema-form/object-fields.d.ts.map +1 -1
- package/dist/schema-form/object-fields.js +16 -3
- package/dist/schema-form/object-fields.js.map +1 -1
- package/dist/schema-form/string-field.d.ts +6 -1
- package/dist/schema-form/string-field.d.ts.map +1 -1
- package/dist/schema-form/string-field.js +42 -8
- package/dist/schema-form/string-field.js.map +1 -1
- package/dist/schema-form/types.d.ts +36 -0
- package/dist/schema-form/types.d.ts.map +1 -1
- package/dist/state-binding/UpdateRow.d.ts +2 -2
- package/dist/state-binding/UpdateRow.js +1 -1
- package/dist/state-binding/UpdateRow.js.map +1 -1
- package/dist/state-binding/adapter.d.ts +12 -8
- package/dist/state-binding/adapter.d.ts.map +1 -1
- package/dist/state-binding/adapter.js +30 -13
- package/dist/state-binding/adapter.js.map +1 -1
- package/package.json +5 -4
- package/src/components/chip-group.tsx +157 -0
- package/src/components/components.css +16 -3
- package/src/components/execution-key-picker.tsx +1 -1
- package/src/components/extension-grouping.ts +22 -22
- package/src/components/extension-picker.tsx +13 -14
- package/src/components/fleet-report.tsx +66 -7
- package/src/components/json-tree-model.ts +36 -8
- package/src/components/json-tree.tsx +24 -4
- package/src/components/select.tsx +11 -1
- package/src/components/served-media.tsx +150 -16
- package/src/env-markers.ts +70 -0
- package/src/hooks/interactions-stream.ts +9 -7
- package/src/hooks/useCapabilities.ts +8 -31
- package/src/host.ts +2 -0
- package/src/index.ts +22 -2
- package/src/plugin/coverage.ts +23 -0
- package/src/plugin/registry.ts +58 -3
- package/src/plugin/types.ts +45 -1
- package/src/schema-form/SchemaForm.tsx +10 -0
- package/src/schema-form/classify.ts +59 -3
- package/src/schema-form/date-range-field.tsx +167 -0
- package/src/schema-form/enum-field.tsx +20 -0
- package/src/schema-form/field-model.ts +24 -2
- package/src/schema-form/field-node.tsx +19 -0
- package/src/schema-form/index.ts +10 -1
- package/src/schema-form/multiselect-field.tsx +21 -0
- package/src/schema-form/object-fields.tsx +36 -2
- package/src/schema-form/string-field.tsx +72 -1
- package/src/schema-form/types.ts +38 -0
- package/src/state-binding/README.md +10 -7
- package/src/state-binding/UpdateRow.tsx +3 -3
- package/src/state-binding/adapter.ts +31 -16
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A named template `update` jq declares an input object (its `params` / declared
|
|
5
5
|
* input keys). A binding must map the run's OUTPUT (and the run's INPUT) into that
|
|
6
|
-
* object; that mapping is the `adapter` — a single jq program over
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* object; that mapping is the `adapter` — a single jq program over the tool output
|
|
7
|
+
* (its `.`) with the run input bound as `$input`, that CONSTRUCTS the update jq's
|
|
8
|
+
* declared input (which the update jq then reads as `$input`, over the record).
|
|
9
9
|
*
|
|
10
10
|
* The editor authors the adapter as a ROW form: one row per declared input key,
|
|
11
11
|
* each row picking one of three value sources — a picked field (from the run's
|
|
@@ -21,13 +21,19 @@ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
21
21
|
export function jqKey(key) {
|
|
22
22
|
return IDENTIFIER.test(key) ? key : JSON.stringify(key);
|
|
23
23
|
}
|
|
24
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* A picked field rendered as a jq path: an output field reads off the program root
|
|
26
|
+
* (`.user.name`, `.["odd key"]`, the whole output `.`), an input field off the bound run
|
|
27
|
+
* input (`$input.user.name`, the whole input `$input`).
|
|
28
|
+
*/
|
|
25
29
|
export function fieldPathToJq(root, path) {
|
|
26
|
-
let out =
|
|
30
|
+
let out = root === 'input' ? '$input' : '';
|
|
27
31
|
for (const segment of path) {
|
|
28
|
-
out += IDENTIFIER.test(segment)
|
|
32
|
+
out += IDENTIFIER.test(segment)
|
|
33
|
+
? `.${segment}`
|
|
34
|
+
: `${out === '' ? '.' : ''}[${JSON.stringify(segment)}]`;
|
|
29
35
|
}
|
|
30
|
-
return out;
|
|
36
|
+
return out === '' ? '.' : out;
|
|
31
37
|
}
|
|
32
38
|
/** The jq fragment ONE row's value compiles to — the row's "show jq". */
|
|
33
39
|
export function rowValueJq(source) {
|
|
@@ -58,7 +64,7 @@ export function rowValueJq(source) {
|
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
60
66
|
* Compile a mapping form into ONE adapter jq that constructs the update jq's
|
|
61
|
-
* declared input over
|
|
67
|
+
* declared input over the tool output and `$input`. An empty form compiles to the
|
|
62
68
|
* empty object `{}`; a blank target, a duplicate target, or an invalid source is a
|
|
63
69
|
* loud error.
|
|
64
70
|
*/
|
|
@@ -93,7 +99,7 @@ export function defaultRowsForInput(params) {
|
|
|
93
99
|
// `compileAdapter` emits a canonical, PARSEABLE shape so a stored adapter reopens as
|
|
94
100
|
// the form it was authored in (never UI-only state): the object
|
|
95
101
|
// `{ <key>: (<value>), … }` where `<key>` is a bare identifier or a JSON string and
|
|
96
|
-
// `<value>` is one of — a field path
|
|
102
|
+
// `<value>` is one of — a field path `.…` (output) / `$input…` (input), a JSON literal, or a raw
|
|
97
103
|
// jq expression. `parseAdapter` inverts it; `parse(compileAdapter(rows).jq) === rows`
|
|
98
104
|
// for every row kind whose literal is already canonical JSON. A shape it does not
|
|
99
105
|
// recognise returns `null` — the editor falls back to the raw-jq escape hatch.
|
|
@@ -102,11 +108,22 @@ const BRACKET_SEGMENT = /^\[("(?:[^"\\]|\\.)*")\]/;
|
|
|
102
108
|
const ADAPTER_KEY = /^("(?:[^"\\]|\\.)*"|[A-Za-z_][A-Za-z0-9_]*)\s*:\s*\(/;
|
|
103
109
|
/** Recover a field source from a pure path expression, or `null` if it is not one. */
|
|
104
110
|
export function parseFieldPath(value) {
|
|
105
|
-
|
|
106
|
-
|
|
111
|
+
let root;
|
|
112
|
+
let rest;
|
|
113
|
+
if (value === '.')
|
|
114
|
+
return { kind: 'field', root: 'output', path: [] };
|
|
115
|
+
if (value.startsWith('$input')) {
|
|
116
|
+
root = 'input';
|
|
117
|
+
rest = value.slice('$input'.length);
|
|
118
|
+
}
|
|
119
|
+
else if (value.startsWith('.')) {
|
|
120
|
+
root = 'output';
|
|
121
|
+
// A root bracket (`.["odd key"]`) drops its leading dot; a root key keeps it (`.a`).
|
|
122
|
+
rest = value.startsWith('.[') ? value.slice(1) : value;
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
107
125
|
return null;
|
|
108
|
-
|
|
109
|
-
let rest = value.slice(head[0].length);
|
|
126
|
+
}
|
|
110
127
|
const path = [];
|
|
111
128
|
while (rest.length > 0) {
|
|
112
129
|
if (rest.startsWith('.')) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/state-binding/adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAqCH,MAAM,UAAU,GAAG,0BAA0B,CAAC;AAE9C,sFAAsF;AACtF,MAAM,UAAU,KAAK,CAAC,GAAW;IAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../src/state-binding/adapter.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tai42/studio-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "22.0.0",
|
|
4
4
|
"description": "The Studio plugin contract, shared UI components, and hooks that every feature builds on.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "tai42 <oss@tai42.ai>",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"@radix-ui/react-select": "^2.3.2",
|
|
97
97
|
"@radix-ui/react-tabs": "^1.1.21",
|
|
98
98
|
"@radix-ui/react-tooltip": "^1.2.16",
|
|
99
|
-
"@tai42/api-client": "
|
|
99
|
+
"@tai42/api-client": "22.0.0"
|
|
100
100
|
},
|
|
101
101
|
"scripts": {
|
|
102
102
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -104,7 +104,8 @@
|
|
|
104
104
|
"lint": "eslint src scripts --max-warnings 0",
|
|
105
105
|
"format:check": "prettier --check \"src/**/*.{ts,tsx,css}\" \"scripts/**/*.mjs\"",
|
|
106
106
|
"test": "vitest run",
|
|
107
|
-
"api:
|
|
108
|
-
"api:
|
|
107
|
+
"api:build": "tsc -b tsconfig.build.json --clean && pnpm run build",
|
|
108
|
+
"api:check": "pnpm run api:build && api-extractor run --config config/api-extractor.index.json && api-extractor run --config config/api-extractor.host.json && api-extractor run --config config/api-extractor.testing.json && api-extractor run --config config/api-extractor.schema-form.json",
|
|
109
|
+
"api:update": "pnpm run api:build && api-extractor run --local --config config/api-extractor.index.json && api-extractor run --local --config config/api-extractor.host.json && api-extractor run --local --config config/api-extractor.testing.json && api-extractor run --local --config config/api-extractor.schema-form.json"
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ChipGroup` — a tap-to-select grid of pill chips (`.tai-chip`), in two modes.
|
|
3
|
+
*
|
|
4
|
+
* `single` is a `role="radiogroup"` of `role="radio"` chips carrying `aria-checked`:
|
|
5
|
+
* exactly one value is selected, the arrow keys move the selection, and only the
|
|
6
|
+
* selected chip (or the first, when none is) sits in the tab order. `multi` is a
|
|
7
|
+
* group of toggle buttons carrying `aria-pressed`: any number are selected, the
|
|
8
|
+
* arrow keys move focus, and Space/Enter (the native button activation) toggles the
|
|
9
|
+
* focused chip.
|
|
10
|
+
*
|
|
11
|
+
* Roving tabindex and arrow-key movement are owned here — there is no multi-select
|
|
12
|
+
* primitive to borrow — so both modes share one keyboard model: ←/↑ to the previous
|
|
13
|
+
* chip, →/↓ to the next (wrapping), Home/End to the ends, skipping disabled chips.
|
|
14
|
+
* The handler rides each chip (a button, an interactive element) rather than the
|
|
15
|
+
* container, so no static element carries an interaction. Like `RadioGroup`, the
|
|
16
|
+
* group takes its accessible name from an enclosing group `Field`, not a label of
|
|
17
|
+
* its own.
|
|
18
|
+
*/
|
|
19
|
+
import type { KeyboardEvent, ReactNode } from 'react';
|
|
20
|
+
import { useRef, useState } from 'react';
|
|
21
|
+
|
|
22
|
+
import { useFieldControl } from './field';
|
|
23
|
+
|
|
24
|
+
/** One selectable chip: its answer `value` and the visible `label`. */
|
|
25
|
+
export interface ChipOption {
|
|
26
|
+
readonly value: string;
|
|
27
|
+
readonly label: string;
|
|
28
|
+
readonly disabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type ChipGroupProps =
|
|
32
|
+
| {
|
|
33
|
+
/** Exactly one value selected — a `radiogroup` of radios. */
|
|
34
|
+
readonly mode: 'single';
|
|
35
|
+
readonly options: readonly ChipOption[];
|
|
36
|
+
readonly value: string;
|
|
37
|
+
readonly onValueChange: (value: string) => void;
|
|
38
|
+
readonly disabled?: boolean;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
/** Any number selected — a group of `aria-pressed` toggle buttons. */
|
|
42
|
+
readonly mode: 'multi';
|
|
43
|
+
readonly options: readonly ChipOption[];
|
|
44
|
+
readonly value: readonly string[];
|
|
45
|
+
readonly onValueChange: (value: readonly string[]) => void;
|
|
46
|
+
readonly disabled?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export function ChipGroup(props: ChipGroupProps): ReactNode {
|
|
50
|
+
const { options, disabled = false } = props;
|
|
51
|
+
const field = useFieldControl();
|
|
52
|
+
const chipRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
53
|
+
|
|
54
|
+
const isSelected = (option: ChipOption): boolean =>
|
|
55
|
+
props.mode === 'single' ? props.value === option.value : props.value.includes(option.value);
|
|
56
|
+
|
|
57
|
+
// The chip in the tab order: the (first) selected one, else the first enabled
|
|
58
|
+
// one — so Tab lands on the active choice and the arrows rove from there.
|
|
59
|
+
const [rovingIndex, setRovingIndex] = useState<number>(() => {
|
|
60
|
+
const selected = options.findIndex(isSelected);
|
|
61
|
+
if (selected >= 0) return selected;
|
|
62
|
+
const firstEnabled = options.findIndex((option) => option.disabled !== true);
|
|
63
|
+
return firstEnabled >= 0 ? firstEnabled : 0;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const enabledIndices = options
|
|
67
|
+
.map((option, index) => (option.disabled === true ? -1 : index))
|
|
68
|
+
.filter((index) => index >= 0);
|
|
69
|
+
|
|
70
|
+
const focusChip = (index: number): void => {
|
|
71
|
+
setRovingIndex(index);
|
|
72
|
+
chipRefs.current[index]?.focus();
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const select = (option: ChipOption): void => {
|
|
76
|
+
if (props.mode === 'single') {
|
|
77
|
+
props.onValueChange(option.value);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const chosen = new Set(props.value);
|
|
81
|
+
if (chosen.has(option.value)) chosen.delete(option.value);
|
|
82
|
+
else chosen.add(option.value);
|
|
83
|
+
// Keep the OPTIONS' declared order, so the emitted array reads the way the form
|
|
84
|
+
// lists the choices rather than the order the chips were pressed.
|
|
85
|
+
props.onValueChange(options.map((candidate) => candidate.value).filter((v) => chosen.has(v)));
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const onChipKeyDown = (event: KeyboardEvent<HTMLButtonElement>): void => {
|
|
89
|
+
if (disabled || enabledIndices.length === 0) return;
|
|
90
|
+
const position = enabledIndices.indexOf(rovingIndex);
|
|
91
|
+
let nextPosition: number;
|
|
92
|
+
switch (event.key) {
|
|
93
|
+
case 'ArrowRight':
|
|
94
|
+
case 'ArrowDown':
|
|
95
|
+
nextPosition = (position + 1) % enabledIndices.length;
|
|
96
|
+
break;
|
|
97
|
+
case 'ArrowLeft':
|
|
98
|
+
case 'ArrowUp':
|
|
99
|
+
nextPosition = (position - 1 + enabledIndices.length) % enabledIndices.length;
|
|
100
|
+
break;
|
|
101
|
+
case 'Home':
|
|
102
|
+
nextPosition = 0;
|
|
103
|
+
break;
|
|
104
|
+
case 'End':
|
|
105
|
+
nextPosition = enabledIndices.length - 1;
|
|
106
|
+
break;
|
|
107
|
+
default:
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
const nextIndex = enabledIndices[nextPosition];
|
|
112
|
+
if (nextIndex === undefined) return;
|
|
113
|
+
const nextOption = options[nextIndex];
|
|
114
|
+
if (nextOption === undefined) return;
|
|
115
|
+
focusChip(nextIndex);
|
|
116
|
+
// A radiogroup selects as it moves; a multi-select group only moves focus and
|
|
117
|
+
// leaves Space/Enter to toggle.
|
|
118
|
+
if (props.mode === 'single') props.onValueChange(nextOption.value);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
role={props.mode === 'single' ? 'radiogroup' : 'group'}
|
|
124
|
+
className="tai-row"
|
|
125
|
+
aria-describedby={field['aria-describedby']}
|
|
126
|
+
>
|
|
127
|
+
{options.map((option, index) => {
|
|
128
|
+
const selected = isSelected(option);
|
|
129
|
+
return (
|
|
130
|
+
<button
|
|
131
|
+
key={option.value}
|
|
132
|
+
ref={(element) => {
|
|
133
|
+
chipRefs.current[index] = element;
|
|
134
|
+
}}
|
|
135
|
+
type="button"
|
|
136
|
+
className="tai-chip"
|
|
137
|
+
role={props.mode === 'single' ? 'radio' : undefined}
|
|
138
|
+
aria-checked={props.mode === 'single' ? selected : undefined}
|
|
139
|
+
aria-pressed={props.mode === 'multi' ? selected : undefined}
|
|
140
|
+
// `role="group"` does not support `aria-invalid` (ARIA 1.2), so the
|
|
141
|
+
// chips carry the enclosing errored Field's invalid state.
|
|
142
|
+
aria-invalid={field['aria-invalid']}
|
|
143
|
+
tabIndex={index === rovingIndex ? 0 : -1}
|
|
144
|
+
disabled={disabled || option.disabled === true}
|
|
145
|
+
onClick={() => {
|
|
146
|
+
focusChip(index);
|
|
147
|
+
select(option);
|
|
148
|
+
}}
|
|
149
|
+
onKeyDown={onChipKeyDown}
|
|
150
|
+
>
|
|
151
|
+
{option.label}
|
|
152
|
+
</button>
|
|
153
|
+
);
|
|
154
|
+
})}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
@@ -1061,11 +1061,16 @@
|
|
|
1061
1061
|
color var(--tai-motion-fast) ease;
|
|
1062
1062
|
}
|
|
1063
1063
|
|
|
1064
|
-
.tai-chip:not(.tai-chip-static):not([aria-pressed='true']):hover:not(
|
|
1064
|
+
.tai-chip:not(.tai-chip-static):not([aria-pressed='true']):not([aria-checked='true']):hover:not(
|
|
1065
|
+
:disabled
|
|
1066
|
+
) {
|
|
1065
1067
|
background: var(--tai-color-surface);
|
|
1066
1068
|
}
|
|
1067
1069
|
|
|
1068
|
-
|
|
1070
|
+
/* A selected chip, pressed (a multi-select toggle) OR checked (a single-select
|
|
1071
|
+
radio chip): both wear the accent tint. */
|
|
1072
|
+
.tai-chip[aria-pressed='true'],
|
|
1073
|
+
.tai-chip[aria-checked='true'] {
|
|
1069
1074
|
background: var(--tai-color-accent-tint);
|
|
1070
1075
|
border-color: var(--tai-color-accent);
|
|
1071
1076
|
color: var(--tai-color-accent-on-tint);
|
|
@@ -1073,7 +1078,8 @@
|
|
|
1073
1078
|
|
|
1074
1079
|
/* A selected chip KEEPS its tint on hover and strengthens its border instead —
|
|
1075
1080
|
the one part that moves in both themes (see the segment rule above). */
|
|
1076
|
-
.tai-chip[aria-pressed='true']:hover:not(:disabled)
|
|
1081
|
+
.tai-chip[aria-pressed='true']:hover:not(:disabled),
|
|
1082
|
+
.tai-chip[aria-checked='true']:hover:not(:disabled) {
|
|
1077
1083
|
border-color: var(--tai-color-accent-hover);
|
|
1078
1084
|
}
|
|
1079
1085
|
|
|
@@ -2075,6 +2081,13 @@
|
|
|
2075
2081
|
border-bottom: 1px solid var(--tai-color-border);
|
|
2076
2082
|
}
|
|
2077
2083
|
|
|
2084
|
+
/* The bar already pads its edges, so the brand drops its own inline padding here:
|
|
2085
|
+
at 320 px the brand, the three-option theme switch and the menu button then fit
|
|
2086
|
+
on one row, and the switch never wraps into a box taller than the bar. */
|
|
2087
|
+
.tai-topbar .tai-brand {
|
|
2088
|
+
padding-inline: 0;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2078
2091
|
/* -- Master / detail ---------------------------------------------------------------
|
|
2079
2092
|
`data-pane` drives the layout at every width: `list` gives the list the full
|
|
2080
2093
|
width with no empty detail pane; `detail` shows a balanced two-pane split.
|
|
@@ -39,7 +39,7 @@ export function isExecutionKeyListEmpty(query: ExecutionKeyQuery): boolean {
|
|
|
39
39
|
function keyFingerprint(key: TokensPayload[number]): string | undefined {
|
|
40
40
|
const data = key.policy_data;
|
|
41
41
|
if (typeof data !== 'object' || data === null) return undefined;
|
|
42
|
-
const value =
|
|
42
|
+
const value = data.key_fingerprint;
|
|
43
43
|
return typeof value === 'string' ? value : undefined;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -4,26 +4,19 @@
|
|
|
4
4
|
* `ExtensionPicker`. Kept free of React so the grouping logic is unit-tested
|
|
5
5
|
* directly.
|
|
6
6
|
*
|
|
7
|
-
* The `/api/extensions` catalog is a flat list of `{ name, kind }` entries. Two
|
|
7
|
+
* The `/api/extensions` catalog is a flat list of `{ name, kind, multiple }` entries. Two
|
|
8
8
|
* independent groupings live here:
|
|
9
9
|
* - `groupIntoFamilies` groups a tool's composed branch VARIANTS (which share a
|
|
10
10
|
* base tool name and append underscore suffixes — `tool`, `tool_a`, `tool_a_b`)
|
|
11
11
|
* back into one FAMILY keyed by that base name (the catalog view);
|
|
12
|
-
* - `groupByKind` groups the catalog by extension KIND (the picker view),
|
|
13
|
-
* the `
|
|
12
|
+
* - `groupByKind` groups the catalog by extension KIND (the picker view), each
|
|
13
|
+
* group carrying the served `multiple` flag of its kind.
|
|
14
14
|
* `kindVariant` maps each serialized `kind` value to a design-system Badge variant.
|
|
15
15
|
*/
|
|
16
16
|
import type { Extension } from '@tai42/api-client';
|
|
17
17
|
|
|
18
18
|
import type { BadgeProps } from './badge';
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* The single non-stackable extension kind: at most one `backend` extension may
|
|
22
|
-
* appear in a combo (the `backend` kind is declared non-stackable, `multiple=False`).
|
|
23
|
-
* The `ExtensionPicker` enforces single-select for this kind.
|
|
24
|
-
*/
|
|
25
|
-
export const NON_STACKABLE_KIND = 'backend';
|
|
26
|
-
|
|
27
20
|
/** One base tool and every extension entry that branches from it. */
|
|
28
21
|
export interface ExtensionFamily {
|
|
29
22
|
/** The base tool name (the segment before the first underscore). */
|
|
@@ -36,8 +29,9 @@ export interface ExtensionFamily {
|
|
|
36
29
|
export interface ExtensionKindGroup {
|
|
37
30
|
/** The serialized kind value (`wrapper` / `transformer` / `backend`). */
|
|
38
31
|
readonly kind: string;
|
|
39
|
-
/** Whether this kind is
|
|
40
|
-
|
|
32
|
+
/** Whether a combo may carry several extensions of this kind; `false` is single-select
|
|
33
|
+
* in the picker. Served per kind by the catalog. */
|
|
34
|
+
readonly multiple: boolean;
|
|
41
35
|
/** The kind's entries, ordered by name. */
|
|
42
36
|
readonly members: readonly Extension[];
|
|
43
37
|
}
|
|
@@ -79,23 +73,29 @@ export function groupIntoFamilies(extensions: readonly Extension[]): ExtensionFa
|
|
|
79
73
|
/**
|
|
80
74
|
* Group a flat extension list by `kind`, for the picker. Groups are ordered by
|
|
81
75
|
* kind and each group's members by name, so the render order is stable regardless
|
|
82
|
-
* of the server's list order.
|
|
76
|
+
* of the server's list order. Each group carries its kind's served `multiple`; a kind
|
|
77
|
+
* whose entries disagree is a catalog fault and throws.
|
|
83
78
|
*/
|
|
84
79
|
export function groupByKind(extensions: readonly Extension[]): ExtensionKindGroup[] {
|
|
85
|
-
const byKind = new Map<string, Extension[]>();
|
|
80
|
+
const byKind = new Map<string, { multiple: boolean; members: Extension[] }>();
|
|
86
81
|
for (const extension of extensions) {
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
byKind.set(extension.kind, [extension]);
|
|
82
|
+
const group = byKind.get(extension.kind);
|
|
83
|
+
if (group === undefined) {
|
|
84
|
+
byKind.set(extension.kind, { multiple: extension.multiple, members: [extension] });
|
|
85
|
+
continue;
|
|
92
86
|
}
|
|
87
|
+
if (group.multiple !== extension.multiple) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`extension kind ${extension.kind} is served as both single- and multi-select`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
group.members.push(extension);
|
|
93
93
|
}
|
|
94
94
|
return [...byKind.entries()]
|
|
95
|
-
.map(([kind,
|
|
95
|
+
.map(([kind, group]) => ({
|
|
96
96
|
kind,
|
|
97
|
-
|
|
98
|
-
members: [...members].sort((a, b) => a.name.localeCompare(b.name)),
|
|
97
|
+
multiple: group.multiple,
|
|
98
|
+
members: [...group.members].sort((a, b) => a.name.localeCompare(b.name)),
|
|
99
99
|
}))
|
|
100
100
|
.sort((a, b) => a.kind.localeCompare(b.kind));
|
|
101
101
|
}
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
* live in the SDK). It is PRESENTATIONAL: the caller fetches `available` and owns
|
|
7
7
|
* the selected `value`; this component fetches nothing.
|
|
8
8
|
*
|
|
9
|
-
* The catalog is GROUPED by `kind`.
|
|
10
|
-
* one
|
|
11
|
-
*
|
|
12
|
-
* freely multi-select. Selection order is preserved (the combo is ordered — the
|
|
9
|
+
* The catalog is GROUPED by `kind`. A kind the catalog serves as not `multiple` is
|
|
10
|
+
* SINGLE-SELECT — checking one of its extensions replaces any other of that kind
|
|
11
|
+
* already in the combo; a `multiple` kind is freely multi-select. Selection order is preserved (the combo is ordered — the
|
|
13
12
|
* stacking order that produces the branch tool name), so a newly-checked extension
|
|
14
13
|
* appends to the end and unchecking removes in place.
|
|
15
14
|
*
|
|
@@ -60,20 +59,20 @@ export function ExtensionPicker({
|
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
|
-
* Toggle a single extension. For a
|
|
64
|
-
* first strips any other selected member of that kind before
|
|
65
|
-
* combo carries at most one
|
|
66
|
-
* preserved: a newly-checked name lands at the end.
|
|
62
|
+
* Toggle a single extension. For a kind the catalog serves as not `multiple`,
|
|
63
|
+
* checking one first strips any other selected member of that kind before
|
|
64
|
+
* appending, so the combo carries at most one of it; unchecking just removes it.
|
|
65
|
+
* Order is preserved: a newly-checked name lands at the end.
|
|
67
66
|
*/
|
|
68
|
-
const toggle = (extension: Extension,
|
|
67
|
+
const toggle = (extension: Extension, multiple: boolean, checked: boolean): void => {
|
|
69
68
|
if (!checked) {
|
|
70
69
|
onChange(value.filter((name) => name !== extension.name));
|
|
71
70
|
return;
|
|
72
71
|
}
|
|
73
72
|
if (selected.has(extension.name)) return;
|
|
74
|
-
const siblingNames =
|
|
75
|
-
?
|
|
76
|
-
:
|
|
73
|
+
const siblingNames = multiple
|
|
74
|
+
? undefined
|
|
75
|
+
: new Set(available.filter((e) => e.kind === extension.kind).map((e) => e.name));
|
|
77
76
|
const kept =
|
|
78
77
|
siblingNames === undefined ? [...value] : value.filter((name) => !siblingNames.has(name));
|
|
79
78
|
onChange([...kept, extension.name]);
|
|
@@ -87,7 +86,7 @@ export function ExtensionPicker({
|
|
|
87
86
|
and the single-select qualifier rides beside it as secondary text. */}
|
|
88
87
|
<div className="tai-row">
|
|
89
88
|
<Badge variant={kindVariant(group.kind)}>{group.kind}</Badge>
|
|
90
|
-
{group.
|
|
89
|
+
{group.multiple ? null : <span className="tai-muted">(single-select)</span>}
|
|
91
90
|
</div>
|
|
92
91
|
<div className="tai-row">
|
|
93
92
|
{group.members.map((extension) => (
|
|
@@ -97,7 +96,7 @@ export function ExtensionPicker({
|
|
|
97
96
|
checked={selected.has(extension.name)}
|
|
98
97
|
disabled={disabled}
|
|
99
98
|
onCheckedChange={(checked) => {
|
|
100
|
-
toggle(extension, group.
|
|
99
|
+
toggle(extension, group.multiple, checked);
|
|
101
100
|
}}
|
|
102
101
|
/>
|
|
103
102
|
))}
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* framing and points the operator back at re-running the deregister, because a
|
|
15
15
|
* reload would re-attach the very server being detached — the wrong remediation.
|
|
16
16
|
*
|
|
17
|
-
* A `converged` summary (every worker applied, or a lone-worker local-only change)
|
|
18
|
-
* renders NOTHING — the calling surface shows its own success. A `degraded` summary
|
|
17
|
+
* A `converged` summary (every worker applied, or a lone-worker local-only change) with
|
|
18
|
+
* no worker awaiting a respawn renders NOTHING — the calling surface shows its own success. A `degraded` summary
|
|
19
19
|
* (the bus was reached but a sibling did not confirm) or an `unreachable` summary (the
|
|
20
20
|
* bus itself failed) renders a loud `role="alert"` panel naming the honest per-worker
|
|
21
21
|
* outcomes: `failed` / `missing` / `departed` / `timed_out` / `resyncing` / `recycling` /
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
* server-supplied string (a worker name, an error/detail message) renders as escaped
|
|
24
24
|
* React text, never an HTML sink.
|
|
25
25
|
*
|
|
26
|
+
* Whatever the status, a worker whose reload named recycle-class settings awaiting a
|
|
27
|
+
* respawn is listed in a warning status panel beside it: the reload applied every other
|
|
28
|
+
* change, and those settings take effect only when that worker restarts.
|
|
29
|
+
*
|
|
26
30
|
* Every state — the headline and each worker's outcome — is a `tai-status` line: an
|
|
27
31
|
* icon MARK plus a word LABEL, with the tone color only reinforcing them, so the
|
|
28
32
|
* report is readable without color perception.
|
|
@@ -30,6 +34,7 @@
|
|
|
30
34
|
import type {
|
|
31
35
|
FleetFailureOutcome,
|
|
32
36
|
FleetReportSummary,
|
|
37
|
+
FleetWorkerAwaitingRespawn,
|
|
33
38
|
FleetWorkerFailure,
|
|
34
39
|
} from '@tai42/api-client';
|
|
35
40
|
import type { ReactNode } from 'react';
|
|
@@ -74,9 +79,10 @@ const OUTCOME_STATUS: Record<FleetFailureOutcome, StatusTone> = {
|
|
|
74
79
|
};
|
|
75
80
|
|
|
76
81
|
/**
|
|
77
|
-
* Render a fleet broadcast's failure state
|
|
78
|
-
* report
|
|
79
|
-
*
|
|
82
|
+
* Render a fleet broadcast's failure state and its workers awaiting a respawn. Returns
|
|
83
|
+
* `null` for an absent report or a converged one with no worker awaiting a respawn
|
|
84
|
+
* (nothing to warn about) so callers can always mount it unconditionally beside their
|
|
85
|
+
* own success line.
|
|
80
86
|
*/
|
|
81
87
|
export interface FleetReportProps {
|
|
82
88
|
readonly summary: FleetReportSummary | null;
|
|
@@ -193,8 +199,50 @@ function DegradedReport({
|
|
|
193
199
|
);
|
|
194
200
|
}
|
|
195
201
|
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
/**
|
|
203
|
+
* The panel for workers whose reload named recycle-class settings awaiting a respawn:
|
|
204
|
+
* the reload applied everything else, and these take effect only when the worker
|
|
205
|
+
* restarts. Guidance the operator acts on, not a failure, so it is a warning status
|
|
206
|
+
* rather than an alert.
|
|
207
|
+
*/
|
|
208
|
+
function AwaitingRespawnReport({
|
|
209
|
+
awaiting,
|
|
210
|
+
}: {
|
|
211
|
+
readonly awaiting: readonly FleetWorkerAwaitingRespawn[];
|
|
212
|
+
}): ReactNode {
|
|
213
|
+
const count = awaiting.length;
|
|
214
|
+
return (
|
|
215
|
+
<div role="status" className="tai-warn-state tai-stack tai-stack-2">
|
|
216
|
+
<strong className="tai-status tai-status-warn">
|
|
217
|
+
<AlertTriangleIcon />
|
|
218
|
+
{`${String(count)} worker${count === 1 ? ' needs' : 's need'} a restart to apply some settings`}
|
|
219
|
+
</strong>
|
|
220
|
+
<p>
|
|
221
|
+
These settings changed in the stored environment after the worker started. The reload
|
|
222
|
+
applied every other change; these take effect only when the worker restarts. Restart it
|
|
223
|
+
through your deployment’s supervisor (on a bare server, restart the server).
|
|
224
|
+
</p>
|
|
225
|
+
<ul className="tai-stack tai-stack-2">
|
|
226
|
+
{awaiting.map((worker) => (
|
|
227
|
+
<li key={worker.name} className="tai-row">
|
|
228
|
+
<span className="tai-mono">{worker.name}</span>
|
|
229
|
+
<span className="tai-muted">waits for a restart:</span>
|
|
230
|
+
<span className="tai-mono">{worker.keys.join(', ')}</span>
|
|
231
|
+
</li>
|
|
232
|
+
))}
|
|
233
|
+
</ul>
|
|
234
|
+
</div>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function FailureReport({
|
|
239
|
+
summary,
|
|
240
|
+
action,
|
|
241
|
+
}: {
|
|
242
|
+
readonly summary: FleetReportSummary;
|
|
243
|
+
readonly action: FleetAction;
|
|
244
|
+
}): ReactNode {
|
|
245
|
+
if (summary.status === 'converged') return null;
|
|
198
246
|
|
|
199
247
|
// Only a saved config change carries the "Change saved" framing; a reload or a
|
|
200
248
|
// deregister saved nothing.
|
|
@@ -218,3 +266,14 @@ export function FleetReport({ summary, action = 'save' }: FleetReportProps): Rea
|
|
|
218
266
|
/>
|
|
219
267
|
);
|
|
220
268
|
}
|
|
269
|
+
|
|
270
|
+
export function FleetReport({ summary, action = 'save' }: FleetReportProps): ReactNode {
|
|
271
|
+
if (summary === null) return null;
|
|
272
|
+
if (summary.status === 'converged' && summary.awaiting.length === 0) return null;
|
|
273
|
+
return (
|
|
274
|
+
<>
|
|
275
|
+
<FailureReport summary={summary} action={action} />
|
|
276
|
+
{summary.awaiting.length > 0 ? <AwaitingRespawnReport awaiting={summary.awaiting} /> : null}
|
|
277
|
+
</>
|
|
278
|
+
);
|
|
279
|
+
}
|