@pipelex/mthds-ui 0.17.0 → 0.18.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 +38 -4
- package/dist/chunk-2N65P32H.js +17 -0
- package/dist/chunk-2N65P32H.js.map +1 -0
- package/dist/{chunk-CQTAXK76.js → chunk-BFQHQQ3E.js} +6 -189
- package/dist/chunk-BFQHQQ3E.js.map +1 -0
- package/dist/chunk-HASMGCVK.js +195 -0
- package/dist/chunk-HASMGCVK.js.map +1 -0
- package/dist/{chunk-3XFIKUSZ.js → chunk-RJVEDP7M.js} +2 -16
- package/dist/chunk-RJVEDP7M.js.map +1 -0
- package/dist/form/react/RunPanel.css +131 -0
- package/dist/form/react/index.d.ts +188 -0
- package/dist/form/react/index.js +265 -0
- package/dist/form/react/index.js.map +1 -0
- package/dist/graph/index.d.ts +2 -2
- package/dist/graph/index.js +13 -9
- package/dist/graph/react/index.d.ts +2 -2
- package/dist/graph/react/index.js +7 -4
- package/dist/graph/react/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +13 -9
- package/dist/shiki/index.js +8 -2
- package/dist/shiki/index.js.map +1 -1
- package/dist/standalone/graph-standalone.html +8 -8
- package/dist/standalone/graph-viewer.js +8 -8
- package/dist/static-graph/index.d.ts +4 -2
- package/dist/static-graph/index.js +64 -21
- package/dist/static-graph/index.js.map +1 -1
- package/dist/{types-DUSThVb4.d.ts → types-Cv5Elty8.d.ts} +10 -1
- package/package.json +12 -1
- package/dist/chunk-3XFIKUSZ.js.map +0 -1
- package/dist/chunk-CQTAXK76.js.map +0 -1
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { ValidationMessageKey, PipeIOContract, RunField, RunInputError } from '@pipelex/mthds-form';
|
|
3
|
+
import { FieldEnv } from '@pipelex/mthds-form/react';
|
|
4
|
+
import { G as GraphTheme } from '../../types-Cv5Elty8.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The panel's submit path, kept React-free.
|
|
8
|
+
*
|
|
9
|
+
* Everything between "the user pressed Run" and "the payload goes on the wire"
|
|
10
|
+
* is the kernel's (`@pipelex/mthds-form`, `core/gate.ts`); this module bridges
|
|
11
|
+
* the panel's values into the shape the gate takes, calls it, and turns the
|
|
12
|
+
* verdict into one line a panel can show. Splitting it out of `RunPanel.tsx` is
|
|
13
|
+
* what lets the node vitest project test that seam without mounting React — the
|
|
14
|
+
* gate's own behavior is covered by the kernel's tests and is deliberately not
|
|
15
|
+
* re-tested here.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** The outcome of one submit attempt. */
|
|
19
|
+
type RunGateOutcome = {
|
|
20
|
+
ok: true;
|
|
21
|
+
apiInputs: Record<string, unknown>;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
missingInputs: string[];
|
|
25
|
+
summary: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Every message key the error summary can render — the kernel's, plus the two
|
|
29
|
+
* this panel owns.
|
|
30
|
+
*
|
|
31
|
+
* The kernel's keys describe one ajv error each and are rendered by
|
|
32
|
+
* `describeValidationError`. The `runPanel.` ones are lines the panel writes
|
|
33
|
+
* itself, around those errors, and they carry the prefix precisely so the
|
|
34
|
+
* ownership is legible at the call site and in a host's translation file.
|
|
35
|
+
*
|
|
36
|
+
* Nesting the kernel's union inside ours is deliberate: a key the kernel ADDS
|
|
37
|
+
* lands here automatically and then fails `VALIDATION_STRINGS`'s exhaustiveness
|
|
38
|
+
* until it is given a string, which is how a kernel bump is stopped from
|
|
39
|
+
* silently rendering `undefined` at a user.
|
|
40
|
+
*/
|
|
41
|
+
type RunPanelMessageKey = ValidationMessageKey | "runPanel.missingInputs" | "runPanel.fillRequired";
|
|
42
|
+
/**
|
|
43
|
+
* The panel's i18n seam — the kernel's `Translate` widened to the panel's own
|
|
44
|
+
* keys. It accepts a superset of what the kernel asks for, so it is still a
|
|
45
|
+
* valid `Translate` wherever the kernel wants one.
|
|
46
|
+
*/
|
|
47
|
+
type RunPanelTranslate = (key: RunPanelMessageKey, values?: Record<string, string>) => string;
|
|
48
|
+
/**
|
|
49
|
+
* The default translator: the English strings above with `{placeholder}`
|
|
50
|
+
* interpolation. A host that localizes passes its own translator instead.
|
|
51
|
+
*/
|
|
52
|
+
declare const defaultValidationTranslate: RunPanelTranslate;
|
|
53
|
+
/**
|
|
54
|
+
* One human line for an invalid verdict.
|
|
55
|
+
*
|
|
56
|
+
* `missingInputs` is the good case: it names the VARIABLES at fault, which is
|
|
57
|
+
* what the user clicks on. The scan can legitimately come up empty on an
|
|
58
|
+
* invalid form (a wrong value shape, a nested mismatch), and that is exactly
|
|
59
|
+
* when falling back to the raw ajv errors keeps the failure diagnosable rather
|
|
60
|
+
* than silent — see `validateRunInputs` in the kernel.
|
|
61
|
+
*
|
|
62
|
+
* All three routes go through `t`. Which one runs is decided by the kernel's
|
|
63
|
+
* verdict, so a host that localizes cannot know in advance which line it will
|
|
64
|
+
* be shown — leaving any of them in hardcoded English would surface as the
|
|
65
|
+
* summary silently reverting to English on some inputs and not others.
|
|
66
|
+
*/
|
|
67
|
+
declare function summarizeVerdict(missingInputs: string[], errors: RunInputError[], preparedData: unknown, t?: RunPanelTranslate): string;
|
|
68
|
+
/**
|
|
69
|
+
* Run the kernel's gate over the panel's current values.
|
|
70
|
+
*
|
|
71
|
+
* `gateRunInputs` IS the gate — the whole chain in one call — so all this
|
|
72
|
+
* module does either side of it is bridge and phrase: the panel holds values
|
|
73
|
+
* keyed by variable, the gate takes the schema-shaped data those become, and a
|
|
74
|
+
* rejected verdict has to come back as one line rather than as a discriminated
|
|
75
|
+
* union the panel would have to read.
|
|
76
|
+
*
|
|
77
|
+
* Assembling those steps here instead was the earlier shape and it was wrong in
|
|
78
|
+
* a way nothing local could see. The chain's schema validation is not the whole
|
|
79
|
+
* rule: ajv's `required` only asserts a key is PRESENT, and a content model
|
|
80
|
+
* carries no `minLength`, so a required input that arrived empty — `{text: ""}`
|
|
81
|
+
* from an untouched text field, `{url: ""}` from an untouched file field —
|
|
82
|
+
* satisfied the schema and the run went out. The Run button refuses exactly
|
|
83
|
+
* those, through `computeReadiness`. The gate now re-applies that same
|
|
84
|
+
* emptiness rule with `computeReadiness`'s own functions over the same derived
|
|
85
|
+
* fields, which makes the button's verdict and this one a single invariant
|
|
86
|
+
* rather than two things that resemble each other.
|
|
87
|
+
*/
|
|
88
|
+
declare function runSubmitGate(contract: PipeIOContract, fields: RunField[], values: Record<string, unknown>, t?: RunPanelTranslate): RunGateOutcome;
|
|
89
|
+
|
|
90
|
+
/** What a host's upload returns: the stored URL, and optionally a display name. */
|
|
91
|
+
interface UploadedFile {
|
|
92
|
+
url: string;
|
|
93
|
+
filename?: string;
|
|
94
|
+
}
|
|
95
|
+
interface RunPanelProps {
|
|
96
|
+
/**
|
|
97
|
+
* The pipe's IO contract — the kernel's type, supplied by the host.
|
|
98
|
+
*
|
|
99
|
+
* Hosts that can actually run a method already hold `pipe_io_contracts` from
|
|
100
|
+
* the same `/validate` call that produced their graph spec; look an entry up
|
|
101
|
+
* with the kernel's `getPipeIOContract(contracts, domain, pipeCode)`. The
|
|
102
|
+
* panel deliberately does NOT accept a `GraphSpec` and derive a contract from
|
|
103
|
+
* it — deriving field meaning locally is precisely what this component exists
|
|
104
|
+
* not to do (design Decision A).
|
|
105
|
+
*/
|
|
106
|
+
contract: PipeIOContract;
|
|
107
|
+
/** Fully controlled field values, host-owned — the kernel's philosophy. */
|
|
108
|
+
values: Record<string, unknown>;
|
|
109
|
+
onValuesChange: (values: Record<string, unknown>) => void;
|
|
110
|
+
/** Fires only after the kernel's run gate passes, with the wire-ready payload. */
|
|
111
|
+
onRun: (apiInputs: Record<string, unknown>) => void;
|
|
112
|
+
/**
|
|
113
|
+
* A run is in flight: the fields and the Run button go inert.
|
|
114
|
+
*
|
|
115
|
+
* **Set it synchronously inside `onRun`, before any `await`.** Across anything
|
|
116
|
+
* longer than one task it is the only thing standing between a second submit
|
|
117
|
+
* and a duplicate execution, and the panel cannot supply that itself: it never
|
|
118
|
+
* learns that a run finished, so a lock held for a run's lifetime would have
|
|
119
|
+
* no release. A host that flips this only once the server answers leaves the
|
|
120
|
+
* button live for the whole round trip, and a second click there starts a
|
|
121
|
+
* second run that nothing undoes.
|
|
122
|
+
*
|
|
123
|
+
* Synchronous re-entry within a single task is the panel's own problem and is
|
|
124
|
+
* handled — see `justSubmittedRef` beside `handleSubmit`. That guard does not
|
|
125
|
+
* reduce what this prop is for; it closes a window this prop cannot reach,
|
|
126
|
+
* because React has not re-rendered inside it.
|
|
127
|
+
*/
|
|
128
|
+
running?: boolean;
|
|
129
|
+
/**
|
|
130
|
+
* Ambient field state, passed to every control.
|
|
131
|
+
*
|
|
132
|
+
* For `disabled`, `onDropFile` and `resolveUrl` the host's value wins PER
|
|
133
|
+
* KEY: the panel fills in only what the host left undefined — `disabled`
|
|
134
|
+
* from `running`, `onDropFile` from its own upload tracking when
|
|
135
|
+
* `uploadFile` is supplied.
|
|
136
|
+
*
|
|
137
|
+
* `uploadingIds` is the exception, and deliberately so: it is the UNION of
|
|
138
|
+
* your set and the panel's, never a replacement. Because the keys default
|
|
139
|
+
* independently, a host can supply `uploadFile` — leaving the panel to own
|
|
140
|
+
* the drop and mark the field in its own set — while also passing a tracker
|
|
141
|
+
* of its own; letting yours win outright there would hide an upload the
|
|
142
|
+
* panel itself started and leave Run live through it. A host that owns the
|
|
143
|
+
* whole loop supplies `onDropFile` too, so the panel's set is empty and the
|
|
144
|
+
* union is exactly your set.
|
|
145
|
+
*/
|
|
146
|
+
env?: FieldEnv;
|
|
147
|
+
/**
|
|
148
|
+
* Stores a dropped file and returns its URL. The host performs the upload —
|
|
149
|
+
* this library never touches an API client — while the panel owns the
|
|
150
|
+
* bookkeeping around it: marking the field busy and writing the result back
|
|
151
|
+
* at the field's dotted path.
|
|
152
|
+
*/
|
|
153
|
+
uploadFile?: (file: File, fieldId: string) => Promise<UploadedFile>;
|
|
154
|
+
/** Panel header. The host names the pipe; there is no default heading. */
|
|
155
|
+
title?: string;
|
|
156
|
+
/**
|
|
157
|
+
* The resolved theme, which drives the kernel's `.dark` class on the panel
|
|
158
|
+
* container alongside this library's own palette variables. Defaults to
|
|
159
|
+
* `light`. Pair it with `GraphViewer`'s `onThemeChange`, whose second
|
|
160
|
+
* argument is exactly this resolved value.
|
|
161
|
+
*/
|
|
162
|
+
theme?: GraphTheme;
|
|
163
|
+
/**
|
|
164
|
+
* Renders the whole error summary in the host's language. English by default.
|
|
165
|
+
*
|
|
166
|
+
* It covers the panel's own lines (`runPanel.*`) as well as the kernel's
|
|
167
|
+
* per-error ones, because which of them a rejected run produces is the
|
|
168
|
+
* kernel's decision and not something a host can predict.
|
|
169
|
+
*/
|
|
170
|
+
translate?: RunPanelTranslate;
|
|
171
|
+
/** Appended to the panel container's class list. */
|
|
172
|
+
className?: string;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* A pipe's input form: fields, readiness, and the run gate, composed.
|
|
176
|
+
*
|
|
177
|
+
* Every question of meaning is the form kernel's — `fieldsForContract` decides
|
|
178
|
+
* what each input IS, `computeReadiness` decides whether the form MAY run, and
|
|
179
|
+
* `runSubmitGate` (over the kernel's four steps) decides what goes on the wire.
|
|
180
|
+
* What lives here is layout: which fields are visible, where the toggle and the
|
|
181
|
+
* button sit, and how a rejected verdict is shown. The panel never reads
|
|
182
|
+
* `json_schema` to make a rendering decision and never sniffs a value's shape
|
|
183
|
+
* (design Decision C) — which is what keeps a change to the kernel's derivation
|
|
184
|
+
* invisible to this repo.
|
|
185
|
+
*/
|
|
186
|
+
declare function RunPanel({ contract, values, onValuesChange, onRun, running, env, uploadFile, title, theme, translate, className, }: RunPanelProps): react_jsx_runtime.JSX.Element;
|
|
187
|
+
|
|
188
|
+
export { type RunGateOutcome, RunPanel, type RunPanelMessageKey, type RunPanelProps, type RunPanelTranslate, type UploadedFile, defaultValidationTranslate, runSubmitGate, summarizeVerdict };
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
getPaletteForTheme
|
|
4
|
+
} from "../../chunk-HASMGCVK.js";
|
|
5
|
+
import {
|
|
6
|
+
GRAPH_THEME
|
|
7
|
+
} from "../../chunk-RJVEDP7M.js";
|
|
8
|
+
import {
|
|
9
|
+
__spreadProps,
|
|
10
|
+
__spreadValues
|
|
11
|
+
} from "../../chunk-2NMEKWO5.js";
|
|
12
|
+
|
|
13
|
+
// src/form/react/RunPanel.tsx
|
|
14
|
+
import * as React from "react";
|
|
15
|
+
import {
|
|
16
|
+
computeReadiness,
|
|
17
|
+
fieldsForContract,
|
|
18
|
+
isFilled,
|
|
19
|
+
setValueAtPath
|
|
20
|
+
} from "@pipelex/mthds-form";
|
|
21
|
+
import { FieldRenderer, OptionalToggle } from "@pipelex/mthds-form/react";
|
|
22
|
+
|
|
23
|
+
// src/form/runGate.ts
|
|
24
|
+
import {
|
|
25
|
+
describeValidationError,
|
|
26
|
+
gateRunInputs,
|
|
27
|
+
rjsfDataFromRunValues
|
|
28
|
+
} from "@pipelex/mthds-form";
|
|
29
|
+
var MAX_QUOTED_ERRORS = 3;
|
|
30
|
+
var VALIDATION_STRINGS = {
|
|
31
|
+
"inputPanel.aDateField": "A date field",
|
|
32
|
+
"inputPanel.pickValidDate": "{label}: \u201C{value}\u201D is not a valid date",
|
|
33
|
+
"inputPanel.pickValidDateEmpty": "{label}: pick a valid date",
|
|
34
|
+
"inputPanel.dateCarriesTime": "{label}: \u201C{value}\u201D carries a time, but a date field takes the day alone \u2014 use \u201C{day}\u201D and put the time in the \u201Ctime\u201D field",
|
|
35
|
+
"inputPanel.invalidValue": "Invalid value",
|
|
36
|
+
"inputPanel.invalidValueWithData": "{stack} (value: \u201C{value}\u201D)",
|
|
37
|
+
"runPanel.missingInputs": "Missing required fields in: {inputs}",
|
|
38
|
+
"runPanel.fillRequired": "Please fill in all required fields before running."
|
|
39
|
+
};
|
|
40
|
+
var defaultValidationTranslate = (key, values) => {
|
|
41
|
+
const template = VALIDATION_STRINGS[key];
|
|
42
|
+
if (!values) return template;
|
|
43
|
+
return template.replace(/\{(\w+)\}/g, (whole, name) => {
|
|
44
|
+
var _a;
|
|
45
|
+
return (_a = values[name]) != null ? _a : whole;
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
function summarizeVerdict(missingInputs, errors, preparedData, t = defaultValidationTranslate) {
|
|
49
|
+
if (missingInputs.length > 0) {
|
|
50
|
+
return t("runPanel.missingInputs", { inputs: missingInputs.join(", ") });
|
|
51
|
+
}
|
|
52
|
+
const described = errors.slice(0, MAX_QUOTED_ERRORS).map((error) => describeValidationError(error, t, preparedData)).join("; ");
|
|
53
|
+
return described || t("runPanel.fillRequired");
|
|
54
|
+
}
|
|
55
|
+
function runSubmitGate(contract, fields, values, t = defaultValidationTranslate) {
|
|
56
|
+
const verdict = gateRunInputs(contract, rjsfDataFromRunValues(values, fields));
|
|
57
|
+
if (!verdict.ok) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
missingInputs: verdict.missingInputs,
|
|
61
|
+
summary: summarizeVerdict(verdict.missingInputs, verdict.errors, verdict.preparedData, t)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return { ok: true, apiInputs: verdict.inputs };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/form/react/RunPanel.tsx
|
|
68
|
+
import "./RunPanel.css";
|
|
69
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
70
|
+
var EMPTY_IDS = /* @__PURE__ */ new Set();
|
|
71
|
+
var NOTHING_REVEALED = /* @__PURE__ */ new Set();
|
|
72
|
+
function RunPanel({
|
|
73
|
+
contract,
|
|
74
|
+
values,
|
|
75
|
+
onValuesChange,
|
|
76
|
+
onRun,
|
|
77
|
+
running = false,
|
|
78
|
+
env,
|
|
79
|
+
uploadFile,
|
|
80
|
+
title,
|
|
81
|
+
theme = GRAPH_THEME.LIGHT,
|
|
82
|
+
translate,
|
|
83
|
+
className
|
|
84
|
+
}) {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
const [showOptional, setShowOptional] = React.useState(false);
|
|
87
|
+
const [uploadingIds, setUploadingIds] = React.useState(EMPTY_IDS);
|
|
88
|
+
const [submitError, setSubmitError] = React.useState(null);
|
|
89
|
+
const readinessHintId = React.useId();
|
|
90
|
+
const fields = React.useMemo(() => fieldsForContract(contract), [contract]);
|
|
91
|
+
const readiness = React.useMemo(() => computeReadiness(fields, values), [fields, values]);
|
|
92
|
+
const commitValues = React.useCallback(
|
|
93
|
+
(next) => {
|
|
94
|
+
setSubmitError(null);
|
|
95
|
+
onValuesChange(next);
|
|
96
|
+
},
|
|
97
|
+
[onValuesChange]
|
|
98
|
+
);
|
|
99
|
+
const valuesRef = React.useRef(values);
|
|
100
|
+
React.useLayoutEffect(() => {
|
|
101
|
+
valuesRef.current = values;
|
|
102
|
+
}, [values]);
|
|
103
|
+
const generationRef = React.useRef(0);
|
|
104
|
+
React.useLayoutEffect(() => {
|
|
105
|
+
setUploadingIds(EMPTY_IDS);
|
|
106
|
+
setSubmitError(null);
|
|
107
|
+
return () => {
|
|
108
|
+
generationRef.current += 1;
|
|
109
|
+
};
|
|
110
|
+
}, [contract]);
|
|
111
|
+
const handleDropFile = React.useCallback(
|
|
112
|
+
(id, file) => {
|
|
113
|
+
if (!uploadFile) return;
|
|
114
|
+
const startedAt = generationRef.current;
|
|
115
|
+
setUploadingIds((previous) => new Set(previous).add(id));
|
|
116
|
+
void (async () => await uploadFile(file, id))().then(
|
|
117
|
+
(uploaded) => {
|
|
118
|
+
var _a2;
|
|
119
|
+
if (generationRef.current !== startedAt) return;
|
|
120
|
+
const next = setValueAtPath(valuesRef.current, id.split("."), {
|
|
121
|
+
url: uploaded.url,
|
|
122
|
+
filename: (_a2 = uploaded.filename) != null ? _a2 : file.name
|
|
123
|
+
});
|
|
124
|
+
valuesRef.current = next;
|
|
125
|
+
commitValues(next);
|
|
126
|
+
},
|
|
127
|
+
() => {
|
|
128
|
+
}
|
|
129
|
+
).finally(() => {
|
|
130
|
+
if (generationRef.current !== startedAt) return;
|
|
131
|
+
setUploadingIds((previous) => {
|
|
132
|
+
const next = new Set(previous);
|
|
133
|
+
next.delete(id);
|
|
134
|
+
return next;
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
// `contract` is no longer a dependency: the generation is read off the ref
|
|
139
|
+
// at drop time, so this callback does not need rebuilding per pipe.
|
|
140
|
+
[uploadFile, commitValues]
|
|
141
|
+
);
|
|
142
|
+
const fieldEnv = React.useMemo(
|
|
143
|
+
() => {
|
|
144
|
+
var _a2, _b2, _c2;
|
|
145
|
+
return __spreadProps(__spreadValues({}, env), {
|
|
146
|
+
disabled: (_a2 = env == null ? void 0 : env.disabled) != null ? _a2 : running,
|
|
147
|
+
onDropFile: (_b2 = env == null ? void 0 : env.onDropFile) != null ? _b2 : uploadFile ? handleDropFile : void 0,
|
|
148
|
+
// The UNION, not the host's set winning outright: `onDropFile` and
|
|
149
|
+
// `uploadingIds` default independently per key, so a host may hand us
|
|
150
|
+
// `uploadFile` (leaving the panel to own the drop, and to mark the field
|
|
151
|
+
// in its own set) while also passing a tracker of its own. Letting the
|
|
152
|
+
// host's set replace ours would hide an upload the panel itself started,
|
|
153
|
+
// and Run would stay live through it. A host that owns the whole loop
|
|
154
|
+
// supplies `onDropFile` too, so our set is empty and the union is exactly
|
|
155
|
+
// its set — "yours win" still holds where that clause applies.
|
|
156
|
+
uploadingIds: /* @__PURE__ */ new Set([...(_c2 = env == null ? void 0 : env.uploadingIds) != null ? _c2 : EMPTY_IDS, ...uploadingIds])
|
|
157
|
+
});
|
|
158
|
+
},
|
|
159
|
+
[env, running, uploadFile, handleDropFile, uploadingIds]
|
|
160
|
+
);
|
|
161
|
+
const uploading = ((_b = (_a = fieldEnv.uploadingIds) == null ? void 0 : _a.size) != null ? _b : 0) > 0;
|
|
162
|
+
const notReady = readiness.missing.length > 0;
|
|
163
|
+
const blocked = running || notReady || uploading || ((_c = env == null ? void 0 : env.disabled) != null ? _c : false);
|
|
164
|
+
const [revealed, setRevealed] = React.useState(NOTHING_REVEALED);
|
|
165
|
+
const [latchedFor, setLatchedFor] = React.useState(contract);
|
|
166
|
+
let revealedNow = revealed;
|
|
167
|
+
if (latchedFor !== contract) {
|
|
168
|
+
revealedNow = NOTHING_REVEALED;
|
|
169
|
+
setLatchedFor(contract);
|
|
170
|
+
setRevealed(revealedNow);
|
|
171
|
+
}
|
|
172
|
+
const newlyRevealed = fields.filter(
|
|
173
|
+
(field) => !field.required && isFilled(values[field.name]) && !revealedNow.has(field.name)
|
|
174
|
+
);
|
|
175
|
+
if (newlyRevealed.length > 0) {
|
|
176
|
+
revealedNow = /* @__PURE__ */ new Set([...revealedNow, ...newlyRevealed.map((field) => field.name)]);
|
|
177
|
+
setRevealed(revealedNow);
|
|
178
|
+
}
|
|
179
|
+
const isFolded = React.useCallback(
|
|
180
|
+
(field) => !field.required && !revealedNow.has(field.name),
|
|
181
|
+
[revealedNow]
|
|
182
|
+
);
|
|
183
|
+
const foldableCount = fields.filter(
|
|
184
|
+
(field) => !field.required && !isFilled(values[field.name])
|
|
185
|
+
).length;
|
|
186
|
+
const visibleFields = showOptional ? fields : fields.filter((field) => !isFolded(field));
|
|
187
|
+
const justSubmittedRef = React.useRef(false);
|
|
188
|
+
const handleSubmit = (event) => {
|
|
189
|
+
event.preventDefault();
|
|
190
|
+
if (blocked || justSubmittedRef.current) return;
|
|
191
|
+
const outcome = runSubmitGate(contract, fields, values, translate);
|
|
192
|
+
if (!outcome.ok) {
|
|
193
|
+
setSubmitError(outcome.summary);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
setSubmitError(null);
|
|
197
|
+
justSubmittedRef.current = true;
|
|
198
|
+
queueMicrotask(() => {
|
|
199
|
+
justSubmittedRef.current = false;
|
|
200
|
+
});
|
|
201
|
+
onRun(outcome.apiInputs);
|
|
202
|
+
};
|
|
203
|
+
const paletteStyle = React.useMemo(
|
|
204
|
+
() => getPaletteForTheme(theme),
|
|
205
|
+
[theme]
|
|
206
|
+
);
|
|
207
|
+
return /* @__PURE__ */ jsxs(
|
|
208
|
+
"form",
|
|
209
|
+
{
|
|
210
|
+
className: ["mthds-run-panel", theme === GRAPH_THEME.DARK && "dark", className].filter(Boolean).join(" "),
|
|
211
|
+
style: paletteStyle,
|
|
212
|
+
noValidate: true,
|
|
213
|
+
onSubmit: handleSubmit,
|
|
214
|
+
children: [
|
|
215
|
+
title && /* @__PURE__ */ jsx("h2", { className: "mthds-run-panel-title", children: title }),
|
|
216
|
+
fields.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "mthds-run-panel-fields", children: [
|
|
217
|
+
visibleFields.map((field) => /* @__PURE__ */ jsx(
|
|
218
|
+
FieldRenderer,
|
|
219
|
+
{
|
|
220
|
+
field,
|
|
221
|
+
value: values[field.name],
|
|
222
|
+
onChange: (value) => commitValues(__spreadProps(__spreadValues({}, values), { [field.name]: value })),
|
|
223
|
+
id: field.name,
|
|
224
|
+
env: fieldEnv
|
|
225
|
+
},
|
|
226
|
+
field.name
|
|
227
|
+
)),
|
|
228
|
+
foldableCount > 0 && /* @__PURE__ */ jsx(
|
|
229
|
+
OptionalToggle,
|
|
230
|
+
{
|
|
231
|
+
count: foldableCount,
|
|
232
|
+
expanded: showOptional,
|
|
233
|
+
onToggle: () => {
|
|
234
|
+
setShowOptional((expanded) => !expanded);
|
|
235
|
+
setRevealed(NOTHING_REVEALED);
|
|
236
|
+
},
|
|
237
|
+
noun: "input"
|
|
238
|
+
}
|
|
239
|
+
)
|
|
240
|
+
] }) : /* @__PURE__ */ jsx("p", { className: "mthds-run-panel-empty", children: "This pipe takes no inputs." }),
|
|
241
|
+
submitError && /* @__PURE__ */ jsx("p", { className: "mthds-run-panel-error", role: "alert", children: submitError }),
|
|
242
|
+
/* @__PURE__ */ jsxs("div", { className: "mthds-run-panel-footer", children: [
|
|
243
|
+
/* @__PURE__ */ jsx(
|
|
244
|
+
"button",
|
|
245
|
+
{
|
|
246
|
+
type: "submit",
|
|
247
|
+
className: "mthds-run-panel-run",
|
|
248
|
+
disabled: blocked,
|
|
249
|
+
"aria-describedby": notReady ? readinessHintId : void 0,
|
|
250
|
+
children: running ? "Running\u2026" : "Run"
|
|
251
|
+
}
|
|
252
|
+
),
|
|
253
|
+
notReady && /* @__PURE__ */ jsx("span", { id: readinessHintId, className: "mthds-run-panel-readiness", children: `${readiness.ready} of ${readiness.total} ready \u2014 still needed: ${readiness.missing.join(", ")}` })
|
|
254
|
+
] })
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
RunPanel,
|
|
261
|
+
defaultValidationTranslate,
|
|
262
|
+
runSubmitGate,
|
|
263
|
+
summarizeVerdict
|
|
264
|
+
};
|
|
265
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/form/react/RunPanel.tsx","../../../src/form/runGate.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n computeReadiness,\n fieldsForContract,\n isFilled,\n setValueAtPath,\n type PipeIOContract,\n type RunField,\n} from \"@pipelex/mthds-form\";\nimport { FieldRenderer, OptionalToggle, type FieldEnv } from \"@pipelex/mthds-form/react\";\nimport { getPaletteForTheme } from \"@graph/graphConfig\";\nimport { GRAPH_THEME, type GraphTheme } from \"@graph/types\";\nimport { runSubmitGate, type RunPanelTranslate } from \"@form/runGate\";\nimport \"./RunPanel.css\";\n\n/** What a host's upload returns: the stored URL, and optionally a display name. */\nexport interface UploadedFile {\n url: string;\n filename?: string;\n}\n\nexport interface RunPanelProps {\n /**\n * The pipe's IO contract — the kernel's type, supplied by the host.\n *\n * Hosts that can actually run a method already hold `pipe_io_contracts` from\n * the same `/validate` call that produced their graph spec; look an entry up\n * with the kernel's `getPipeIOContract(contracts, domain, pipeCode)`. The\n * panel deliberately does NOT accept a `GraphSpec` and derive a contract from\n * it — deriving field meaning locally is precisely what this component exists\n * not to do (design Decision A).\n */\n contract: PipeIOContract;\n /** Fully controlled field values, host-owned — the kernel's philosophy. */\n values: Record<string, unknown>;\n onValuesChange: (values: Record<string, unknown>) => void;\n /** Fires only after the kernel's run gate passes, with the wire-ready payload. */\n onRun: (apiInputs: Record<string, unknown>) => void;\n /**\n * A run is in flight: the fields and the Run button go inert.\n *\n * **Set it synchronously inside `onRun`, before any `await`.** Across anything\n * longer than one task it is the only thing standing between a second submit\n * and a duplicate execution, and the panel cannot supply that itself: it never\n * learns that a run finished, so a lock held for a run's lifetime would have\n * no release. A host that flips this only once the server answers leaves the\n * button live for the whole round trip, and a second click there starts a\n * second run that nothing undoes.\n *\n * Synchronous re-entry within a single task is the panel's own problem and is\n * handled — see `justSubmittedRef` beside `handleSubmit`. That guard does not\n * reduce what this prop is for; it closes a window this prop cannot reach,\n * because React has not re-rendered inside it.\n */\n running?: boolean;\n /**\n * Ambient field state, passed to every control.\n *\n * For `disabled`, `onDropFile` and `resolveUrl` the host's value wins PER\n * KEY: the panel fills in only what the host left undefined — `disabled`\n * from `running`, `onDropFile` from its own upload tracking when\n * `uploadFile` is supplied.\n *\n * `uploadingIds` is the exception, and deliberately so: it is the UNION of\n * your set and the panel's, never a replacement. Because the keys default\n * independently, a host can supply `uploadFile` — leaving the panel to own\n * the drop and mark the field in its own set — while also passing a tracker\n * of its own; letting yours win outright there would hide an upload the\n * panel itself started and leave Run live through it. A host that owns the\n * whole loop supplies `onDropFile` too, so the panel's set is empty and the\n * union is exactly your set.\n */\n env?: FieldEnv;\n /**\n * Stores a dropped file and returns its URL. The host performs the upload —\n * this library never touches an API client — while the panel owns the\n * bookkeeping around it: marking the field busy and writing the result back\n * at the field's dotted path.\n */\n uploadFile?: (file: File, fieldId: string) => Promise<UploadedFile>;\n /** Panel header. The host names the pipe; there is no default heading. */\n title?: string;\n /**\n * The resolved theme, which drives the kernel's `.dark` class on the panel\n * container alongside this library's own palette variables. Defaults to\n * `light`. Pair it with `GraphViewer`'s `onThemeChange`, whose second\n * argument is exactly this resolved value.\n */\n theme?: GraphTheme;\n /**\n * Renders the whole error summary in the host's language. English by default.\n *\n * It covers the panel's own lines (`runPanel.*`) as well as the kernel's\n * per-error ones, because which of them a rejected run produces is the\n * kernel's decision and not something a host can predict.\n */\n translate?: RunPanelTranslate;\n /** Appended to the panel container's class list. */\n className?: string;\n}\n\nconst EMPTY_IDS: ReadonlySet<string> = new Set<string>();\nconst NOTHING_REVEALED: ReadonlySet<string> = new Set<string>();\n\n/**\n * A pipe's input form: fields, readiness, and the run gate, composed.\n *\n * Every question of meaning is the form kernel's — `fieldsForContract` decides\n * what each input IS, `computeReadiness` decides whether the form MAY run, and\n * `runSubmitGate` (over the kernel's four steps) decides what goes on the wire.\n * What lives here is layout: which fields are visible, where the toggle and the\n * button sit, and how a rejected verdict is shown. The panel never reads\n * `json_schema` to make a rendering decision and never sniffs a value's shape\n * (design Decision C) — which is what keeps a change to the kernel's derivation\n * invisible to this repo.\n */\nexport function RunPanel({\n contract,\n values,\n onValuesChange,\n onRun,\n running = false,\n env,\n uploadFile,\n title,\n theme = GRAPH_THEME.LIGHT,\n translate,\n className,\n}: RunPanelProps) {\n const [showOptional, setShowOptional] = React.useState(false);\n const [uploadingIds, setUploadingIds] = React.useState<ReadonlySet<string>>(EMPTY_IDS);\n const [submitError, setSubmitError] = React.useState<string | null>(null);\n const readinessHintId = React.useId();\n\n const fields = React.useMemo(() => fieldsForContract(contract), [contract]);\n const readiness = React.useMemo(() => computeReadiness(fields, values), [fields, values]);\n\n const commitValues = React.useCallback(\n (next: Record<string, unknown>) => {\n // A summary describes the values that produced it; the moment they move\n // it is stale, and a stale complaint about a field just corrected is\n // worse than none.\n setSubmitError(null);\n onValuesChange(next);\n },\n [onValuesChange],\n );\n\n // An upload resolves long after the render that started it, so the write-back\n // must not target the values THAT render captured — the user may well have\n // typed elsewhere while waiting. This mirror holds the latest ones. Only the\n // async continuation reads it; every synchronous path stays on the prop,\n // which is correct there because it runs inside the render that captured it.\n // Layout, not passive, for the reason spelled out on the contract marker\n // below: a mirror that lags the commit is read by continuations landing in\n // exactly that lag, and here the cost is the host's newer edits overwritten.\n const valuesRef = React.useRef(values);\n React.useLayoutEffect(() => {\n valuesRef.current = values;\n }, [values]);\n\n // Switching pipes makes an upload started under the old one irrelevant — and\n // worse than irrelevant when both pipes declare the same input name, which\n // happens within a single method: `recruitment.cv_screening` and\n // `recruitment.extract_cv` both take a required `cv` document. A file chosen\n // for one would land in the other looking like a deliberate answer, gating\n // satisfied, ready to run. So a drop remembers the generation it happened\n // under, and a result arriving under a later one is dropped. This makes\n // `contract` referentially significant — a host that rebuilds it every render\n // loses its uploads (and is already rebuilding every field, since `fields`\n // memoizes on it).\n //\n // The marker COUNTS rather than naming the contract, because identity answers\n // a subtly different question. A host can leave pipe A and come back to the\n // very same contract object while A's upload is still in flight, and an\n // identity check then says \"same pipe, accept it\" — ABA, and it is not the\n // same form: switching away emptied `uploadingIds`, so that upload stopped\n // gating Run and its dropzone re-opened. Accepting it lands a file in a form\n // that never marked it — Run was live throughout, and for a non-gating input\n // the run may already have gone out without it — and, if the user re-dropped\n // on the way back, overwrites the fresh result while un-marking an upload\n // still in progress. Counting makes the predicate \"has the form moved on\n // since this drop\", which is what the guard always meant.\n //\n // A LAYOUT effect, deliberately. The marker answers \"which form is on\n // screen\", and it has to answer that as of the last COMMIT. A passive effect\n // answers later: React schedules those on a task, while an upload settling is\n // a promise continuation — a microtask, which runs first. In that window the\n // switched form is already rendered and the marker still counts the pipe the\n // user left, so the guard compares the departed generation against itself and\n // lets through precisely the write it exists to reject. Bumping during render\n // would close the window too, and is worse: a concurrent render React\n // abandons would advance past a contract that never commits, and an upload\n // belonging to the pipe still on screen would be thrown away instead. Layout\n // effects run inside the commit, and only for renders that commit.\n //\n // The bump lives in the CLEANUP, which is the one place BOTH ways of leaving a\n // form pass through. Changing `contract` is only one of them: a host that\n // writes `<RunPanel key={pipeRef} …>` — the ordinary React idiom for \"reset\n // this child when the entity changes\" — never changes the prop at all, it\n // unmounts this instance and mounts another. The effect body would then never\n // re-run, the marker would still equal the departed upload's `startedAt`, and\n // that upload's continuation would call `onValuesChange`, which is the HOST's\n // setter and very much still alive. The abandoned file would land in the\n // replacement panel's `cv` exactly as if it had been chosen there. Bumping on\n // cleanup makes the predicate mean what it says — this form instance has\n // stopped being the one on screen — however it stopped.\n const generationRef = React.useRef(0);\n React.useLayoutEffect(() => {\n // Whatever was in flight belonged to the previous pipe; it must not go on\n // gating this form's Run button until it happens to settle.\n setUploadingIds(EMPTY_IDS);\n // Nor may a complaint about the previous pipe's inputs stand over this\n // pipe's form: it names fields that are no longer on screen.\n setSubmitError(null);\n return () => {\n generationRef.current += 1;\n };\n }, [contract]);\n\n const handleDropFile = React.useCallback(\n (id: string, file: File) => {\n if (!uploadFile) return;\n const startedAt = generationRef.current;\n setUploadingIds((previous) => new Set(previous).add(id));\n // Called through an async wrapper rather than directly. `uploadFile` is\n // the HOST's function and is typed as a plain function returning a\n // promise — nothing obliges it to be `async`. One that validates before\n // it starts the request (no API key configured, a file over the size\n // limit, a mime type it will not take) throws SYNCHRONOUSLY, and a\n // synchronous throw happens before there is a promise to hang `.catch()`\n // and `.finally()` on. The id marked one line above would then stay\n // marked for good: `uploading` disables the dropzone, so the field cannot\n // even be retried, and Run stays gated until the user leaves the form\n // entirely. The wrapper turns that throw into the rejection this chain\n // already cleans up, which makes the SAME host logic behave the same way\n // whether it was written `async` or not — the only difference between\n // those two spellings being the keyword.\n void (async () => await uploadFile(file, id))()\n .then(\n (uploaded) => {\n if (generationRef.current !== startedAt) return;\n const next = setValueAtPath(valuesRef.current, id.split(\".\"), {\n url: uploaded.url,\n filename: uploaded.filename ?? file.name,\n });\n // Advance the mirror here rather than waiting for the effect. Two\n // uploads finishing in the same React batch both run before any\n // re-render, so both would otherwise read the same snapshot and the\n // second write would drop the first — a pipe with two file inputs is\n // ordinary (`candidate_screening.screen_candidate` takes a `cv` and a\n // `job_offer`), and the loss is silent: both fields look settled\n // while one value is gone. The effect still owns syncing FROM the\n // host, so the mirror converges on whatever the host actually kept.\n valuesRef.current = next;\n commitValues(next);\n },\n () => {\n // The host owns how a failed upload is announced (it owns the\n // transport). Swallowing it here keeps a rejected promise from\n // becoming an unhandled rejection; the field simply stays empty.\n //\n // This is `.then`'s SECOND ARGUMENT rather than a `.catch()` link,\n // and the difference is the whole point: a `.catch()` chained after\n // the handler above also catches whatever that handler throws, and\n // the handler dereferences a value the host produced. `uploadFile`\n // is typed `Promise<UploadedFile>`, but a type is not a runtime\n // guarantee — a host branch that forgets its `return`, or unwraps a\n // JSON response one level too few, resolves to `undefined`, and\n // `uploaded.url` then throws. Swallowed, that is the one failure in\n // this whole lifecycle with NO diagnostic surface anywhere: the\n // spinner clears, the field reads empty, nothing is logged, and\n // re-dropping repeats it forever. As the second argument this\n // handler covers only the upload's own rejection, which is all it\n // ever claimed to; a write-back bug stays an unhandled rejection,\n // where the host's error reporting can see it.\n },\n )\n .finally(() => {\n // The same generation check as the write-back, for a sharper reason.\n // Switching contracts already emptied this set, and the very same id\n // may since have been re-added by a NEW upload under the new\n // generation — nothing exotic, since that is what sharing the `cv`\n // input name means, and a host returning to the pipe it left reaches\n // it with no rename at all. Deleting it here would un-mark an upload\n // that is still running: its dropzone re-enables mid-flight, its\n // progress indicator vanishes, and the Run gate lets go of it.\n if (generationRef.current !== startedAt) return;\n setUploadingIds((previous) => {\n const next = new Set(previous);\n next.delete(id);\n return next;\n });\n });\n },\n // `contract` is no longer a dependency: the generation is read off the ref\n // at drop time, so this callback does not need rebuilding per pipe.\n [uploadFile, commitValues],\n );\n\n const fieldEnv = React.useMemo<FieldEnv>(\n () => ({\n ...env,\n disabled: env?.disabled ?? running,\n onDropFile: env?.onDropFile ?? (uploadFile ? handleDropFile : undefined),\n // The UNION, not the host's set winning outright: `onDropFile` and\n // `uploadingIds` default independently per key, so a host may hand us\n // `uploadFile` (leaving the panel to own the drop, and to mark the field\n // in its own set) while also passing a tracker of its own. Letting the\n // host's set replace ours would hide an upload the panel itself started,\n // and Run would stay live through it. A host that owns the whole loop\n // supplies `onDropFile` too, so our set is empty and the union is exactly\n // its set — \"yours win\" still holds where that clause applies.\n uploadingIds: new Set([...(env?.uploadingIds ?? EMPTY_IDS), ...uploadingIds]),\n }),\n [env, running, uploadFile, handleDropFile, uploadingIds],\n );\n\n // Readiness alone would let a run go out while a file is still uploading: a\n // non-gating file input (optional, or plural — `mustBeFilled` excludes lists)\n // never counts toward readiness, so Run stays live through its upload and the\n // method would run with the file simply absent. Read off `fieldEnv` rather\n // than the raw state, so a host driving its own loop through\n // `env.uploadingIds` gets the same gate.\n const uploading = (fieldEnv.uploadingIds?.size ?? 0) > 0;\n const notReady = readiness.missing.length > 0;\n\n // ONE expression, read by the button and by the submit path, because they are\n // the same question asked twice and any gap between them is reachable: the\n // panel puts a real `<form>` in the host's DOM under a class name the docs\n // offer as a hook, and `form.requestSubmit()` ignores the submitter entirely,\n // so a disabled button stops nothing there. (The keyboard it does stop: Run is\n // this form's only submit button — every control the kernel renders carries\n // `type=\"button\"` — hence the default button, and implicit submission on a\n // disabled default button does nothing. Measured, not assumed.)\n //\n // Each term has to be here on its own merit, and none is redundant with the\n // kernel gate that runs afterwards. `uploading`: a non-gating file input\n // (optional, or plural — `mustBeFilled` excludes lists) never counts toward\n // readiness, so without this the method runs with the file simply absent.\n // `notReady`: an empty REQUIRED text input reaches ajv as `{ text: \"\" }`, a\n // perfectly valid string, so the gate passes it and only readiness notices —\n // that is pinned in `runGate.test.ts` and is exactly why the button gates on\n // readiness at all. `running`: a second run started over the first is a\n // duplicate execution, which nothing downstream would undo — and this term is\n // only ever as timely as the host makes it, which is why the prop's own doc\n // asks for it synchronously. `env.disabled`: the panel already treats the\n // host's flag and `running` as ONE concept where the fields are concerned\n // (`fieldEnv.disabled` falls back from one to the other), so gating the\n // button on only one of the two sources was the same asymmetry rounds 4 and 9\n // turned up — a host freezing the form got every input inert and a live Run\n // button beside them, and a click there starts a run from a form the user\n // cannot edit. It is read off the prop rather than off `fieldEnv.disabled`\n // because those differ exactly when the host passes `disabled: false` while\n // `running` is true, and there the button must still be held.\n const blocked = running || notReady || uploading || (env?.disabled ?? false);\n\n // Optional inputs that are still empty stay folded, so the form opens at its\n // simplest shape and grows on demand. Required or already-filled always show.\n //\n // Which optionals are revealed is LATCHED rather than re-derived from the\n // current value, and that is the whole point. Deriving it live means an\n // optional the user empties disappears from under the cursor: clearing the\n // last character flips `isFilled`, the field drops out of `visibleFields`,\n // and React unmounts the very input being typed into — focus lost, every\n // keystroke after it going nowhere. It needs no host pre-fill to reach:\n // expand the fold, fill an optional, collapse it (it stays on screen because\n // it is filled), then clear it.\n //\n // So between fold transitions the form's shape only ever GROWS. The latch is\n // cleared by the toggle's own handler and by a contract change, which are the\n // two moments nobody is mid-edit; each is followed by the grow pass below\n // re-adding every optional that still holds a value, so an emptied one folds\n // back. Nothing about readiness or the wire depends on this — an emptied\n // optional is omitted from the payload whether or not its input is on screen.\n //\n // The latch is STATE adjusted during render, not a ref mutated during render,\n // and in a library component that distinction is load-bearing. A ref written\n // during render survives a render that never commits, so a host rendering the\n // panel inside a transition — which App Router navigation does by default —\n // can have an abandoned render for contract B wipe the set that the committed\n // contract A is still displaying from, and an optional the user revealed,\n // collapsed and emptied vanishes on A's next render. That is the very defect\n // this latch exists to prevent, re-entering by another door. React discards a\n // state update from an abandoned render, so the two-`useState` form below is\n // the one that actually holds. It is React's documented shape for adjusting\n // state when a prop changes, and it converges: after the update lands, the\n // reset branch and `newlyRevealed` are both empty on the following pass.\n const [revealed, setRevealed] = React.useState<ReadonlySet<string>>(NOTHING_REVEALED);\n const [latchedFor, setLatchedFor] = React.useState(contract);\n // Read this render off the local value, never off `revealed`: a state update\n // made during render is not visible until the re-render it schedules, and the\n // output computed below has to already reflect it.\n let revealedNow = revealed;\n if (latchedFor !== contract) {\n revealedNow = NOTHING_REVEALED;\n setLatchedFor(contract);\n setRevealed(revealedNow);\n }\n const newlyRevealed = fields.filter(\n (field) => !field.required && isFilled(values[field.name]) && !revealedNow.has(field.name),\n );\n if (newlyRevealed.length > 0) {\n revealedNow = new Set([...revealedNow, ...newlyRevealed.map((field) => field.name)]);\n setRevealed(revealedNow);\n }\n const isFolded = React.useCallback(\n (field: RunField) => !field.required && !revealedNow.has(field.name),\n [revealedNow],\n );\n // What a collapse would hide — every EMPTY optional — rather than what is\n // hidden right now. The two differ only for an optional that was revealed and\n // then emptied, and that difference is the whole affordance: counting the\n // latched-visible ones leaves the count at zero there, the toggle unrendered,\n // and the empty input stuck on screen with no control that would fold it. The\n // latch keeps deciding what renders; it just stops deciding whether the user\n // is offered a way out of it.\n const foldableCount = fields.filter(\n (field) => !field.required && !isFilled(values[field.name]),\n ).length;\n const visibleFields = showOptional ? fields : fields.filter((field) => !isFolded(field));\n\n // A submit that has passed the gate latches until the end of the current task.\n //\n // `blocked` is computed during render, so two `requestSubmit()` calls made in\n // ONE synchronous host callback both close over the same render and both pass\n // it — even when the host sets `running` synchronously inside `onRun`, exactly\n // as that prop's doc demands, because React has not re-rendered in between.\n // Two real clicks are two separate tasks with a commit between them and were\n // never affected; this closes the programmatic path, which is a surface this\n // component documents and pins a story against.\n //\n // The release is a microtask, and that is the whole reason this is safe rather\n // than the lock this component deliberately does NOT take. A lock held for the\n // lifetime of a run would have no release — the panel is told when a run\n // starts and never that one finished — so a host that never passes `running`\n // would be wedged after its first run. This latch outlives nothing: the\n // microtask is queued unconditionally and always runs, so it cannot wedge, and\n // it holds no run state a later render could disagree with.\n const justSubmittedRef = React.useRef(false);\n\n const handleSubmit = (event: React.FormEvent) => {\n event.preventDefault();\n // The submit PATH owns the gate, not the button's `disabled` attribute —\n // see `blocked` above for why each term is there and why none is covered by\n // the kernel gate that runs next.\n if (blocked || justSubmittedRef.current) return;\n const outcome = runSubmitGate(contract, fields, values, translate);\n if (!outcome.ok) {\n setSubmitError(outcome.summary);\n return;\n }\n setSubmitError(null);\n // Latched BEFORE `onRun`, so a host that re-enters synchronously from inside\n // its own handler is held by the same guard.\n justSubmittedRef.current = true;\n queueMicrotask(() => {\n justSubmittedRef.current = false;\n });\n onRun(outcome.apiInputs);\n };\n\n const paletteStyle = React.useMemo(\n () => getPaletteForTheme(theme) as React.CSSProperties,\n [theme],\n );\n\n return (\n <form\n className={[\"mthds-run-panel\", theme === GRAPH_THEME.DARK && \"dark\", className]\n .filter(Boolean)\n .join(\" \")}\n style={paletteStyle}\n // The panel's gate is the authority on whether a run may go out, and the\n // browser's is not merely redundant beside it — it is WRONG. The kernel's\n // number control carries `step={integer ? 1 : 0.1}`, so any float with\n // more than one decimal is a `stepMismatch` even though `native.Number`\n // accepts it and the kernel's own ajv pass does too. Interactive\n // validation then aborts submission before `onSubmit` runs at all: no\n // gate, no `onRun`, no error from the panel, and a native message that is\n // wrong about the domain (\"nearest valid values are 87.2 and 87.3\").\n // Turning it off is what makes the documented `requestSubmit()` promise\n // true, and costs nothing — every constraint that should hold is checked\n // by `runSubmitGate`.\n noValidate\n onSubmit={handleSubmit}\n >\n {title && <h2 className=\"mthds-run-panel-title\">{title}</h2>}\n\n {fields.length > 0 ? (\n <div className=\"mthds-run-panel-fields\">\n {visibleFields.map((field) => (\n <FieldRenderer\n key={field.name}\n field={field}\n value={values[field.name]}\n onChange={(value) => commitValues({ ...values, [field.name]: value })}\n id={field.name}\n env={fieldEnv}\n />\n ))}\n {foldableCount > 0 && (\n <OptionalToggle\n count={foldableCount}\n expanded={showOptional}\n onToggle={() => {\n setShowOptional((expanded) => !expanded);\n // Clearing the latch here is what makes \"the shape only grows\"\n // a temporary state rather than a permanent one. A fold\n // transition is a click on this button, so nobody is mid-edit\n // in a field, and the render below re-adds every optional that\n // still holds a value — leaving exactly the emptied ones to\n // fold away, which is what the user just asked for.\n setRevealed(NOTHING_REVEALED);\n }}\n noun=\"input\"\n />\n )}\n </div>\n ) : (\n <p className=\"mthds-run-panel-empty\">This pipe takes no inputs.</p>\n )}\n\n {submitError && (\n <p className=\"mthds-run-panel-error\" role=\"alert\">\n {submitError}\n </p>\n )}\n\n <div className=\"mthds-run-panel-footer\">\n {/*\n The readiness line is the only thing on screen that says WHY Run is\n disabled, so it has to be reachable by the people who cannot see it\n sitting next to the button — and a disabled button is out of the tab\n order, so they will not arrive at it by walking the controls either.\n `aria-describedby` is the association that makes a screen reader read\n the reason with the button instead of announcing a bare dimmed \"Run\".\n The id comes from `useId` because a host may put two panels on one\n page, and a hardcoded one would make the second panel describe the\n first panel's button.\n */}\n <button\n type=\"submit\"\n className=\"mthds-run-panel-run\"\n disabled={blocked}\n aria-describedby={notReady ? readinessHintId : undefined}\n >\n {running ? \"Running…\" : \"Run\"}\n </button>\n {notReady && (\n <span id={readinessHintId} className=\"mthds-run-panel-readiness\">\n {`${readiness.ready} of ${readiness.total} ready — still needed: ${readiness.missing.join(\", \")}`}\n </span>\n )}\n </div>\n </form>\n );\n}\n","/**\n * The panel's submit path, kept React-free.\n *\n * Everything between \"the user pressed Run\" and \"the payload goes on the wire\"\n * is the kernel's (`@pipelex/mthds-form`, `core/gate.ts`); this module bridges\n * the panel's values into the shape the gate takes, calls it, and turns the\n * verdict into one line a panel can show. Splitting it out of `RunPanel.tsx` is\n * what lets the node vitest project test that seam without mounting React — the\n * gate's own behavior is covered by the kernel's tests and is deliberately not\n * re-tested here.\n */\nimport {\n describeValidationError,\n gateRunInputs,\n rjsfDataFromRunValues,\n type PipeIOContract,\n type RunField,\n type RunInputError,\n type ValidationMessageKey,\n} from \"@pipelex/mthds-form\";\n\n/**\n * How many ajv errors the summary quotes when the missing-input scan comes up\n * empty. A form with a deep mismatch can produce dozens; past a handful the\n * line stops being readable and the user acts on the first one anyway.\n */\nconst MAX_QUOTED_ERRORS = 3;\n\n/** The outcome of one submit attempt. */\nexport type RunGateOutcome =\n | { ok: true; apiInputs: Record<string, unknown> }\n | { ok: false; missingInputs: string[]; summary: string };\n\n/**\n * Every message key the error summary can render — the kernel's, plus the two\n * this panel owns.\n *\n * The kernel's keys describe one ajv error each and are rendered by\n * `describeValidationError`. The `runPanel.` ones are lines the panel writes\n * itself, around those errors, and they carry the prefix precisely so the\n * ownership is legible at the call site and in a host's translation file.\n *\n * Nesting the kernel's union inside ours is deliberate: a key the kernel ADDS\n * lands here automatically and then fails `VALIDATION_STRINGS`'s exhaustiveness\n * until it is given a string, which is how a kernel bump is stopped from\n * silently rendering `undefined` at a user.\n */\nexport type RunPanelMessageKey =\n | ValidationMessageKey\n | \"runPanel.missingInputs\"\n | \"runPanel.fillRequired\";\n\n/**\n * The panel's i18n seam — the kernel's `Translate` widened to the panel's own\n * keys. It accepts a superset of what the kernel asks for, so it is still a\n * valid `Translate` wherever the kernel wants one.\n */\nexport type RunPanelTranslate = (\n key: RunPanelMessageKey,\n values?: Record<string, string>,\n) => string;\n\n/**\n * English defaults for every key above. The kernel deliberately ships no\n * defaults for its own (its `Translate` contract is host-supplied), so a panel\n * that wants to say anything at all has to name them — the wording matches the\n * canonical consumer's `en.json` so the two surfaces read alike.\n */\nconst VALIDATION_STRINGS: Record<RunPanelMessageKey, string> = {\n \"inputPanel.aDateField\": \"A date field\",\n \"inputPanel.pickValidDate\": \"{label}: “{value}” is not a valid date\",\n \"inputPanel.pickValidDateEmpty\": \"{label}: pick a valid date\",\n \"inputPanel.dateCarriesTime\":\n \"{label}: “{value}” carries a time, but a date field takes the day alone — use “{day}” and put the time in the “time” field\",\n \"inputPanel.invalidValue\": \"Invalid value\",\n \"inputPanel.invalidValueWithData\": \"{stack} (value: “{value}”)\",\n \"runPanel.missingInputs\": \"Missing required fields in: {inputs}\",\n \"runPanel.fillRequired\": \"Please fill in all required fields before running.\",\n};\n\n/**\n * The default translator: the English strings above with `{placeholder}`\n * interpolation. A host that localizes passes its own translator instead.\n */\nexport const defaultValidationTranslate: RunPanelTranslate = (key, values) => {\n const template = VALIDATION_STRINGS[key];\n if (!values) return template;\n return template.replace(/\\{(\\w+)\\}/g, (whole, name: string) => values[name] ?? whole);\n};\n\n/**\n * One human line for an invalid verdict.\n *\n * `missingInputs` is the good case: it names the VARIABLES at fault, which is\n * what the user clicks on. The scan can legitimately come up empty on an\n * invalid form (a wrong value shape, a nested mismatch), and that is exactly\n * when falling back to the raw ajv errors keeps the failure diagnosable rather\n * than silent — see `validateRunInputs` in the kernel.\n *\n * All three routes go through `t`. Which one runs is decided by the kernel's\n * verdict, so a host that localizes cannot know in advance which line it will\n * be shown — leaving any of them in hardcoded English would surface as the\n * summary silently reverting to English on some inputs and not others.\n */\nexport function summarizeVerdict(\n missingInputs: string[],\n errors: RunInputError[],\n preparedData: unknown,\n t: RunPanelTranslate = defaultValidationTranslate,\n): string {\n if (missingInputs.length > 0) {\n return t(\"runPanel.missingInputs\", { inputs: missingInputs.join(\", \") });\n }\n const described = errors\n .slice(0, MAX_QUOTED_ERRORS)\n .map((error) => describeValidationError(error, t, preparedData))\n .join(\"; \");\n return described || t(\"runPanel.fillRequired\");\n}\n\n/**\n * Run the kernel's gate over the panel's current values.\n *\n * `gateRunInputs` IS the gate — the whole chain in one call — so all this\n * module does either side of it is bridge and phrase: the panel holds values\n * keyed by variable, the gate takes the schema-shaped data those become, and a\n * rejected verdict has to come back as one line rather than as a discriminated\n * union the panel would have to read.\n *\n * Assembling those steps here instead was the earlier shape and it was wrong in\n * a way nothing local could see. The chain's schema validation is not the whole\n * rule: ajv's `required` only asserts a key is PRESENT, and a content model\n * carries no `minLength`, so a required input that arrived empty — `{text: \"\"}`\n * from an untouched text field, `{url: \"\"}` from an untouched file field —\n * satisfied the schema and the run went out. The Run button refuses exactly\n * those, through `computeReadiness`. The gate now re-applies that same\n * emptiness rule with `computeReadiness`'s own functions over the same derived\n * fields, which makes the button's verdict and this one a single invariant\n * rather than two things that resemble each other.\n */\nexport function runSubmitGate(\n contract: PipeIOContract,\n fields: RunField[],\n values: Record<string, unknown>,\n t: RunPanelTranslate = defaultValidationTranslate,\n): RunGateOutcome {\n const verdict = gateRunInputs(contract, rjsfDataFromRunValues(values, fields));\n if (!verdict.ok) {\n return {\n ok: false,\n missingInputs: verdict.missingInputs,\n summary: summarizeVerdict(verdict.missingInputs, verdict.errors, verdict.preparedData, t),\n };\n }\n return { ok: true, apiInputs: verdict.inputs };\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
package/dist/graph/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { A as ARROW_CLOSED_MARKER,
|
|
1
|
+
import { o as GraphSpecNode, r as PipeCallNode, b as GraphSpec, D as DataflowAnalysis, C as ConceptInfo, P as PipeBlueprintUnion, s as GraphData, n as GraphSpecMode, t as PipeCardPayload, l as FoldToggleOptions, e as GraphNode, d as GraphEdge, c as GraphNodeData, N as NodeValidationSummary, V as ValidationIssue, g as GraphDirection, u as LayoutConfig, f as GraphConfig, T as ToolbarPosition, G as GraphTheme } from '../types-Cv5Elty8.js';
|
|
2
|
+
export { A as ARROW_CLOSED_MARKER, v as CONTROLLER_PADDING_BOTTOM, w as CONTROLLER_PADDING_TOP, x as CONTROLLER_PADDING_X, E as EDGE_TYPE, y as EdgeType, z as FOLD_MODE, B as FieldResolution, F as FoldMode, H as GRAPH_DIRECTION, I as GRAPH_SPEC_MODE, J as GRAPH_THEME, K as GRAPH_THEME_MODE, M as GraphSpecEdge, O as GraphSpecEdgeKind, Q as GraphSpecMeta, R as GraphSpecModelUsage, S as GraphSpecNodeError, U as GraphSpecNodeIo, p as GraphSpecNodeIoItem, W as GraphSpecNodeTiming, X as GraphSpecNodeUsage, Y as GraphSpecUsage, h as GraphThemeMode, Z as KNOWN_PIPE_TYPES, L as LabelDescriptor, _ as NODE_TYPE_CONTROLLER, $ as NODE_TYPE_PIPE_CARD, a0 as NODE_TYPE_STUFF, a1 as NodeKind, a2 as PipeBatchBlueprint, a3 as PipeBlueprintBase, a4 as PipeComposeBlueprint, a5 as PipeComposeConstructBlueprint, a6 as PipeComposeConstructField, a7 as PipeConditionBlueprint, k as PipeControllerType, a8 as PipeExtractBlueprint, a9 as PipeFuncBlueprint, aa as PipeImgGenBlueprint, ab as PipeLLMBlueprint, q as PipeOperatorType, ac as PipeParallelBlueprint, ad as PipeSearchBlueprint, ae as PipeSequenceBlueprint, af as PipeSignatureBlueprint, i as PipeStatus, ag as PipeStructureBlueprint, m as PipeType, a as PresenceMarker, ah as STUFF_ID_PREFIX, ai as StuffRole, aj as StuffSpecInfo, ak as SubPipeSpec, al as TOOLBAR_POSITION, am as TemplateBlueprint, an as ToolbarOrientation, ao as ToolbarSide, ap as VALIDATION_STATE, j as ValidationState, aq as graphSpecMode, ar as isDryGraphSpec, as as isStaticGraphSpec, at as isStuffNodeId, au as nodeHeight, av as nodeWidth, aw as stuffDigestFromId, ax as stuffNodeId, ay as toolbarOrientation, az as toolbarSide } from '../types-Cv5Elty8.js';
|
|
3
3
|
export { P as ParsedPipeRef, m as makePipeRef, p as parsePipeRef } from '../pipeRefs-BQJO5nh5.js';
|
|
4
4
|
import { ElkNode } from 'elkjs/lib/elk-api';
|
|
5
5
|
|
package/dist/graph/index.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import "../chunk-QD5YDMWV.js";
|
|
2
2
|
import {
|
|
3
|
-
DARK_PALETTE_COLORS,
|
|
4
|
-
DEFAULT_GRAPH_CONFIG,
|
|
5
3
|
GraphSpecValidationError,
|
|
6
|
-
LIGHT_PALETTE_COLORS,
|
|
7
4
|
MAX_VISIBLE_CONTROLLER_CHILDREN,
|
|
8
5
|
applyControllers,
|
|
9
6
|
applyFolds,
|
|
@@ -23,8 +20,6 @@ import {
|
|
|
23
20
|
findCousinControllers,
|
|
24
21
|
getConceptInfo,
|
|
25
22
|
getLayoutedElements,
|
|
26
|
-
getPaletteColors,
|
|
27
|
-
getPaletteForTheme,
|
|
28
23
|
getPipeBlueprint,
|
|
29
24
|
inputPortId,
|
|
30
25
|
outermostFoldedAncestor,
|
|
@@ -32,7 +27,18 @@ import {
|
|
|
32
27
|
resolveConceptRef,
|
|
33
28
|
resolveIssueTargetNodeId,
|
|
34
29
|
validateGraphSpec
|
|
35
|
-
} from "../chunk-
|
|
30
|
+
} from "../chunk-BFQHQQ3E.js";
|
|
31
|
+
import {
|
|
32
|
+
makePipeRef,
|
|
33
|
+
parsePipeRef
|
|
34
|
+
} from "../chunk-2N65P32H.js";
|
|
35
|
+
import {
|
|
36
|
+
DARK_PALETTE_COLORS,
|
|
37
|
+
DEFAULT_GRAPH_CONFIG,
|
|
38
|
+
LIGHT_PALETTE_COLORS,
|
|
39
|
+
getPaletteColors,
|
|
40
|
+
getPaletteForTheme
|
|
41
|
+
} from "../chunk-HASMGCVK.js";
|
|
36
42
|
import {
|
|
37
43
|
ARROW_CLOSED_MARKER,
|
|
38
44
|
CONTROLLER_PADDING_BOTTOM,
|
|
@@ -55,15 +61,13 @@ import {
|
|
|
55
61
|
isDryGraphSpec,
|
|
56
62
|
isStaticGraphSpec,
|
|
57
63
|
isStuffNodeId,
|
|
58
|
-
makePipeRef,
|
|
59
64
|
nodeHeight,
|
|
60
65
|
nodeWidth,
|
|
61
|
-
parsePipeRef,
|
|
62
66
|
stuffDigestFromId,
|
|
63
67
|
stuffNodeId,
|
|
64
68
|
toolbarOrientation,
|
|
65
69
|
toolbarSide
|
|
66
|
-
} from "../chunk-
|
|
70
|
+
} from "../chunk-RJVEDP7M.js";
|
|
67
71
|
import "../chunk-2NMEKWO5.js";
|
|
68
72
|
export {
|
|
69
73
|
ARROW_CLOSED_MARKER,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import {
|
|
4
|
-
export {
|
|
3
|
+
import { c as GraphNodeData, d as GraphEdge, e as GraphNode, b as GraphSpec, f as GraphConfig, g as GraphDirection, F as FoldMode, h as GraphThemeMode, T as ToolbarPosition, G as GraphTheme, i as PipeStatus, j as ValidationState, V as ValidationIssue, L as LabelDescriptor, k as PipeControllerType, l as FoldToggleOptions, N as NodeValidationSummary, m as PipeType, n as GraphSpecMode, o as GraphSpecNode, C as ConceptInfo, p as GraphSpecNodeIoItem } from '../../types-Cv5Elty8.js';
|
|
4
|
+
export { q as PipeOperatorType } from '../../types-Cv5Elty8.js';
|
|
5
5
|
import { Edge, Node, ReactFlowInstance } from '@xyflow/react';
|
|
6
6
|
|
|
7
7
|
/** Data for rendering a stuff (data item) in the StuffViewer component. */
|