@tai42/studio-sdk 10.0.0 → 11.1.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/README.md +8 -5
- package/dist/components/components.css +41 -0
- package/dist/components/dialog.d.ts +14 -2
- package/dist/components/dialog.d.ts.map +1 -1
- package/dist/components/dialog.js +25 -2
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/dirty-guard.d.ts +23 -10
- package/dist/components/dirty-guard.d.ts.map +1 -1
- package/dist/components/dirty-guard.js +22 -12
- package/dist/components/dirty-guard.js.map +1 -1
- package/dist/components/explorer-view.d.ts.map +1 -1
- package/dist/components/explorer-view.js +2 -2
- package/dist/components/explorer-view.js.map +1 -1
- package/dist/components/field.d.ts.map +1 -1
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/fleet-report.d.ts +12 -3
- package/dist/components/fleet-report.d.ts.map +1 -1
- package/dist/components/fleet-report.js +28 -9
- package/dist/components/fleet-report.js.map +1 -1
- package/dist/components/icons.d.ts +4 -0
- package/dist/components/icons.d.ts.map +1 -1
- package/dist/components/icons.js +5 -0
- package/dist/components/icons.js.map +1 -1
- package/dist/components/tokens.css +18 -6
- package/dist/hooks/useApi.d.ts +348 -2
- package/dist/hooks/useApi.d.ts.map +1 -1
- package/dist/host.d.ts +1 -1
- package/dist/host.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/navigation/context.d.ts +6 -2
- package/dist/navigation/context.d.ts.map +1 -1
- package/dist/navigation/context.js +10 -2
- package/dist/navigation/context.js.map +1 -1
- package/dist/navigation/types.d.ts +1 -0
- package/dist/navigation/types.d.ts.map +1 -1
- package/dist/plugin/host-state.d.ts +16 -0
- package/dist/plugin/host-state.d.ts.map +1 -1
- package/dist/plugin/host-state.js +1 -0
- package/dist/plugin/host-state.js.map +1 -1
- package/dist/plugin/types.d.ts +17 -7
- package/dist/plugin/types.d.ts.map +1 -1
- package/dist/schema-form/SecretRefField.d.ts.map +1 -1
- package/dist/schema-form/SecretRefField.js +3 -2
- package/dist/schema-form/SecretRefField.js.map +1 -1
- package/dist/schema-form/default-value.d.ts +12 -0
- package/dist/schema-form/default-value.d.ts.map +1 -1
- package/dist/schema-form/default-value.js +34 -3
- package/dist/schema-form/default-value.js.map +1 -1
- package/dist/schema-form/field-node.d.ts +7 -0
- package/dist/schema-form/field-node.d.ts.map +1 -1
- package/dist/schema-form/field-node.js +3 -2
- package/dist/schema-form/field-node.js.map +1 -1
- package/dist/schema-form/object-fields.d.ts +3 -1
- package/dist/schema-form/object-fields.d.ts.map +1 -1
- package/dist/schema-form/object-fields.js +2 -2
- package/dist/schema-form/object-fields.js.map +1 -1
- package/dist/schema-form/union-field.d.ts +5 -2
- package/dist/schema-form/union-field.d.ts.map +1 -1
- package/dist/schema-form/union-field.js +68 -15
- package/dist/schema-form/union-field.js.map +1 -1
- package/package.json +2 -2
- package/src/components/components.css +41 -0
- package/src/components/dialog.tsx +36 -1
- package/src/components/dirty-guard.tsx +23 -13
- package/src/components/explorer-view.tsx +9 -2
- package/src/components/field.tsx +10 -5
- package/src/components/fleet-report.tsx +44 -13
- package/src/components/icons.tsx +19 -0
- package/src/components/tokens.css +18 -6
- package/src/host.ts +5 -1
- package/src/index.ts +3 -1
- package/src/navigation/context.tsx +16 -1
- package/src/navigation/types.ts +3 -0
- package/src/plugin/host-state.ts +18 -0
- package/src/plugin/types.ts +17 -7
- package/src/schema-form/SecretRefField.tsx +3 -2
- package/src/schema-form/default-value.ts +36 -3
- package/src/schema-form/field-node.tsx +21 -2
- package/src/schema-form/object-fields.tsx +4 -0
- package/src/schema-form/union-field.tsx +85 -19
|
@@ -2,21 +2,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Field } from '../components/field';
|
|
3
3
|
import { Select } from '../components/select';
|
|
4
4
|
import { classifySchema } from './classify';
|
|
5
|
-
import {
|
|
5
|
+
import { skeletonValueForSchema } from './default-value';
|
|
6
6
|
import { FieldGroup } from './field-group';
|
|
7
7
|
import { FieldNode } from './field-node';
|
|
8
8
|
import { ObjectFields } from './object-fields';
|
|
9
9
|
import { resolveRef } from './resolve';
|
|
10
10
|
import { groupClass } from './styles';
|
|
11
11
|
import { activeVariantIndex } from './union';
|
|
12
|
+
function isPlainObject(value) {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
12
15
|
/**
|
|
13
|
-
* A concrete seed for a chosen union variant. Object variants get their
|
|
14
|
-
* required
|
|
16
|
+
* A concrete seed for a chosen union variant. Object variants get their full
|
|
17
|
+
* skeleton (every required field minted, so the seed validates against the
|
|
18
|
+
* variant's own schema); a scalar/enum variant that has no explicit default
|
|
15
19
|
* still needs a non-`undefined` value so the selection sticks (otherwise
|
|
16
20
|
* `activeVariantIndex` reads it back as "no variant" and the picker snaps empty).
|
|
17
21
|
*/
|
|
18
22
|
function seedVariantValue(schema, root) {
|
|
19
|
-
const seeded =
|
|
23
|
+
const seeded = skeletonValueForSchema(schema, root);
|
|
20
24
|
if (seeded !== undefined)
|
|
21
25
|
return seeded;
|
|
22
26
|
const resolved = resolveRef(schema, root);
|
|
@@ -24,16 +28,65 @@ function seedVariantValue(schema, root) {
|
|
|
24
28
|
return resolved.const;
|
|
25
29
|
if (resolved.enum !== undefined && resolved.enum.length > 0)
|
|
26
30
|
return resolved.enum[0];
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The value committed when the user switches to `target`. Typed values SURVIVE
|
|
35
|
+
* the switch: the target's skeleton is minted first, then every top-level field
|
|
36
|
+
* of the current value whose NAME exists in the target variant's properties is
|
|
37
|
+
* carried over wholesale — the merge happens at the top level only, each carried
|
|
38
|
+
* field keeping its nested contents as-is — so fields both variants share are
|
|
39
|
+
* never silently discarded. Only fields absent from the current value keep
|
|
40
|
+
* their freshly minted seed. The discriminator tag is never carried: it always
|
|
41
|
+
* comes from the target's seed (the old tag would re-select the old variant).
|
|
42
|
+
* A non-object side (scalar variant, or a non-object current value) has no
|
|
43
|
+
* fields to carry, so it reseeds wholesale.
|
|
44
|
+
*/
|
|
45
|
+
function switchVariantValue(current, target, discriminator, root) {
|
|
46
|
+
const seeded = seedVariantValue(target.schema, root);
|
|
47
|
+
if (!isPlainObject(current) || !isPlainObject(seeded))
|
|
48
|
+
return seeded;
|
|
49
|
+
const { model } = classifySchema(target.schema, root);
|
|
50
|
+
if (model.kind !== 'object')
|
|
51
|
+
return seeded;
|
|
52
|
+
const targetNames = new Set(model.properties.map(([name]) => name));
|
|
53
|
+
const merged = { ...seeded };
|
|
54
|
+
for (const [name, held] of Object.entries(current)) {
|
|
55
|
+
if (name === discriminator)
|
|
56
|
+
continue;
|
|
57
|
+
if (targetNames.has(name))
|
|
58
|
+
merged[name] = held;
|
|
59
|
+
}
|
|
60
|
+
return merged;
|
|
61
|
+
}
|
|
62
|
+
/** Title-case a property name: `kind` → "Kind", `content_type` → "Content Type". */
|
|
63
|
+
function titleCaseName(name) {
|
|
64
|
+
return name
|
|
65
|
+
.split(/[_\-\s]+/)
|
|
66
|
+
.filter((part) => part.length > 0)
|
|
67
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
68
|
+
.join(' ');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The variant picker's label. A discriminated union names the picker after the
|
|
72
|
+
* discriminator property it sets — the property's schema `title` when a variant
|
|
73
|
+
* declares one, else the property name title-cased — so the picker reads as the
|
|
74
|
+
* field it is ("Kind", "Format"). Only a union without a discriminator falls
|
|
75
|
+
* back to the generic "Variant".
|
|
76
|
+
*/
|
|
77
|
+
function pickerLabel(discriminator, variants, root) {
|
|
78
|
+
if (discriminator === undefined)
|
|
79
|
+
return 'Variant';
|
|
80
|
+
for (const variant of variants) {
|
|
81
|
+
const property = resolveRef(variant.schema, root).properties?.[discriminator];
|
|
82
|
+
if (property === undefined)
|
|
83
|
+
continue;
|
|
84
|
+
const title = property.title ?? resolveRef(property, root).title;
|
|
85
|
+
if (typeof title === 'string' && title.length > 0)
|
|
86
|
+
return title;
|
|
36
87
|
}
|
|
88
|
+
const cased = titleCaseName(discriminator);
|
|
89
|
+
return cased.length > 0 ? cased : 'Variant';
|
|
37
90
|
}
|
|
38
91
|
export function UnionField({ heading, description, error, model, root, value, onChange, path, errors, idPrefix, }) {
|
|
39
92
|
const index = activeVariantIndex(value, model.variants, model.discriminator, root);
|
|
@@ -46,10 +99,10 @@ export function UnionField({ heading, description, error, model, root, value, on
|
|
|
46
99
|
const variant = model.variants[position];
|
|
47
100
|
if (variant === undefined)
|
|
48
101
|
return;
|
|
49
|
-
onChange(
|
|
102
|
+
onChange(switchVariantValue(value, variant, model.discriminator, root));
|
|
50
103
|
};
|
|
51
104
|
const active = index === -1 ? undefined : model.variants[index];
|
|
52
105
|
const activeClassified = active === undefined ? undefined : classifySchema(active.schema, root);
|
|
53
|
-
return (_jsxs(FieldGroup, { heading: heading, description: description, error: error, atRoot:
|
|
106
|
+
return (_jsxs(FieldGroup, { heading: heading, description: description, error: error, atRoot: path === '', children: [_jsx(Field, { label: pickerLabel(model.discriminator, model.variants, root), children: _jsx(Select, { options: options, value: index === -1 ? '' : String(index), placeholder: "Select a variant\u2026", onValueChange: selectVariant }) }), activeClassified?.model.kind === 'object' ? (_jsx("div", { className: groupClass, children: _jsx(ObjectFields, { properties: activeClassified.model.properties, requiredKeys: activeClassified.model.required, root: root, value: value, onChange: onChange, path: path, errors: errors, idPrefix: idPrefix, skip: model.discriminator, markRequired: true }) })) : active !== undefined ? (_jsx(FieldNode, { schema: active.schema, root: root, value: value, onChange: onChange, path: path, label: active.label, required: true, errors: errors, idPrefix: idPrefix })) : null] }));
|
|
54
107
|
}
|
|
55
108
|
//# sourceMappingURL=union-field.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"union-field.js","sourceRoot":"","sources":["../../src/schema-form/union-field.tsx"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"union-field.js","sourceRoot":"","sources":["../../src/schema-form/union-field.tsx"],"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": "11.1.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>",
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@radix-ui/react-select": "^2.3.2",
|
|
96
96
|
"@radix-ui/react-tabs": "^1.1.16",
|
|
97
97
|
"@radix-ui/react-tooltip": "^1.2.11",
|
|
98
|
-
"@tai42/api-client": "
|
|
98
|
+
"@tai42/api-client": "11.1.0"
|
|
99
99
|
},
|
|
100
100
|
"scripts": {
|
|
101
101
|
"build": "tsc -b tsconfig.build.json && node scripts/copy-assets.mjs",
|
|
@@ -984,6 +984,24 @@
|
|
|
984
984
|
background-image: linear-gradient(var(--tai-color-accent-tint), var(--tai-color-accent-tint));
|
|
985
985
|
}
|
|
986
986
|
|
|
987
|
+
/* The HOST-APPLIED provenance mark on plugin-contributed nav (the shell renders
|
|
988
|
+
it; a plugin cannot suppress or restyle it). A small muted glyph that links to
|
|
989
|
+
the plugin's marketplace row and brightens on hover/focus. It sits beside a nav
|
|
990
|
+
row — per-entry, whether the row sits in a core section or the shared Plugins section. */
|
|
991
|
+
.tai-plugin-badge {
|
|
992
|
+
display: inline-flex;
|
|
993
|
+
align-items: center;
|
|
994
|
+
justify-content: center;
|
|
995
|
+
flex: none;
|
|
996
|
+
color: var(--tai-color-text-muted);
|
|
997
|
+
border-radius: var(--tai-radius-sm);
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.tai-plugin-badge:hover,
|
|
1001
|
+
.tai-plugin-badge:focus-visible {
|
|
1002
|
+
color: var(--tai-color-text);
|
|
1003
|
+
}
|
|
1004
|
+
|
|
987
1005
|
/* Skip-to-content: off-screen until focused, so a keyboard or screen-reader
|
|
988
1006
|
user can bypass the sidebar nav and jump straight to the page content
|
|
989
1007
|
(WCAG 2.4.1). It is the first focusable element in the shell. */
|
|
@@ -2072,6 +2090,29 @@
|
|
|
2072
2090
|
flex-wrap: wrap;
|
|
2073
2091
|
}
|
|
2074
2092
|
|
|
2093
|
+
/* A search field whose magnifier sits INSIDE the input on its leading edge,
|
|
2094
|
+
rather than as a detached glyph beside it. The wrapper is not a flex row —
|
|
2095
|
+
the input is its only in-flow child — so it stays out of the horizontal-strip
|
|
2096
|
+
contract; the icon is taken out of flow and centred over the input's reserved
|
|
2097
|
+
leading padding, and made click-through so a press still lands in the field. */
|
|
2098
|
+
.tai-search-field {
|
|
2099
|
+
position: relative;
|
|
2100
|
+
display: inline-block;
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
.tai-search-field > .tai-icon {
|
|
2104
|
+
position: absolute;
|
|
2105
|
+
inset-inline-start: var(--tai-space-3);
|
|
2106
|
+
top: 50%;
|
|
2107
|
+
transform: translateY(-50%);
|
|
2108
|
+
color: var(--tai-color-text-muted);
|
|
2109
|
+
pointer-events: none;
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
.tai-search-field > .tai-input {
|
|
2113
|
+
padding-inline-start: calc(var(--tai-space-3) + 1.5rem);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2075
2116
|
/* A first-class folder entry (row cell or card): the folder affordance beside
|
|
2076
2117
|
its optional actions, the actions pushed to the trailing edge. */
|
|
2077
2118
|
.tai-explorer-folder {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* (see the props) reshape the panel without giving up any Radix behaviour.
|
|
12
12
|
*/
|
|
13
13
|
import * as RadixDialog from '@radix-ui/react-dialog';
|
|
14
|
-
import type
|
|
14
|
+
import { useRef, type ReactElement, type ReactNode } from 'react';
|
|
15
15
|
|
|
16
16
|
import { assertSlotElement } from '../element-slot';
|
|
17
17
|
import { useModalFocusReturn } from './modal-focus';
|
|
@@ -51,6 +51,18 @@ export interface DialogProps {
|
|
|
51
51
|
* accessible name never depends on the visible chrome.
|
|
52
52
|
*/
|
|
53
53
|
readonly chromeless?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When `false`, the light-dismiss gestures are disabled: Escape and a pointer
|
|
56
|
+
* press outside the panel no longer close it, so the only way out is an explicit
|
|
57
|
+
* action the `children` render (a Done/Cancel button calling `onOpenChange`).
|
|
58
|
+
* The focus trap, the scrim and every other Radix behaviour are unchanged. This
|
|
59
|
+
* is a generic mechanism — it knows nothing of what the dialog holds — for the
|
|
60
|
+
* one-time-reveal case where an accidental dismiss would lose content the server
|
|
61
|
+
* cannot reproduce. Defaults to `true`, the ordinary dismissable modal. This
|
|
62
|
+
* design-system panel renders no built-in close (X) affordance, so there is none
|
|
63
|
+
* to hide here; explicit close stays entirely with the caller's own buttons.
|
|
64
|
+
*/
|
|
65
|
+
readonly dismissable?: boolean;
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
export function Dialog({
|
|
@@ -64,6 +76,7 @@ export function Dialog({
|
|
|
64
76
|
fullscreen = false,
|
|
65
77
|
contentClassName,
|
|
66
78
|
chromeless = false,
|
|
79
|
+
dismissable = true,
|
|
67
80
|
}: DialogProps) {
|
|
68
81
|
const focusReturn = useModalFocusReturn(trigger !== undefined);
|
|
69
82
|
if (trigger !== undefined) assertSlotElement(trigger, 'Dialog `trigger`');
|
|
@@ -72,6 +85,27 @@ export function Dialog({
|
|
|
72
85
|
if (fullscreen) contentClasses.push('tai-dialog-fullscreen');
|
|
73
86
|
if (contentClassName !== undefined) contentClasses.push(contentClassName);
|
|
74
87
|
|
|
88
|
+
// Blocking a dismiss is `preventDefault` on the two gestures Radix fires for
|
|
89
|
+
// one — Escape and a pointer press outside the panel — so the trap and scrim
|
|
90
|
+
// stay in place and only an explicit action can call `onOpenChange`.
|
|
91
|
+
//
|
|
92
|
+
// The handlers are STABLE (created once) and read `dismissable` through a live
|
|
93
|
+
// ref, deliberately: Radix's dismissable layer latches the handler it was given
|
|
94
|
+
// and keeps calling that first closure, so a dialog that mounts dismissable and
|
|
95
|
+
// later turns undismissable (the shown-once reveal) would go on honouring the
|
|
96
|
+
// stale `dismissable` if the value were read from the render closure. The ref,
|
|
97
|
+
// rewritten every render, is what a fresh gesture always sees.
|
|
98
|
+
const dismissableRef = useRef(dismissable);
|
|
99
|
+
dismissableRef.current = dismissable;
|
|
100
|
+
const dismissGuards = useRef({
|
|
101
|
+
onEscapeKeyDown: (event: KeyboardEvent) => {
|
|
102
|
+
if (!dismissableRef.current) event.preventDefault();
|
|
103
|
+
},
|
|
104
|
+
onPointerDownOutside: (event: Event) => {
|
|
105
|
+
if (!dismissableRef.current) event.preventDefault();
|
|
106
|
+
},
|
|
107
|
+
}).current;
|
|
108
|
+
|
|
75
109
|
return (
|
|
76
110
|
<RadixDialog.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
77
111
|
{trigger !== undefined ? <RadixDialog.Trigger asChild>{trigger}</RadixDialog.Trigger> : null}
|
|
@@ -84,6 +118,7 @@ export function Dialog({
|
|
|
84
118
|
<RadixDialog.Content
|
|
85
119
|
className={contentClasses.join(' ')}
|
|
86
120
|
{...focusReturn}
|
|
121
|
+
{...dismissGuards}
|
|
87
122
|
{...(description === undefined ? { 'aria-describedby': undefined } : {})}
|
|
88
123
|
>
|
|
89
124
|
{/* The title always names the dialog; `chromeless` only hides it, moving
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unsaved-changes guard for
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* {@link
|
|
7
|
-
*
|
|
2
|
+
* Unsaved-changes guard for editors that get torn down or navigated away from. One
|
|
3
|
+
* registry closes the gap: editors report dirtiness via {@link useRegisterDirty}; the
|
|
4
|
+
* enclosing {@link DirtyGuardBoundary} arms {@link useNavigationGuard} from that
|
|
5
|
+
* registry, so a route navigation confirms through a shared dialog and a full-page
|
|
6
|
+
* unload raises the native prompt. {@link GuardedTabs} adds a tab-switch confirm on
|
|
7
|
+
* top of the same boundary — `Tabs` unmount inactive panels, so a switch away from a
|
|
8
|
+
* dirty editor is held behind the confirm too. The dialog is one shared
|
|
8
9
|
* {@link ConfirmDialog} driven by a pending-promise resolver, so the guard's
|
|
9
10
|
* `Promise<boolean>` handler and the tab switch both settle against it.
|
|
10
11
|
*/
|
|
@@ -36,7 +37,16 @@ interface DirtyGuardValue {
|
|
|
36
37
|
|
|
37
38
|
const DirtyGuardContext = createContext<DirtyGuardValue | null>(null);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Boundary that guards navigation away from a region holding a dirty editor. Editors
|
|
42
|
+
* inside it arm the guard through {@link useRegisterDirty}; while any is dirty a route
|
|
43
|
+
* navigation (a route-token/plugin navigate, an {@link AppLink}, or a browser
|
|
44
|
+
* back/forward) confirms through the shared discard dialog, and a full-page unload
|
|
45
|
+
* raises the native prompt. Use this directly to guard a dirty editor that is a plain
|
|
46
|
+
* page section (no tabs); {@link GuardedTabs} wraps this same boundary to additionally
|
|
47
|
+
* guard tab switches.
|
|
48
|
+
*/
|
|
49
|
+
export function DirtyGuardBoundary({ children }: { readonly children: ReactNode }): ReactNode {
|
|
40
50
|
const dirtyIds = useRef<Set<string>>(new Set());
|
|
41
51
|
// Mirror the registry's non-empty state into React so the navigation guard's
|
|
42
52
|
// `when` re-arms as editors go dirty/clean.
|
|
@@ -96,9 +106,9 @@ function DirtyGuardProvider({ children }: { readonly children: ReactNode }): Rea
|
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
/**
|
|
99
|
-
* Report an editor's dirty state to the enclosing {@link
|
|
100
|
-
* outside a
|
|
101
|
-
* the hook is always safe to call.
|
|
109
|
+
* Report an editor's dirty state to the enclosing {@link DirtyGuardBoundary} (which
|
|
110
|
+
* {@link GuardedTabs} also provides). A no-op outside a boundary (an editor rendered
|
|
111
|
+
* standalone, e.g. in isolation tests) so the hook is always safe to call.
|
|
102
112
|
*/
|
|
103
113
|
export function useRegisterDirty(dirty: boolean): void {
|
|
104
114
|
const ctx = useContext(DirtyGuardContext);
|
|
@@ -135,7 +145,7 @@ function GuardedTabsInner({
|
|
|
135
145
|
|
|
136
146
|
/**
|
|
137
147
|
* A drop-in replacement for {@link Tabs} that guards a switch away from a dirty
|
|
138
|
-
* editor. Wraps the tabs in a {@link
|
|
148
|
+
* editor. Wraps the tabs in a {@link DirtyGuardBoundary}, so an editor rendered in
|
|
139
149
|
* any panel can arm the guard through {@link useRegisterDirty}.
|
|
140
150
|
*/
|
|
141
151
|
export function GuardedTabs({
|
|
@@ -146,8 +156,8 @@ export function GuardedTabs({
|
|
|
146
156
|
readonly defaultValue?: string;
|
|
147
157
|
}): ReactNode {
|
|
148
158
|
return (
|
|
149
|
-
<
|
|
159
|
+
<DirtyGuardBoundary>
|
|
150
160
|
<GuardedTabsInner items={items} defaultValue={defaultValue} />
|
|
151
|
-
</
|
|
161
|
+
</DirtyGuardBoundary>
|
|
152
162
|
);
|
|
153
163
|
}
|
|
@@ -409,7 +409,11 @@ export function ExplorerView<T>({
|
|
|
409
409
|
const footer =
|
|
410
410
|
entries.length > 0 ? (
|
|
411
411
|
<nav className="tai-explorer-pagination" aria-label={`${label} pagination`}>
|
|
412
|
-
|
|
412
|
+
{/* The pager counts ENTRIES — subfolders plus filtered items in the
|
|
413
|
+
current directory — which is a different unit from the header's total
|
|
414
|
+
item count across every folder. Naming the unit keeps the two honest:
|
|
415
|
+
"1–4 of 4 entries" here versus "7 templates" above. */}
|
|
416
|
+
<span className="tai-muted">{`${String(pageStart + 1)}–${String(Math.min(pageStart + pageSize, entries.length))} of ${String(entries.length)} entries`}</span>
|
|
413
417
|
<div className="tai-row">
|
|
414
418
|
<Select
|
|
415
419
|
aria-label="Items per page"
|
|
@@ -542,7 +546,10 @@ export function ExplorerView<T>({
|
|
|
542
546
|
<div className="tai-explorer-controls">
|
|
543
547
|
<div className="tai-row">
|
|
544
548
|
{search !== undefined ? (
|
|
545
|
-
|
|
549
|
+
// The magnifier is a leading icon INSIDE the input, not a detached
|
|
550
|
+
// glyph beside it: the wrapper positions the icon over the input's
|
|
551
|
+
// reserved leading padding.
|
|
552
|
+
<span className="tai-search-field">
|
|
546
553
|
<SearchIcon />
|
|
547
554
|
<TextInput
|
|
548
555
|
value={search.value}
|
package/src/components/field.tsx
CHANGED
|
@@ -155,6 +155,16 @@ export function Field({
|
|
|
155
155
|
{label}
|
|
156
156
|
</label>
|
|
157
157
|
)}
|
|
158
|
+
{/* The description sits BETWEEN the label and the control, so it stays
|
|
159
|
+
adjacent to the label it glosses. After the control it drifts: a
|
|
160
|
+
group Field can wrap an arbitrarily tall surface (a whole schema
|
|
161
|
+
form), and a description below that surface reads as a footnote to
|
|
162
|
+
nothing. */}
|
|
163
|
+
{description !== undefined ? (
|
|
164
|
+
<p id={descriptionId} className="tai-field-hint" style={{ margin: 0 }}>
|
|
165
|
+
{description}
|
|
166
|
+
</p>
|
|
167
|
+
) : null}
|
|
158
168
|
{group ? (
|
|
159
169
|
<div
|
|
160
170
|
role="group"
|
|
@@ -167,11 +177,6 @@ export function Field({
|
|
|
167
177
|
) : (
|
|
168
178
|
children
|
|
169
179
|
)}
|
|
170
|
-
{description !== undefined ? (
|
|
171
|
-
<p id={descriptionId} className="tai-field-hint" style={{ margin: 0 }}>
|
|
172
|
-
{description}
|
|
173
|
-
</p>
|
|
174
|
-
) : null}
|
|
175
180
|
{/* The icon carries the invalid state alongside the color, never the color
|
|
176
181
|
alone — and it is the ERROR mark: a rejected value is a definite
|
|
177
182
|
negative, not the warning the triangle states. */}
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
* used by every config-mutation surface) reports a saved change that did not fully
|
|
10
10
|
* propagate and points the operator at the System page to reload; `'reload'` (the
|
|
11
11
|
* System page's own fleet-reload action) drops the "Change saved" framing — nothing
|
|
12
|
-
* was saved, a reload was dispatched — and the self-referential System-page pointer
|
|
12
|
+
* was saved, a reload was dispatched — and the self-referential System-page pointer;
|
|
13
|
+
* `'deregister'` and `'remove'` (detach fan-outs) likewise drop the "Change saved"
|
|
14
|
+
* framing and points the operator back at re-running the deregister, because a
|
|
15
|
+
* reload would re-attach the very server being detached — the wrong remediation.
|
|
13
16
|
*
|
|
14
17
|
* A `converged` summary (every worker applied, or a lone-worker local-only change)
|
|
15
18
|
* renders NOTHING — the calling surface shows its own success. A `degraded` summary
|
|
@@ -73,26 +76,44 @@ const OUTCOME_STATUS: Record<FleetFailureOutcome, StatusTone> = {
|
|
|
73
76
|
*/
|
|
74
77
|
export interface FleetReportProps {
|
|
75
78
|
readonly summary: FleetReportSummary | null;
|
|
76
|
-
/**
|
|
77
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Whose framing the copy takes: a saved config change (`'save'`), a dispatched
|
|
81
|
+
* reload (`'reload'`), a connector detach fan-out (`'deregister'`), or a live-registry
|
|
82
|
+
* tool removal (`'remove'`). All but `'save'` drop the "Change saved" framing;
|
|
83
|
+
* `'deregister'` and `'remove'` also point remediation back at re-running that
|
|
84
|
+
* same detach, since a reload would re-attach the detached server or tool.
|
|
85
|
+
*/
|
|
86
|
+
readonly action?: 'save' | 'reload' | 'deregister' | 'remove';
|
|
78
87
|
}
|
|
79
88
|
|
|
80
89
|
export function FleetReport({ summary, action = 'save' }: FleetReportProps): ReactNode {
|
|
81
90
|
if (summary === null || summary.status === 'converged') return null;
|
|
82
91
|
|
|
92
|
+
// Only a saved config change carries the "Change saved" framing; a reload or a
|
|
93
|
+
// deregister saved nothing.
|
|
94
|
+
const savedFraming = action === 'save';
|
|
95
|
+
|
|
83
96
|
if (summary.status === 'unreachable') {
|
|
97
|
+
// Where to send the operator once the bus is back: re-run the same op, except a
|
|
98
|
+
// saved change reloads from the System page.
|
|
99
|
+
const unreachableRemediation =
|
|
100
|
+
action === 'deregister'
|
|
101
|
+
? 'Re-run the deregister once the bus is back.'
|
|
102
|
+
: action === 'remove'
|
|
103
|
+
? 'Re-run the remove once the bus is back.'
|
|
104
|
+
: action === 'reload'
|
|
105
|
+
? 'Re-run the reload once the bus is back.'
|
|
106
|
+
: 'Re-run the reload from the System page once the bus is back.';
|
|
84
107
|
return (
|
|
85
108
|
<div role="alert" className="tai-error-state tai-stack tai-stack-2">
|
|
86
109
|
<strong className="tai-status tai-status-err">
|
|
87
110
|
<XCircleIcon />
|
|
88
|
-
{
|
|
89
|
-
? '
|
|
90
|
-
: '
|
|
111
|
+
{savedFraming
|
|
112
|
+
? 'Change saved, but the worker fleet was not reached'
|
|
113
|
+
: 'The worker fleet was not reached'}
|
|
91
114
|
</strong>
|
|
92
115
|
<p>
|
|
93
|
-
{
|
|
94
|
-
? 'The worker bus was unreachable, so other workers may still be running the old config. Re-run the reload once the bus is back.'
|
|
95
|
-
: 'The worker bus was unreachable, so other workers may still be running the old config. Re-run the reload from the System page once the bus is back.'}
|
|
116
|
+
{`The worker bus was unreachable, so other workers may still be running the old config. ${unreachableRemediation}`}
|
|
96
117
|
</p>
|
|
97
118
|
{/* The bus error is a message, not a listing: it wraps rather than scrolls. */}
|
|
98
119
|
{summary.error !== null ? (
|
|
@@ -111,17 +132,27 @@ export function FleetReport({ summary, action = 'save' }: FleetReportProps): Rea
|
|
|
111
132
|
// worker is the commonest degraded fleet there is.
|
|
112
133
|
const count = summary.failures.length;
|
|
113
134
|
const workers = `${String(count)} worker${count === 1 ? '' : 's'}`;
|
|
135
|
+
// How the operator converges the stranded workers: re-run the same op, except a
|
|
136
|
+
// saved change reloads the fleet from the System page.
|
|
137
|
+
const remediation =
|
|
138
|
+
action === 'deregister'
|
|
139
|
+
? 're-run the deregister'
|
|
140
|
+
: action === 'remove'
|
|
141
|
+
? 're-run the remove'
|
|
142
|
+
: action === 'reload'
|
|
143
|
+
? 're-run the reload'
|
|
144
|
+
: 'reload the fleet from the System page';
|
|
114
145
|
return (
|
|
115
146
|
<div role="alert" className="tai-warn-state tai-stack tai-stack-2">
|
|
116
147
|
<strong className="tai-status tai-status-warn">
|
|
117
148
|
<AlertTriangleIcon />
|
|
118
|
-
{
|
|
119
|
-
?
|
|
120
|
-
:
|
|
149
|
+
{savedFraming
|
|
150
|
+
? `Change saved, but ${workers} did not converge`
|
|
151
|
+
: `${workers} did not converge`}
|
|
121
152
|
</strong>
|
|
122
153
|
<p>
|
|
123
154
|
{`${count === 1 ? 'This worker' : 'These workers'} may still be running the old config — ` +
|
|
124
|
-
|
|
155
|
+
remediation +
|
|
125
156
|
` to converge ${count === 1 ? 'it' : 'them'}.`}
|
|
126
157
|
</p>
|
|
127
158
|
<ul className="tai-stack tai-stack-2">
|
package/src/components/icons.tsx
CHANGED
|
@@ -504,6 +504,24 @@ export const GridIcon: IconComponent = (props) => (
|
|
|
504
504
|
</Icon>
|
|
505
505
|
);
|
|
506
506
|
|
|
507
|
+
/** Served endpoints: a broadcast node radiating to the tools it serves. */
|
|
508
|
+
export const ServedEndpointsIcon: IconComponent = (props) => (
|
|
509
|
+
<Icon {...props}>
|
|
510
|
+
<circle cx="12" cy="12" r="2.5" />
|
|
511
|
+
<path d="M7.8 7.8a6 6 0 0 0 0 8.4" />
|
|
512
|
+
<path d="M16.2 7.8a6 6 0 0 1 0 8.4" />
|
|
513
|
+
<path d="M5 5a9 9 0 0 0 0 14" />
|
|
514
|
+
<path d="M19 5a9 9 0 0 1 0 14" />
|
|
515
|
+
</Icon>
|
|
516
|
+
);
|
|
517
|
+
|
|
518
|
+
/** Plugin: a puzzle piece — the host's provenance mark for plugin-contributed nav. */
|
|
519
|
+
export const PluginIcon: IconComponent = (props) => (
|
|
520
|
+
<Icon {...props}>
|
|
521
|
+
<path d="M10 4a2 2 0 0 1 4 0c0 .8.7 1.4 1.5 1.4H18a1 1 0 0 1 1 1v2.6c0 .8.6 1.5 1.4 1.5a2 2 0 0 1 0 4c-.8 0-1.4.7-1.4 1.5V19a1 1 0 0 1-1 1h-2.6c-.8 0-1.4-.6-1.4-1.4a2 2 0 0 0-4 0c0 .8-.6 1.4-1.4 1.4H6a1 1 0 0 1-1-1v-2.5C5 15.7 4.4 15 3.6 15a2 2 0 0 1 0-4c.8 0 1.4-.7 1.4-1.5V6a1 1 0 0 1 1-1h2.5C9.3 5 10 4.4 10 3.6z" />
|
|
522
|
+
</Icon>
|
|
523
|
+
);
|
|
524
|
+
|
|
507
525
|
// -- Route mapping -----------------------------------------------------------
|
|
508
526
|
|
|
509
527
|
/**
|
|
@@ -519,6 +537,7 @@ export const NAV_ICONS: Readonly<Record<Exclude<RouteToken, 'login'>, IconCompon
|
|
|
519
537
|
extensions: ExtensionsIcon,
|
|
520
538
|
templates: TemplatesIcon,
|
|
521
539
|
connectors: ConnectorsIcon,
|
|
540
|
+
servedEndpoints: ServedEndpointsIcon,
|
|
522
541
|
hooks: HooksIcon,
|
|
523
542
|
storage: StorageIcon,
|
|
524
543
|
scheduling: SchedulingIcon,
|
|
@@ -197,16 +197,26 @@
|
|
|
197
197
|
--tai-color-syntax-bool: var(--tai-color-warn-text);
|
|
198
198
|
|
|
199
199
|
/* -- Legacy token slots -------------------------------------------------
|
|
200
|
-
The names a plugin already styles against
|
|
201
|
-
the contract above that keep them legible.
|
|
202
|
-
|
|
200
|
+
The names a plugin already styles against. `primary`/`success`/`warning`
|
|
201
|
+
re-point at the members of the contract above that keep them legible.
|
|
202
|
+
|
|
203
|
+
`danger` is the one destructive EMPHASIS, and it runs deeper than the
|
|
204
|
+
accent on purpose: a destructive fill sharing the crimson brand ramp reads
|
|
205
|
+
as the primary action, so this is its own deep red — clearly darker and
|
|
206
|
+
less pink than the accent in BOTH themes, unlike the light-fill dark-ink
|
|
207
|
+
convention the accent follows. Its label is white in both themes and its
|
|
208
|
+
hover a shade deeper still, so a destructive button is instantly told apart
|
|
209
|
+
from a primary one. `danger-text` is that white label on the danger FILL;
|
|
210
|
+
`danger-surface` is the faint error-tint ground the label sits over
|
|
211
|
+
elsewhere. */
|
|
203
212
|
--tai-color-primary: var(--tai-color-accent);
|
|
204
213
|
--tai-color-primary-text: var(--tai-color-on-accent);
|
|
205
|
-
--tai-color-danger:
|
|
214
|
+
--tai-color-danger: #991b1b;
|
|
215
|
+
--tai-dark-color-danger: #dc2626;
|
|
206
216
|
--tai-color-danger-text: #ffffff;
|
|
207
|
-
--tai-dark-color-danger-text: #
|
|
217
|
+
--tai-dark-color-danger-text: #ffffff;
|
|
208
218
|
--tai-color-danger-hover: #7f1d1d;
|
|
209
|
-
--tai-dark-color-danger-hover: #
|
|
219
|
+
--tai-dark-color-danger-hover: #b91c1c;
|
|
210
220
|
--tai-color-danger-surface: var(--tai-color-err-tint);
|
|
211
221
|
--tai-color-success: var(--tai-color-ok-text);
|
|
212
222
|
--tai-color-warning: var(--tai-color-warn-text);
|
|
@@ -325,6 +335,7 @@
|
|
|
325
335
|
--tai-color-scrim: var(--tai-dark-color-scrim);
|
|
326
336
|
--tai-color-prose-link: var(--tai-dark-color-prose-link);
|
|
327
337
|
--tai-color-prose-link-hover: var(--tai-dark-color-prose-link-hover);
|
|
338
|
+
--tai-color-danger: var(--tai-dark-color-danger);
|
|
328
339
|
--tai-color-danger-text: var(--tai-dark-color-danger-text);
|
|
329
340
|
--tai-color-danger-hover: var(--tai-dark-color-danger-hover);
|
|
330
341
|
--tai-shadow-lift-color: var(--tai-dark-shadow-lift-color);
|
|
@@ -371,6 +382,7 @@
|
|
|
371
382
|
--tai-color-scrim: var(--tai-dark-color-scrim);
|
|
372
383
|
--tai-color-prose-link: var(--tai-dark-color-prose-link);
|
|
373
384
|
--tai-color-prose-link-hover: var(--tai-dark-color-prose-link-hover);
|
|
385
|
+
--tai-color-danger: var(--tai-dark-color-danger);
|
|
374
386
|
--tai-color-danger-text: var(--tai-dark-color-danger-text);
|
|
375
387
|
--tai-color-danger-hover: var(--tai-dark-color-danger-hover);
|
|
376
388
|
--tai-shadow-lift-color: var(--tai-dark-shadow-lift-color);
|
package/src/host.ts
CHANGED
|
@@ -12,4 +12,8 @@ export {
|
|
|
12
12
|
subscribePluginHost,
|
|
13
13
|
usePluginContributions,
|
|
14
14
|
} from './plugin/host-state';
|
|
15
|
-
export type {
|
|
15
|
+
export type {
|
|
16
|
+
PluginLoaderState,
|
|
17
|
+
LoadedPlugin,
|
|
18
|
+
PluginContributionsSnapshot,
|
|
19
|
+
} from './plugin/host-state';
|
package/src/index.ts
CHANGED
|
@@ -191,7 +191,7 @@ export { Dialog } from './components/dialog';
|
|
|
191
191
|
export type { DialogProps } from './components/dialog';
|
|
192
192
|
export { ConfirmDialog } from './components/confirm-dialog';
|
|
193
193
|
export type { ConfirmDialogProps } from './components/confirm-dialog';
|
|
194
|
-
export { GuardedTabs, useRegisterDirty } from './components/dirty-guard';
|
|
194
|
+
export { GuardedTabs, DirtyGuardBoundary, useRegisterDirty } from './components/dirty-guard';
|
|
195
195
|
export { FormDialog } from './components/form-dialog';
|
|
196
196
|
export type { FormDialogProps } from './components/form-dialog';
|
|
197
197
|
export { Drawer } from './components/drawer';
|
|
@@ -318,6 +318,8 @@ export {
|
|
|
318
318
|
ExtensionsIcon,
|
|
319
319
|
TemplatesIcon,
|
|
320
320
|
ConnectorsIcon,
|
|
321
|
+
ServedEndpointsIcon,
|
|
322
|
+
PluginIcon,
|
|
321
323
|
HooksIcon,
|
|
322
324
|
StorageIcon,
|
|
323
325
|
SchedulingIcon,
|