@pipelex/mthds-ui 0.27.0 → 0.28.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 +19 -5
- package/dist/{chunk-XBHDA3PQ.js → chunk-7WJYQHYF.js} +2 -2
- package/dist/{chunk-VCO4GZDZ.js → chunk-AE7AA5XC.js} +1 -1
- package/dist/{chunk-VCO4GZDZ.js.map → chunk-AE7AA5XC.js.map} +1 -1
- package/dist/{chunk-K3BWHQVX.js → chunk-I7Z5TTQI.js} +2 -2
- package/dist/form/react/RunPanel.css +1 -3
- package/dist/form/react/index.d.ts +5 -5
- package/dist/form/react/index.js +2 -2
- package/dist/form/react/index.js.map +1 -1
- package/dist/graph/index.d.ts +2 -2
- package/dist/graph/index.js +3 -3
- package/dist/graph/react/index.d.ts +107 -43
- package/dist/graph/react/index.js +37 -24
- package/dist/graph/react/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/standalone/graph-standalone.html +408 -114
- package/dist/standalone/graph-viewer.css +1 -1
- package/dist/standalone/graph-viewer.js +406 -112
- package/dist/static-graph/index.d.ts +1 -1
- package/dist/static-graph/index.js +1 -1
- package/dist/static-graph/index.js.map +1 -1
- package/dist/{types-DgqW3Jg4.d.ts → types-DvhUuMHz.d.ts} +1 -1
- package/package.json +2 -2
- /package/dist/{chunk-XBHDA3PQ.js.map → chunk-7WJYQHYF.js.map} +0 -0
- /package/dist/{chunk-K3BWHQVX.js.map → chunk-I7Z5TTQI.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/form/react/index.ts","../../../src/form/react/RunPanel.tsx","../../../src/form/runGate.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The `./form/react` entry — the run-form panel over `@pipelex/mthds-form`.\n *\n * The kernel is a DEPENDENCY of this package, so this entry is no longer the\n * only one allowed to import it. It stayed an optional peer while it powered\n * only this run form; it stopped being optional the moment the graph's detail\n * panel began rendering results through it, because a viewer whose detail\n * panel cannot show data is not a viewer. See `docs/stuff-result-panel.md`.\n */\n\n/**\n * No form kernel stylesheet is imported here, on purpose: the host styles the\n * kernel's controls in the way its kind of host needs. A host with Tailwind 4\n * compiles the kernel's classes itself, with\n * `@import \"@pipelex/mthds-ui/tailwind.css\"` in place of the `@source` line of\n * the kernel's documented setup; a host without Tailwind imports\n * `@pipelex/mthds-ui/form-kernel.css` once. The README's \"Styling the form\n * controls\" section is the host's side of it.\n *\n * Do not put an import back. v0.20.0 made the kernel's sheet this entry's job,\n * because Tailwind 3 hosts kept forgetting a content glob into `node_modules`\n * and silently lost the classes only the kernel uses (the result grid's column\n * template was the one that showed). v0.21.0 wrapped the sheet in a cascade\n * layer after the raw copy beat the host's own responsive variants. Neither\n * arrangement works, because the sheet is a complete Tailwind build whose two\n * halves need different places in a Tailwind 4 host's cascade: appended, the\n * layer outranked every variant the host wrote; named first, the host's\n * preflight stripped the kernel's utilities. In a host without Tailwind, the\n * preflight reset every browser default on the page the moment a panel\n * mounted. `docs/run-form-panel.md` has the full account.\n */\n\n/**\n * The kernel's React surface, re-exported. A host imports its controls from\n * here rather than naming `@pipelex/mthds-form` itself — see `../index.ts` for\n * why that indirection is the point rather than ceremony, and why importing the\n * kernel directly beside this package is the one thing that can reintroduce the\n * two-context-identities bug.\n */\nexport * from \"@pipelex/mthds-form/react\";\n\nexport { RunPanel } from \"./RunPanel\";\nexport type { RunPanelProps, UploadedFile } from \"./RunPanel\";\n\n// The submit path, React-free and re-exported for hosts that run the gate\n// outside a panel (a toolbar button, a keyboard shortcut).\nexport { runSubmitGate, summarizeVerdict, defaultValidationTranslate } from \"@form/runGate\";\nexport type { RunGateOutcome, RunPanelMessageKey, RunPanelTranslate } from \"@form/runGate\";\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n computeReadiness,\n fieldsForContract,\n isFilled,\n setValueAtPath,\n type PipeInputFormDescriptor,\n type PipeIOContract,\n type RunField,\n} from \"@pipelex/mthds-form\";\nimport {\n FieldDomIdProvider,\n FieldRenderer,\n OptionalToggle,\n type FieldEnv,\n} 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 /**\n * The pipe's input-form descriptor — the contract's sibling artifact, and\n * what now decides how every field renders.\n *\n * Since kernel `0.5.0` the descriptor IS the derivation: `buildRunFields`\n * maps this tree structurally, and the concept-name and schema-shape\n * heuristics that used to guess a field's kind are deleted rather than left\n * unreached. The contract is still required beside it — the two are\n * co-walked for the facts the descriptor deliberately omits (a scalar's\n * wrapper property, a nested array's bounds) — so neither artifact renders a\n * form on its own.\n *\n * It arrives the same way the contract does: the `/validate` call that\n * produced the graph spec returns `input_form` beside `pipe_io_contracts`\n * when asked for both views (`views: [\"pipe_io_contracts\", \"input_form\"]`),\n * and the kernel's `getPipeInputForm(inputForm, domain, pipeCode)` is\n * `getPipeIOContract`'s twin for looking an entry up.\n *\n * Required rather than optional, because the alternative is silent: the\n * kernel's `fieldsForContract` returns `[]` unless both artifacts are\n * present, so a host that omitted this would render a panel with no fields\n * and no error — a working form and an empty one look identical from here.\n */\n descriptor: PipeInputFormDescriptor;\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 /**\n * Makes this panel's control DOM ids predictable: a control at value path\n * `cv` gets `id=\"<idPrefix>-cv\"`, and `\"\"` writes the path unprefixed.\n *\n * Ids are namespaced whether or not you pass this. A control's `id` must be\n * unique in the DOCUMENT while a value path is unique only within one form,\n * so two panels each declaring an input named `text` would otherwise emit two\n * `id=\"text\"` — and a `<label for>` binds the first match in tree order,\n * which hands the second panel's label to the first panel's control and\n * leaves the second input with no accessible name. Left unset, the prefix\n * comes from `useId`: unique per panel and hydration-stable, but opaque, so\n * nothing outside can predict it.\n *\n * Pass one when something must ADDRESS a control — `getElementById`, a\n * deep-link that focuses a field, an end-to-end selector. Doing so moves the\n * uniqueness obligation to you: the prefix scopes this whole panel, so it\n * must be unique in the document, and `\"\"` restores the ids this panel emitted\n * before the kernel's fix along with the collision they had.\n */\n idPrefix?: string;\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 descriptor,\n values,\n onValuesChange,\n onRun,\n running = false,\n env,\n uploadFile,\n title,\n theme = GRAPH_THEME.LIGHT,\n translate,\n idPrefix,\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 const fallbackPrefix = React.useId();\n\n const fields = React.useMemo(\n () => fieldsForContract(contract, descriptor),\n [contract, descriptor],\n );\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 // One provider around the whole panel, which is exactly one form — the scope\n // the kernel's contract asks for. `fallbackPrefix` is this panel's own\n // `useId`, so an unset `idPrefix` keeps the kernel's default behaviour (unique\n // per instance) rather than turning the namespacing off.\n return (\n <FieldDomIdProvider prefix={idPrefix ?? fallbackPrefix}>\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 </FieldDomIdProvider>\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"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/form/react/index.ts","../../../src/form/react/RunPanel.tsx","../../../src/form/runGate.ts"],"sourcesContent":["\"use client\";\n\n/**\n * The `./form/react` entry — the run-form panel over `@pipelex/mthds-form`.\n *\n * The kernel is a DEPENDENCY of this package, so this entry is no longer the\n * only one allowed to import it. It stayed an optional peer while it powered\n * only this run form; it stopped being optional the moment the graph's detail\n * panel began rendering results through it, because a viewer whose detail\n * panel cannot show data is not a viewer. See `docs/stuff-result-panel.md`.\n */\n\n/**\n * No form kernel stylesheet is imported here, on purpose: the host styles the\n * kernel's controls in the way its kind of host needs. A host with Tailwind 4\n * compiles the kernel's classes itself, with\n * `@import \"@pipelex/mthds-ui/tailwind.css\"` in place of the `@source` line of\n * the kernel's documented setup; a host without Tailwind imports\n * `@pipelex/mthds-ui/form-kernel.css` once. The README's \"Styling the form\n * controls\" section is the host's side of it.\n *\n * Do not put an import back. v0.20.0 made the kernel's sheet this entry's job,\n * because Tailwind 3 hosts kept forgetting a content glob into `node_modules`\n * and silently lost the classes only the kernel uses (the result grid's column\n * template was the one that showed). v0.21.0 wrapped the sheet in a cascade\n * layer after the raw copy beat the host's own responsive variants. Neither\n * arrangement works, because the sheet is a complete Tailwind build whose two\n * halves need different places in a Tailwind 4 host's cascade: appended, the\n * layer outranked every variant the host wrote; named first, the host's\n * preflight stripped the kernel's utilities. In a host without Tailwind, the\n * preflight reset every browser default on the page the moment a panel\n * mounted. `docs/run-form-panel.md` has the full account.\n */\n\n/**\n * The kernel's React surface, re-exported. A host imports its controls from\n * here rather than naming `@pipelex/mthds-form` itself — see `../index.ts` for\n * why that indirection is the point rather than ceremony, and why importing the\n * kernel directly beside this package is the one thing that can reintroduce the\n * two-context-identities bug.\n */\nexport * from \"@pipelex/mthds-form/react\";\n\nexport { RunPanel } from \"./RunPanel\";\nexport type { RunPanelProps, UploadedFile } from \"./RunPanel\";\n\n// The submit path, React-free and re-exported for hosts that run the gate\n// outside a panel (a toolbar button, a keyboard shortcut).\nexport { runSubmitGate, summarizeVerdict, defaultValidationTranslate } from \"@form/runGate\";\nexport type { RunGateOutcome, RunPanelMessageKey, RunPanelTranslate } from \"@form/runGate\";\n","\"use client\";\n\nimport * as React from \"react\";\nimport {\n computeReadiness,\n fieldsForContract,\n isFilled,\n setValueAtPath,\n type PipeInputFormDescriptor,\n type PipeIOContract,\n type RunField,\n} from \"@pipelex/mthds-form\";\nimport {\n FieldDomIdProvider,\n FieldRenderer,\n OptionalToggle,\n type FieldEnv,\n} 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.\n */\n contract: PipeIOContract;\n /**\n * The pipe's input-form descriptor — the contract's sibling artifact, and\n * what now decides how every field renders.\n *\n * Since kernel `0.5.0` the descriptor IS the derivation: `buildRunFields`\n * maps this tree structurally, and the concept-name and schema-shape\n * heuristics that used to guess a field's kind are deleted rather than left\n * unreached. The contract is still required beside it — the two are\n * co-walked for the facts the descriptor deliberately omits (a scalar's\n * wrapper property, a nested array's bounds) — so neither artifact renders a\n * form on its own.\n *\n * It arrives the same way the contract does: the `/validate` call that\n * produced the graph spec returns `input_form` beside `pipe_io_contracts`\n * when asked for both views (`views: [\"pipe_io_contracts\", \"input_form\"]`),\n * and the kernel's `getPipeInputForm(inputForm, domain, pipeCode)` is\n * `getPipeIOContract`'s twin for looking an entry up.\n *\n * Required rather than optional, because the alternative is silent: the\n * kernel's `fieldsForContract` returns `[]` unless both artifacts are\n * present, so a host that omitted this would render a panel with no fields\n * and no error — a working form and an empty one look identical from here.\n */\n descriptor: PipeInputFormDescriptor;\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 /**\n * Makes this panel's control DOM ids predictable: a control at value path\n * `cv` gets `id=\"<idPrefix>-cv\"`, and `\"\"` writes the path unprefixed.\n *\n * Ids are namespaced whether or not you pass this. A control's `id` must be\n * unique in the DOCUMENT while a value path is unique only within one form,\n * so two panels each declaring an input named `text` would otherwise emit two\n * `id=\"text\"` — and a `<label for>` binds the first match in tree order,\n * which hands the second panel's label to the first panel's control and\n * leaves the second input with no accessible name. Left unset, the prefix\n * comes from `useId`: unique per panel and hydration-stable, but opaque, so\n * nothing outside can predict it.\n *\n * Pass one when something must ADDRESS a control — `getElementById`, a\n * deep-link that focuses a field, an end-to-end selector. Doing so moves the\n * uniqueness obligation to you: the prefix scopes this whole panel, so it\n * must be unique in the document, and `\"\"` restores the ids this panel emitted\n * before the kernel's fix along with the collision they had.\n */\n idPrefix?: string;\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 * since meaning is the kernel's — which is what keeps a change to the kernel's\n * derivation invisible to this repo.\n */\nexport function RunPanel({\n contract,\n descriptor,\n values,\n onValuesChange,\n onRun,\n running = false,\n env,\n uploadFile,\n title,\n theme = GRAPH_THEME.LIGHT,\n translate,\n idPrefix,\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 const fallbackPrefix = React.useId();\n\n const fields = React.useMemo(\n () => fieldsForContract(contract, descriptor),\n [contract, descriptor],\n );\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 // One provider around the whole panel, which is exactly one form — the scope\n // the kernel's contract asks for. `fallbackPrefix` is this panel's own\n // `useId`, so an unset `idPrefix` keeps the kernel's default behaviour (unique\n // per instance) rather than turning the namespacing off.\n return (\n <FieldDomIdProvider prefix={idPrefix ?? fallbackPrefix}>\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 </FieldDomIdProvider>\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 { p as GraphSpecNode, r as PipeCallNode, b as GraphSpec, D as DataflowAnalysis, C as ConceptInfo, P as PipeBlueprintUnion, s as GraphData, o as GraphSpecMode, t as PipeCardPayload, m as FoldToggleOptions, e as GraphNode, d as GraphEdge, c as GraphNodeData, N as NodeValidationSummary, V as ValidationIssue, j as GraphSpecNodeIoItem, g as GraphDirection, u as LayoutConfig, f as GraphConfig, T as ToolbarPosition, G as GraphTheme } from '../types-
|
|
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, 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, l 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, n as PipeType, a as PresenceMarker, ah as STUFF_ID_PREFIX, ai as StuffMultiplicity, aj as StuffRegistryEntry, ak as StuffRole, al as StuffSpecInfo, am as SubPipeSpec, an as TOOLBAR_POSITION, ao as TemplateBlueprint, ap as ToolbarOrientation, aq as ToolbarSide, ar as VALIDATION_STATE, k as ValidationState, as as graphSpecMode, at as isDryGraphSpec, au as isPluralMultiplicity, av as isStaticGraphSpec, aw as isStuffNodeId, ax as multiplicitySuffix, ay as nodeHeight, az as nodeWidth, aA as stuffDigestFromId, aB as stuffNodeId, aC as toolbarOrientation, aD as toolbarSide } from '../types-
|
|
1
|
+
import { p as GraphSpecNode, r as PipeCallNode, b as GraphSpec, D as DataflowAnalysis, C as ConceptInfo, P as PipeBlueprintUnion, s as GraphData, o as GraphSpecMode, t as PipeCardPayload, m as FoldToggleOptions, e as GraphNode, d as GraphEdge, c as GraphNodeData, N as NodeValidationSummary, V as ValidationIssue, j as GraphSpecNodeIoItem, g as GraphDirection, u as LayoutConfig, f as GraphConfig, T as ToolbarPosition, G as GraphTheme } from '../types-DvhUuMHz.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, 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, l 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, n as PipeType, a as PresenceMarker, ah as STUFF_ID_PREFIX, ai as StuffMultiplicity, aj as StuffRegistryEntry, ak as StuffRole, al as StuffSpecInfo, am as SubPipeSpec, an as TOOLBAR_POSITION, ao as TemplateBlueprint, ap as ToolbarOrientation, aq as ToolbarSide, ar as VALIDATION_STATE, k as ValidationState, as as graphSpecMode, at as isDryGraphSpec, au as isPluralMultiplicity, av as isStaticGraphSpec, aw as isStuffNodeId, ax as multiplicitySuffix, ay as nodeHeight, az as nodeWidth, aA as stuffDigestFromId, aB as stuffNodeId, aC as toolbarOrientation, aD as toolbarSide } from '../types-DvhUuMHz.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
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
resolveConceptRef,
|
|
30
30
|
resolveIssueTargetNodeId,
|
|
31
31
|
validateGraphSpec
|
|
32
|
-
} from "../chunk-
|
|
32
|
+
} from "../chunk-I7Z5TTQI.js";
|
|
33
33
|
import {
|
|
34
34
|
makePipeRef,
|
|
35
35
|
parsePipeRef
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
LIGHT_PALETTE_COLORS,
|
|
41
41
|
getPaletteColors,
|
|
42
42
|
getPaletteForTheme
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-7WJYQHYF.js";
|
|
44
44
|
import {
|
|
45
45
|
ARROW_CLOSED_MARKER,
|
|
46
46
|
CONTROLLER_PADDING_BOTTOM,
|
|
@@ -71,7 +71,7 @@ import {
|
|
|
71
71
|
stuffNodeId,
|
|
72
72
|
toolbarOrientation,
|
|
73
73
|
toolbarSide
|
|
74
|
-
} from "../chunk-
|
|
74
|
+
} from "../chunk-AE7AA5XC.js";
|
|
75
75
|
import "../chunk-2NMEKWO5.js";
|
|
76
76
|
export {
|
|
77
77
|
ARROW_CLOSED_MARKER,
|
|
@@ -1,11 +1,83 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { c as GraphNodeData, d as GraphEdge, e as GraphNode,
|
|
4
|
-
export { q as PipeOperatorType } from '../../types-
|
|
3
|
+
import { b as GraphSpec, c as GraphNodeData, d as GraphEdge, e as GraphNode, f as GraphConfig, g as GraphDirection, F as FoldMode, h as GraphThemeMode, T as ToolbarPosition, G as GraphTheme, i as PipeStatus, j as GraphSpecNodeIoItem, k as ValidationState, V as ValidationIssue, L as LabelDescriptor, l as PipeControllerType, m as FoldToggleOptions, N as NodeValidationSummary, n as PipeType, o as GraphSpecMode, C as ConceptInfo, p as GraphSpecNode } from '../../types-DvhUuMHz.js';
|
|
4
|
+
export { q as PipeOperatorType } from '../../types-DvhUuMHz.js';
|
|
5
5
|
import { PipeIOContracts, OutputForm, InputForm, SaveFiles } from '@pipelex/mthds-form';
|
|
6
6
|
import { ResolveUrl, ResolveShareUrl, DownloadDisplay } from '@pipelex/mthds-form/react';
|
|
7
7
|
import { Edge, Node, ReactFlowInstance } from '@xyflow/react';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* A graph and the artifacts that describe its pipes, taken from ONE source.
|
|
11
|
+
*
|
|
12
|
+
* `GraphViewer` takes this as its single `graph` prop rather than a spec and
|
|
13
|
+
* three descriptor props side by side, because the descriptors are keyed by the
|
|
14
|
+
* graph's own `pipe_ref`s: the detail panel walks from a data node back to the
|
|
15
|
+
* pipe that produced it and looks that pipe up in `outputForm` and
|
|
16
|
+
* `pipeIoContracts`. A spec paired with another source's descriptors (a past
|
|
17
|
+
* run's graph beside the current method's validate report, after the method's
|
|
18
|
+
* pipes were renamed) misses every lookup, and the panel can only say that no
|
|
19
|
+
* descriptor describes the value. Carrying all four in one object is what keeps
|
|
20
|
+
* them from one run, or from one validate call.
|
|
21
|
+
*
|
|
22
|
+
* Where each source lives:
|
|
23
|
+
*
|
|
24
|
+
* - **A run**: its results carry `graph_spec`, `pipe_io_contracts`,
|
|
25
|
+
* `output_form` and `input_form` together; `graphArtifactsFrom(results)`
|
|
26
|
+
* reads them in one call.
|
|
27
|
+
* - **A method that has not run** (a dry run, or a static graph built from its
|
|
28
|
+
* sources): the descriptors come from the same method's validate report, the
|
|
29
|
+
* one the dry graph or the static build describes.
|
|
30
|
+
*
|
|
31
|
+
* The descriptors are optional, and nullable as the wire carries them. Without
|
|
32
|
+
* `pipeIoContracts` and `outputForm` together, the panel shows a data node's
|
|
33
|
+
* structure table and no data tab, which is the honest floor for a graph whose
|
|
34
|
+
* artifacts were not produced. `inputForm` is optional even beside the other
|
|
35
|
+
* two: it is what lets a method's own inputs, which no pipe produced, show their
|
|
36
|
+
* value through the consuming pipe's slot.
|
|
37
|
+
*/
|
|
38
|
+
interface GraphArtifacts {
|
|
39
|
+
/** The graph to draw: a run's `graph_spec`, a dry run's, or a static build. */
|
|
40
|
+
graphSpec: GraphSpec;
|
|
41
|
+
/** `pipe_io_contracts` for the pipes of THIS graph. */
|
|
42
|
+
pipeIoContracts?: PipeIOContracts | null;
|
|
43
|
+
/** `output_form` for the pipes of THIS graph, from the same source. */
|
|
44
|
+
outputForm?: OutputForm | null;
|
|
45
|
+
/** `input_form` for the pipes of THIS graph, from the same source. */
|
|
46
|
+
inputForm?: InputForm | null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* The four artifacts as a run's results, or a validate report, spell them on
|
|
50
|
+
* the wire. `graph_spec` is `unknown` because transport layers type it that way;
|
|
51
|
+
* `GraphViewer` validates it at its boundary.
|
|
52
|
+
*/
|
|
53
|
+
interface GraphArtifactsSource {
|
|
54
|
+
graph_spec?: unknown;
|
|
55
|
+
pipe_io_contracts?: PipeIOContracts | null;
|
|
56
|
+
output_form?: OutputForm | null;
|
|
57
|
+
input_form?: InputForm | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read the graph and its descriptors out of one run's results (or one report
|
|
61
|
+
* that carries all four), so a host never assembles the bundle by hand from two
|
|
62
|
+
* places. Returns `null` when the source carries no graph.
|
|
63
|
+
*/
|
|
64
|
+
declare function graphArtifactsFrom(source: GraphArtifactsSource): GraphArtifacts | null;
|
|
65
|
+
/** The descriptors the detail panel renders a value from, once they are usable. */
|
|
66
|
+
interface ResultDescriptors {
|
|
67
|
+
pipeIoContracts: PipeIOContracts;
|
|
68
|
+
outputForm: OutputForm;
|
|
69
|
+
inputForm?: InputForm;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The descriptors a graph's detail panel can render from, or `null`.
|
|
73
|
+
*
|
|
74
|
+
* Both `pipeIoContracts` and `outputForm`, or neither: the contract names the
|
|
75
|
+
* payload's shape and the descriptor says what the result IS, so a panel given
|
|
76
|
+
* one of the two would be guessing the other. `inputForm` rides along when the
|
|
77
|
+
* other two are there.
|
|
78
|
+
*/
|
|
79
|
+
declare function resultDescriptors(graph: GraphArtifacts | null): ResultDescriptors | null;
|
|
80
|
+
|
|
9
81
|
/** ReactFlow node parameterized with our domain data. */
|
|
10
82
|
type AppNode = Node<GraphNodeData>;
|
|
11
83
|
/** ReactFlow edge with standard fields (domain-only fields are dropped at the boundary). */
|
|
@@ -30,7 +102,21 @@ declare function toAppNodes(nodes: GraphNode[]): AppNode[];
|
|
|
30
102
|
declare function toAppEdges(edges: GraphEdge[]): AppEdge[];
|
|
31
103
|
|
|
32
104
|
interface GraphViewerProps {
|
|
33
|
-
|
|
105
|
+
/**
|
|
106
|
+
* The graph to draw, together with the artifacts that describe its pipes —
|
|
107
|
+
* all four from ONE source: a run's results, or a method's validate report
|
|
108
|
+
* beside the dry or static graph of that same method. `null` draws nothing.
|
|
109
|
+
*
|
|
110
|
+
* One object rather than a spec prop and three descriptor props, because the
|
|
111
|
+
* detail panel looks a data node's descriptor up by the `pipe_ref` of the pipe
|
|
112
|
+
* that produced it IN THIS GRAPH. A graph paired with another source's
|
|
113
|
+
* descriptors misses every lookup. See `GraphArtifacts`.
|
|
114
|
+
*
|
|
115
|
+
* Without `pipeIoContracts` and `outputForm`, the panel still works: it shows
|
|
116
|
+
* the concept's structure table and no data tab, which is the honest floor
|
|
117
|
+
* for a graph whose artifacts the host does not hold.
|
|
118
|
+
*/
|
|
119
|
+
graph: GraphArtifacts | null;
|
|
34
120
|
config?: GraphConfig;
|
|
35
121
|
/** Initial layout direction. Users can toggle this via the built-in toolbar. */
|
|
36
122
|
initialDirection?: GraphDirection;
|
|
@@ -102,35 +188,6 @@ interface GraphViewerProps {
|
|
|
102
188
|
onPaneClick?: () => void;
|
|
103
189
|
/** Render extra content below the built-in detail panel content for the selected node. */
|
|
104
190
|
renderDetailExtra?: (nodeId: string, nodeData: GraphNodeData) => React.ReactNode;
|
|
105
|
-
/**
|
|
106
|
-
* `pipe_io_contracts` for the method this spec is a run of — one half of what
|
|
107
|
-
* the detail panel needs to render a data node's VALUE.
|
|
108
|
-
*
|
|
109
|
-
* The graph renders results itself now. It used to take a render prop instead,
|
|
110
|
-
* because the form kernel was an optional peer and `./graph/react` had to keep
|
|
111
|
-
* resolving without it — an arrangement that made sense while the kernel only
|
|
112
|
-
* powered an optional run form, and stopped making sense the moment the
|
|
113
|
-
* standard's `output_form` became how this viewer shows a result at all. A
|
|
114
|
-
* viewer whose detail panel cannot show data is not a viewer, so the kernel is
|
|
115
|
-
* a dependency of this package and the seam is gone.
|
|
116
|
-
*
|
|
117
|
-
* Omit these and the panel still works: it shows the concept's structure table
|
|
118
|
-
* and no data tab, which is the honest floor for a spec whose artifacts the
|
|
119
|
-
* host does not hold (a static graph, a spec restored without its validate
|
|
120
|
-
* report).
|
|
121
|
-
*/
|
|
122
|
-
contracts?: PipeIOContracts;
|
|
123
|
-
/**
|
|
124
|
-
* `output_form` from the SAME `/validate` call — the other half. The contract
|
|
125
|
-
* names the payload's shape; the descriptor says what the result IS.
|
|
126
|
-
*/
|
|
127
|
-
outputForm?: OutputForm;
|
|
128
|
-
/**
|
|
129
|
-
* `input_form`, optional even here. It is what lets a method's own INPUTS show
|
|
130
|
-
* their value: no pipe produced them, so no output descriptor describes them,
|
|
131
|
-
* and the CONSUMING pipe's descriptor for their slot is what names them.
|
|
132
|
-
*/
|
|
133
|
-
inputForm?: InputForm;
|
|
134
191
|
/**
|
|
135
192
|
* Turns the runtime's own `pipelex-storage://…` reference into a URL a
|
|
136
193
|
* browser can fetch, so files in a result actually paint.
|
|
@@ -336,10 +393,12 @@ declare function PipeCardBase({ data, children }: PipeCardBaseProps): react_jsx_
|
|
|
336
393
|
*
|
|
337
394
|
* ## What it needs from the host, and why
|
|
338
395
|
*
|
|
339
|
-
* The artifacts
|
|
340
|
-
* `pipe_ref`. The join is the producing pipe: the graph hands
|
|
341
|
-
* `producerPipeRef` (see `stuffLookup.ts`), and `output_form` plus the
|
|
342
|
-
* half of the contract are looked up by it.
|
|
396
|
+
* The artifacts a run's results (or a `/validate` call) return beside the graph
|
|
397
|
+
* spec, keyed by `pipe_ref`. The join is the producing pipe: the graph hands
|
|
398
|
+
* over `producerPipeRef` (see `stuffLookup.ts`), and `output_form` plus the
|
|
399
|
+
* output half of the contract are looked up by it. That join only holds when
|
|
400
|
+
* the artifacts and the graph come from the same source, which is why
|
|
401
|
+
* `GraphViewer` takes them as one `graph` object (`GraphArtifacts`).
|
|
343
402
|
*
|
|
344
403
|
* ## The method's own inputs, which no pipe produced
|
|
345
404
|
*
|
|
@@ -380,9 +439,14 @@ declare function PipeCardBase({ data, children }: PipeCardBaseProps): react_jsx_
|
|
|
380
439
|
* never reach a panel that installs its own.
|
|
381
440
|
*/
|
|
382
441
|
interface StuffResultRendererOptions {
|
|
383
|
-
/**
|
|
384
|
-
|
|
385
|
-
|
|
442
|
+
/**
|
|
443
|
+
* `pipe_io_contracts` from the same source as the `producerPipeRef` and
|
|
444
|
+
* `consumer` below: the run, or the validate report, whose graph they were
|
|
445
|
+
* read from. Inside `GraphViewer` that pairing is guaranteed by its single
|
|
446
|
+
* `graph` prop; a host using this panel on its own owns it.
|
|
447
|
+
*/
|
|
448
|
+
pipeIoContracts: PipeIOContracts;
|
|
449
|
+
/** `output_form` from that SAME source. */
|
|
386
450
|
outputForm: OutputForm;
|
|
387
451
|
/**
|
|
388
452
|
* `input_form`, optional. Supplying it is what lets a method's own INPUTS
|
|
@@ -440,7 +504,7 @@ interface StuffResultPanelProps extends StuffResultRendererOptions {
|
|
|
440
504
|
*/
|
|
441
505
|
downloads?: DownloadDisplay;
|
|
442
506
|
}
|
|
443
|
-
declare function StuffResultPanel({
|
|
507
|
+
declare function StuffResultPanel({ pipeIoContracts, outputForm, inputForm, stuff, producerPipeRef, consumer, resolveUrl, resolveShareUrl, saveFiles, downloads, }: StuffResultPanelProps): react_jsx_runtime.JSX.Element | null;
|
|
444
508
|
|
|
445
509
|
interface DetailPanelProps {
|
|
446
510
|
isOpen: boolean;
|
|
@@ -472,8 +536,8 @@ interface ConceptDetailPanelProps {
|
|
|
472
536
|
isDryRun?: boolean;
|
|
473
537
|
/**
|
|
474
538
|
* Renders the data half. `GraphViewer` supplies it as a `StuffResultPanel`,
|
|
475
|
-
* the form kernel's descriptor-driven result view, when the host
|
|
476
|
-
* `
|
|
539
|
+
* the form kernel's descriptor-driven result view, when the host's `graph`
|
|
540
|
+
* carries `pipeIoContracts` and `outputForm`; see `docs/stuff-result-panel.md`.
|
|
477
541
|
*
|
|
478
542
|
* Without it — or when it returns nothing for this item — the panel shows the
|
|
479
543
|
* structure table alone and no tabs, which is what a viewer that has not been
|
|
@@ -511,4 +575,4 @@ interface UseResizableResult {
|
|
|
511
575
|
}
|
|
512
576
|
declare function useResizable({ defaultWidth, minWidth, maxWidth, containerRef, }: UseResizableOptions): UseResizableResult;
|
|
513
577
|
|
|
514
|
-
export { type AppEdge, type AppNode, type AppRFInstance, ConceptDetailPanel, type ConceptDetailPanelProps, ControllerGroupNode, DetailPanel, type DetailPanelProps, GraphViewer, type GraphViewerProps, PipeCardBase, type PipeCardBaseProps, type PipeCardData, PipeCardNode, PipeDetailPanel, type PipeDetailPanelProps, PipeStatus, StuffResultPanel, type StuffResultPanelProps, type StuffResultRendererOptions, type UseResizableOptions, type UseResizableResult, ValidationPanel, type ValidationPanelPlacement, type ValidationPanelProps, applyStatusOverrides, controllerNodeTypes, detectSystemTheme, hydrateLabels, renderLabel, toAppEdges, toAppNodes, useResizable, useSystemTheme, validationLabel, validationPanelPlacement };
|
|
578
|
+
export { type AppEdge, type AppNode, type AppRFInstance, ConceptDetailPanel, type ConceptDetailPanelProps, ControllerGroupNode, DetailPanel, type DetailPanelProps, type GraphArtifacts, type GraphArtifactsSource, GraphViewer, type GraphViewerProps, PipeCardBase, type PipeCardBaseProps, type PipeCardData, PipeCardNode, PipeDetailPanel, type PipeDetailPanelProps, PipeStatus, type ResultDescriptors, StuffResultPanel, type StuffResultPanelProps, type StuffResultRendererOptions, type UseResizableOptions, type UseResizableResult, ValidationPanel, type ValidationPanelPlacement, type ValidationPanelProps, applyStatusOverrides, controllerNodeTypes, detectSystemTheme, graphArtifactsFrom, hydrateLabels, renderLabel, resultDescriptors, toAppEdges, toAppNodes, useResizable, useSystemTheme, validationLabel, validationPanelPlacement };
|