Skip to content

feat(apollo-react): shared guardrail definitions layer and useGuardrailDefinitions [AL-574] - #1139

Draft
andreizdrali-uipath wants to merge 8 commits into
mainfrom
feat/apollo-react-guardrail-definitions-layer
Draft

feat(apollo-react): shared guardrail definitions layer and useGuardrailDefinitions [AL-574]#1139
andreizdrali-uipath wants to merge 8 commits into
mainfrom
feat/apollo-react-guardrail-definitions-layer

Conversation

@andreizdrali-uipath

@andreizdrali-uipath andreizdrali-uipath commented Sep 9, 2026

Copy link
Copy Markdown

Builds the shared guardrail definitions layer AL-574 asks for, in the apollo-react guardrails family. This is the seam between the /api/execution/guardrails/definitions payload and the GuardrailDefinitions GuardrailBuilder already renders. Flow and Agents each carry their own copy of this today, and the two have drifted.

Base, and what to review

Base main, 8 commits, dev-packages labelled. The first two are #1107 (a96ef79f) and #1138
(9fa10765), each now a single squashed commit; review only the last six:

  • feat(apollo-react): shared guardrail definitions layer and useGuardrailDefinitions - the work.
  • fix(apollo-react): review fixes for the guardrail definitions layer [AL-574] - a first pass of
    review fixes, listed under Review fixes below.
  • fix(apollo-react): stop inventing map-enum bounds the range check now enforces [AL-574] - see
    Synthesized map-enum bounds below.
  • feat(apollo-react): guardrail status chip, shared with #1140 and
    fix(apollo-react): render the guardrail status chip as a span, shared with #1140 - moved down
    here from feat(apollo-react): guardrail list section [AL-575] #1140 and feat(apollo-react): add-guardrail palette [AL-576] #1147, see Shared leaves move here below.
  • test(apollo-react): share the canvas catalog scans across the family [AL-574] - same section.

Retargeted from feat/apollo-react-guardrails-family to main on 2026-09-11: pr-checks,
dev-publish and preview-deploy are all gated on branches: [main, 'support/**'], so against a
feature base they never ran and the absent checks meant "not run", not "passed".

For most of that time they still did not run, because every branch in this stack conflicted with
main in packages/apollo-wind/src/components/ui/select.tsx and GitHub skips the pull_request
workflows when it cannot build a test merge. That is resolved upstream as of 2026-09-14: this
branch and the three leaves all merge into main with zero conflicts, so checks and the -pr1139
preview pair should publish normally from here.

Rebased 2026-09-14 onto #1138's current head (9fa10765), which had itself moved onto #1107's
current head (a96ef79f). Both were squashed to one commit apiece and carry +556/-92 over 32
files
of new content versus the head this PR previously sat on, so the 18 commits that used to be
below this work are gone. Two consequences are visible here: the form now validates number
min/max through zod in onChange mode rather than as DOM attributes, and
getOutOfRangeParameterIds has been widened to map-enum (see Synthesized map-enum bounds).
Merge order is still #1107, then #1138, then this.

Built fresh from #1107/#1138, the rescoped Jira ticket and Confluence §7.3/§7.4.5. Nothing is ported
from the closed #1132.

What lands

Module Public surface
definitions-wire.ts GuardrailDefinitionWire, GuardrailParameterDefinitionWire
definitions-parse.ts parseGuardrailDefinitions
definitions-copy.ts GUARDRAIL_COPY_EN, GUARDRAIL_COPY_EN_MESSAGES, useGuardrailDefinitionCopy
definitions-enrich.ts enrichGuardrailDefinitions, isByoGuardrailDefinition, humanizeGuardrailParameterId, withGuardrailFolderMetadata
use-guardrail-definitions.ts useGuardrailDefinitions
unknown payload → parseGuardrailDefinitions → enrichGuardrailDefinitions → GuardrailBuilder
                       (zod, private)          (canonical copy on lingui)
                                    useGuardrailDefinitions composes all three

Plus a README section, exports from Guardrails/index.ts (no package.json change, ./canvas/guardrails already points there), and the new guardrails.definitions.* ids in the canvas catalog.

Contract highlights

  • The parser never throws. A non-array payload sets inputError; a single bad definition is dropped whole and reported in invalid, which is what both products already do entry by entry. Unknown keys stripped. Transport errors and data errors are separate channels: a malformed payload leaves error null.
  • zod does not cross the boundary. The schema is private to definitions-parse.ts and pinned to the hand-written mirror by a bidirectional assignability check that sits on the hot path in toWireDefinition (so it cannot be dropped as dead code), plus two tests: a key-set assertion over both shapes, and a source-level guard that reads the folder's files and fails on a zod import outside the parser. grep zod dist/canvas/components/Guardrails/**/*.d.ts is empty.
  • Enrichment is pure and exported, so Flow's vsix bridge and non-React callers use it directly. EnrichedGuardrailDefinition extends GuardrailDefinition, so its output feeds the builder unmapped.
  • options.definitions skips the request entirely, which is how Agents keeps SWR, Flow studio and workbench keep react-query, and the vsix keeps postMessage.
  • hiddenValidators hides nothing by default and never hides a BYO definition. Which validators a product exposes is an entitlement decision, so it stays with the caller.

One deliberate divergence from useDiscoveryModels

The context is compared by content, not identity. Keying the effect off context identity means an inline context object refetches on every render, and since every response sets state the loop never terminates. The hook test caught it at 17,640 calls before the fix. useDiscoveryModels still has this footgun; worth a separate look.

Synthesized map-enum bounds, and why they are gone

Enrichment used to give an unbounded map-enum parameter 0..1 step 0.1: some backends omit the
bounds on the threshold maps, and an unbounded numeric editor for a confidence score is a data-entry
hazard. That default was safe only while nothing enforced it, and the JSDoc said so in as many
words: "Before widening either to map-enum, replace this default with something the backend
states."

#1138 has now widened it. getOutOfRangeParameterIds covers map-enum as of 9fa10765, and hosts
gate Save on it, so a bound this layer invented would block a save over a number the backend never
published. Enrichment now passes the wire's min/max through when they are sent and leaves them
off when they are not.
step stays a hint, since neither that check nor buildFieldValidation
reads it and without it a 0..1 score steps by 1.

Nothing on today's wire changes behaviour: PII's entityThresholds arrives unbounded and defaults
to 0.8, harmful content arrives with its own 0..6. What changes is that a future unbounded map
on another scale cannot be Save-gated against a number we made up. The test that pinned the old
"never checked" premise now pins the new one, including that a map on the backend's own bounds
does report.

Shared leaves move here

Two things the leaf PRs were each carrying their own copy of now land once, on the layer they all
branch from. The leaves inherit them and their own diffs shrink by the same amount.

  • GuardrailStatusChip (components/guardrail-status-chip.tsx plus its test), cherry-picked
    verbatim from feat(apollo-react): add-guardrail palette [AL-576] #1147 where it was already an isolated commit. It was byte-identical on feat(apollo-react): guardrail list section [AL-575] #1140 and
    feat(apollo-react): add-guardrail palette [AL-576] #1147 and had to be kept that way by hand, one cherry-pick per edit. A <span> composed from
    wind's exported badgeVariants rather than the Badge component, which renders a <div>: the
    palette entry puts these chips inside its <button>, where flow content is invalid. Comes with
    GUARDRAIL_CHIP_GEOMETRY, extracted from guardrail-chip.tsx so the interactive and read-only
    chips stay one system. Nothing in this PR renders it yet; AL-578 and the two leaves do.
  • The catalog scans (__fixtures__/catalog-coverage.ts). Every component's i18n.test.ts was
    re-implementing the same three checks over its own id prefix, about 40 lines apiece: English
    parity, orphaned ids, and translation coverage. They are reporting functions rather than
    assertions, so a failure still points at the calling test's own line. The definitions layer is the
    first caller and gains what it was missing: the orphan sweep now covers all thirteen catalogs
    instead of English alone, and there is a coverage check that pins FIPassportNumber by name
    rather than leaving the gap invisible.

Canonical copy moves onto lingui

The display copy for the six built-in validators currently lives twice, in Agents' OOB_GUARDRAILS_I8N and Flow's buildValidatorDisplayInfo. Here it is 63 lingui messages in the shared canvas catalog, so both products get the same wording and the same translations, and the strings enter the real loc pipeline instead of a host-side constant.

Message ids use raw wire values (USSocialSecurityNumber), never a transcribed slug. Transcribing is exactly how the two products ended up keying the same Finland entity as finNationalId and fiNationalId.

Translations harvested from whichever product each string was adopted from: 62 of 63 ids in each of the 12 locales. The gap is FIPassportNumber, which Agents has not had translated; it falls back to English per key. ru is empty, matching both products and this package's existing convention.

src/canvas uses no lingui macros, so lingui extract does not feed this catalog and never did: its entries are hand-authored. A test asserts the catalog matches the source in both directions, which is what extraction would otherwise do for you.

QA-visible copy changes

The two products' English differs in 17 places. Each choice is declared with a reason in definitions-parity.test.ts and asserted against both products' transcribed copy, so the suite fails on an undeclared difference, a stale declaration, or a third wording we invented.

Agents users will see: shorter validator descriptions (the "This validator is designed to..." preamble is gone from four of them); harmfulContentEntities reads "Content categories" and its thresholds "Severity thresholds"; ipEntities reads "Content types"; PII thresholds pluralized; LLM-as-judge threshold reads "Strictness"; SelfHarm reads "Self-harm"; a new LLM-as-judge cost note.

Flow users will see: three new parameter tooltips (PII, prompt-injection and harmful-content thresholds) and the Finland passport entity, which Flow renders as a raw value today.

prompt_injection keeps Agents' wording as the deliberate exception to the concision rule, because the Noma Security attribution is load-bearing.

Review fixes

The second commit, from a pass over this PR against the #1107/#1138 review threads:

  • Typed the fetch mocks (vi.fn<typeof fetch>). vi.fn(async () => ...) infers a zero-parameter
    mock, so every mock.calls[i]?.[1] assertion in the hook suite was a TS2493/TS2339 under the
    repo's strict config: 10 errors CI cannot see, since tests are excluded from tsc and biome does
    not typecheck. Same class as the two Copilot catches on feat(apollo-wind): metadata-form controlled-host seam, string-list field, InfoTooltip #1107. Checked with a throwaway tsconfig
    that drops the test/story excludes; this PR's files are clean under it.
  • loading starts true when the hook is about to fetch. It started false, so the first
    render of a self-fetching host was { definitions: [], loading: false } and a
    loading ? <Spinner/> : <Empty/> host flashed the empty state.
  • refetch is a no-op while the hook is disabled. It used to issue a real request whose result
    was then discarded in favour of options.definitions.
  • Docs that were stronger than the code: options.definitions is compared by identity, not
    content (pass a stable reference; an SWR or react-query result already is), a failed request keeps
    the previous results, and the zod boundary is a source-level check plus two tests.
  • Named the 0..1 map-enum assumption and pinned it with a test. The third commit then removed
    the assumption outright once feat(apollo-react): guardrails component family under canvas #1138 started enforcing it, per the section above.

Verification

Re-run in full after the 2026-09-14 rebase.

  • tsc --noEmit: clean.
  • Throwaway tsconfig including tests and stories: clean in this PR's files. 12 remaining errors,
    all pre-existing in feat(apollo-react): guardrails component family under canvas #1138's own suites
    (guardrail-builder.test.tsx 7,
    form-schema-builder.test.ts 2, plus one each in guardrail-builder.stories.tsx,
    guardrail-form-layout.test.tsx and guardrail-validator-form.test.tsx). CI typechecks neither
    tests nor stories, so they are invisible to it. Nothing here touches those files.
  • biome check: clean.
  • Guardrails directory: 333 passing, 1 failing.
  • Full package suite: 2974 passing, 9 failing.

The 9 failures are 8 + 1, and none of them are this PR's:

Review questions

  1. BYO connection and folder resolution (plan Q1). It stays host-side here, via withGuardrailFolderMetadata, because resolving it needs each product's connections API (Agents pages fetchResources, Flow calls getConnectionById). Do you want it inside the hook instead, as a resolveConnections callback?
  2. Canonical copy on lingui (plan Q3). Ratifying the move means accepting the 17 divergences above, each of which changes what one product's users see. Happy to split any of them back out if a specific string should stay as it is.

Rebased 2026-09-14 onto #1138's squashed head 9fa10765. The 18 commits that used to sit
below this work are gone, so every commit above has a new sha, and the select.tsx conflict with
main that kept this stack CONFLICTING is resolved upstream: all four PRs in the stack now
merge cleanly, and pull_request workflows should fire for the first time. The content of the
first two commits is unchanged apart from the catalog rebase resolution, which keeps both #1138's
new validation strings and this PR's definitions block in all thirteen files.

Copilot AI lite review requested due to automatic review settings September 9, 2026 12:23
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Sep 14, 2026, 01:36:03 AM
apollo-docs Ready Preview · Logs Sep 14, 2026, 01:36:03 AM
apollo-landing Ready Preview · Logs Sep 14, 2026, 01:36:03 AM
apollo-vertex Ready Preview · Logs Sep 14, 2026, 01:36:03 AM

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Dependency License Review

  • 1937 package(s) scanned
  • ✅ No license issues found
  • ⚠️ 2 package(s) excluded (see details below)
License distribution
License Packages
MIT 1708
ISC 88
Apache-2.0 55
BSD-3-Clause 27
BSD-2-Clause 23
BlueOak-1.0.0 8
MPL-2.0 4
MIT-0 3
CC0-1.0 3
MIT OR Apache-2.0 2
(MIT OR Apache-2.0) 2
Unlicense 2
LGPL-3.0-or-later 1
Python-2.0 1
CC-BY-4.0 1
(MPL-2.0 OR Apache-2.0) 1
Unknown 1
Artistic-2.0 1
(WTFPL OR MIT) 1
(BSD-2-Clause OR MIT OR Apache-2.0) 1
CC-BY-3.0 1
0BSD 1
(MIT OR CC0-1.0) 1
MIT AND ISC 1
Excluded packages
Package Version License Reason
@img/sharp-libvips-linux-x64 1.3.2 LGPL-3.0-or-later LGPL pre-built binary, not linked
khroma 2.1.0 Unknown MIT per GitHub repo, missing license field in package.json

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed behavioral bugs in the new code (unexpected refetch behavior and render-phase state updates) that should be fixed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR adds a shared “guardrail definitions” layer under packages/apollo-react/src/canvas/components/Guardrails/ that parses the /api/execution/guardrails/definitions payload, enriches it with canonical (Lingui-backed) display copy, and exposes a useGuardrailDefinitions hook as the seam between host transport and GuardrailBuilder. It also extends apollo-wind’s metadata forms to support tooltips and a new string-list field type used by guardrail parameter editors.

Changes:

  • Introduces wire types + zod-based non-throwing parsing, pure enrichment, and a useGuardrailDefinitions hook for fetching/composing guardrail definitions.
  • Moves canonical validator copy into the canvas Lingui catalog and adds parity tests against Flow/Agents baselines.
  • Enhances apollo-wind forms/UI with InfoTooltip, string-list field support, and aria-invalid styling for select/textarea.
File summaries
File Description
pnpm-lock.yaml Locks new deps added for guardrails UI and a11y testing.
packages/apollo-wind/src/index.ts Re-exports new forms/types (MetadataFormProps, useWatch, StringListField*) and InfoTooltip.
packages/apollo-wind/src/components/ui/textarea.tsx Adds aria-invalid error styling to textarea.
packages/apollo-wind/src/components/ui/select.tsx Adds aria-invalid error styling to select trigger.
packages/apollo-wind/src/components/ui/info-tooltip.tsx Adds reusable info-icon tooltip component for form labels.
packages/apollo-wind/src/components/ui/info-tooltip.test.tsx Adds a11y + behavior tests for InfoTooltip.
packages/apollo-wind/src/components/ui/index.ts Exports info-tooltip (and reorders a couple exports).
packages/apollo-wind/src/components/forms/validation-converter.ts Extends schema conversion to treat string-list as an array type.
packages/apollo-wind/src/components/forms/string-list-field.tsx Implements the new string-list field editor and formatTemplate helper.
packages/apollo-wind/src/components/forms/metadata-form.stories.tsx Adds story demonstrating string-list + tooltip + controlled-host seam.
packages/apollo-wind/src/components/forms/index.ts Exposes new forms APIs (controlled seam types, string-list exports, useWatch).
packages/apollo-wind/src/components/forms/form-schema.ts Adds tooltip metadata, textarea constraints, multiselect copy overrides, and string-list field metadata/type.
packages/apollo-wind/src/components/forms/field-renderer.tsx Renders required indicator + optional tooltip, wires htmlFor/id, and passes aria-invalid to select/textarea/multiselect.
packages/apollo-react/src/test/setup.ts Registers jest-axe matchers for Vitest suites.
packages/apollo-react/src/i18n/index.ts Exports getPreImportedMessages helper for hosts merging catalogs.
packages/apollo-react/src/canvas/locales/en.json Adds guardrails chrome strings + canonical validator/parameter/option copy (English).
packages/apollo-react/src/canvas/components/index.ts Exports the Guardrails canvas family from the canvas components barrel.
packages/apollo-react/src/canvas/components/Guardrails/utils.ts Adds parameter seeding/sync/validation helpers for guardrail parameters.
packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts Adds useGuardrailDefinitions hook (fetch + parse + enrich + refetch).
packages/apollo-react/src/canvas/components/Guardrails/types.ts Defines guardrail parameter and form prop types for the validator editor surface.
packages/apollo-react/src/canvas/components/Guardrails/render-parameter-bridge.tsx Bridges host renderParameter overrides into MetadataForm custom components via context.
packages/apollo-react/src/canvas/components/Guardrails/index.ts Public exports for the guardrails family, including the new definitions layer APIs.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-validator-form.tsx Implements validator parameter form using MetadataForm + guardrail-owned custom fields.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.tsx Adds shared modal/inline layout wrapper for guardrail builder forms.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.test.tsx Adds behavior + a11y tests for the shared form layout.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.stories.tsx Adds Storybook examples for the shared form layout modes.
packages/apollo-react/src/canvas/components/Guardrails/form-schema-builder.ts Builds MetadataForm schemas for guardrail parameters + coercion helper.
packages/apollo-react/src/canvas/components/Guardrails/form-schema-builder.test.ts Tests schema mapping/coercion rules for guardrail parameter definitions.
packages/apollo-react/src/canvas/components/Guardrails/definitions-wire.ts Adds hand-written wire types for the definitions endpoint payload.
packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.ts Adds zod validation + non-throwing parse result and issue reporting.
packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.test.ts Tests parsing guarantees + zod boundary constraints.
packages/apollo-react/src/canvas/components/Guardrails/definitions-parity.test.ts Ensures canonical English matches Flow/Agents baselines and declares divergences.
packages/apollo-react/src/canvas/components/Guardrails/definitions-enrich.ts Adds pure enrichment (copy resolution + parameter shaping + folder metadata helper).
packages/apollo-react/src/canvas/components/Guardrails/definitions-copy.test.ts Tests copy table, message id conventions, and catalog parity.
packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx Shared parameter label renderer (required marker + info tooltip).
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.tsx Adds banner for mixed-scope guardrails with “save as new” hint.
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.test.tsx Tests mixed-scopes banner rendering + a11y.
packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx Adds map-enum editor bound to sibling enum-list selection via useWatch.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.tsx Adds status banners for disabled/unauthorized/feature-disabled definitions.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.test.tsx Tests status banner roles and a11y.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-scope-selector.tsx Adds scope/tool targeting selector using chips and self-healing behavior.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-scope-selector.test.tsx Tests selector behavior, targeting semantics, and a11y.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.tsx Adds chip toggle component (CVA variants) for scopes/options.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.test.tsx Tests chip pressed state, interactions, and a11y.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.tsx Adds action configuration section (log/block/filter/escalate).
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.test.tsx Tests action section branching + a11y.
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.tsx Adds non-input “field shell” container with error border option.
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.test.tsx Tests field shell invalid styling toggle.
packages/apollo-react/src/canvas/components/Guardrails/components/enum-list-chips-field.tsx Adds chip-based enum-list editor for small option sets.
packages/apollo-react/src/canvas/components/Guardrails/builder-utils.ts Adds builder helpers for defaults and required-field validation.
packages/apollo-react/src/canvas/components/Guardrails/builder-utils.test.ts Tests builder utils behaviors and edge cases.
packages/apollo-react/src/canvas/components/Guardrails/builder-types.ts Adds public structural types for persisted guardrail values and builder slots.
packages/apollo-react/src/canvas/components/Guardrails/fixtures/host-copy-baselines.ts Adds transcribed Flow/Agents English baselines for copy parity tests.
packages/apollo-react/src/canvas/components/Guardrails/fixtures/definitions-wire.fixtures.ts Adds realistic wire fixtures for parsing/enrichment/copy tests.
packages/apollo-react/package.json Exposes ./canvas/guardrails subpath and adds deps (class-variance-authority, jest-axe, @types/jest-axe).
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 81/82 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +47 to +59
const [rowIds, setRowIds] = useState<string[]>(() => items.map(() => crypto.randomUUID()));
const [prevLength, setPrevLength] = useState(items.length);
if (prevLength !== items.length) {
setPrevLength(items.length);
setRowIds((prev) =>
prev.length < items.length
? [
...prev,
...Array.from({ length: items.length - prev.length }, () => crypto.randomUUID()),
]
: prev.slice(0, items.length)
);
}
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage + size by package

Per-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.

Package Coverage New-line coverage Packed (gzip) Unpacked vs main
@uipath/apollo-core 42.01 MB 50.12 MB ±0
@uipath/apollo-react 41.5% 86.1% (706/820) 7.73 MB 29.85 MB +170.8 KB
@uipath/apollo-ui-icons 2.85 MB 6.91 MB ±0
@uipath/apollo-wind 66.8% 94.0% (109/116) 459.8 KB 2.91 MB +8.3 KB
@uipath/ap-chat 85.8% 43.95 MB 56.83 MB +26.6 KB

"Coverage" is each package's own coverage.include scope (e.g. apollo-core instruments only scripts/). "Packed"/"Unpacked" come from npm pack --dry-run and only cover built packages — "—" means not measured this run (package not affected / not built). "vs main" is the packed (gzipped) delta against the last successful main build (the package-sizes artifact from the Release workflow); "—" there means no main baseline was available this run. The baseline is main's latest build, not this PR's exact merge-base, so it includes any drift since the branch diverged. Packages with no vitest config are omitted.

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Storybook visual diff

⚠️ Visual changes detected: 12 changed, 31 added, 1 errored (of 1061 compared, 1017 unchanged). View report

Baseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs

Updated (PT): Sep 14, 2026, 02:04:46 AM

@andreizdrali-uipath
andreizdrali-uipath changed the base branch from main to feat/apollo-react-guardrails-family September 9, 2026 13:04
@apetraru-uipath
apetraru-uipath force-pushed the feat/apollo-react-guardrails-family branch 2 times, most recently from 2ff0c35 to c9d8f20 Compare September 10, 2026 12:51
@apetraru-uipath
apetraru-uipath force-pushed the feat/apollo-react-guardrails-family branch from c9d8f20 to d658731 Compare September 10, 2026 13:09
andreizdrali-uipath added a commit that referenced this pull request Sep 11, 2026
…AL-574]

Review of #1139 against the #1107/#1138 threads (plan/review-2026-09-11 §3.2).

- Type the fetch mocks as `vi.fn<typeof fetch>`: `vi.fn(async () => ...)` infers a
  zero-parameter mock, so every `mock.calls[i]?.[1]` assertion was a TS2493/TS2339
  under the repo's strict config. CI cannot see it (tests are excluded from `tsc`
  and biome does not typecheck), so it is checked with a throwaway tsconfig.
- `loading` starts `true` when the hook is about to fetch, so a host rendering
  `loading ? <Spinner/> : <Empty/>` no longer flashes the empty state on first paint.
- `refetch` is a no-op while the hook is disabled. It used to issue a real request
  whose result `parsed` then discarded in favour of `options.definitions`.
- JSDoc and README: `options.definitions` is compared by identity (pass a stable
  reference), a failed request keeps the previous results, and the zod boundary is
  pinned by a source-level check plus two tests, not by shipped runtime assertions.
- Name the map-enum `0..1` step `0.1` default as a product assumption and pin what
  keeps it safe: after the rebase onto `d658731b`, `min`/`max` are enforced through
  `validation` in `onChange` mode, but both that path and `getOutOfRangeParameterIds`
  are number-only, so a synthesized map-enum bound cannot reject a threshold map
  whose real range is different (harmful content is 0..6).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-definitions-layer branch from 64e1ff4 to 6c9b54a Compare September 11, 2026 08:19
@andreizdrali-uipath andreizdrali-uipath added the dev-packages Adds dev package publishing on pushes to this PR label Sep 11, 2026
@andreizdrali-uipath
andreizdrali-uipath changed the base branch from feat/apollo-react-guardrails-family to main September 11, 2026 08:20
@andreizdrali-uipath andreizdrali-uipath added dev-packages Adds dev package publishing on pushes to this PR and removed dev-packages Adds dev package publishing on pushes to this PR labels Sep 11, 2026
andreizdrali-uipath added a commit that referenced this pull request Sep 11, 2026
Integration branch only: it exists so a host can pin one preview package carrying
every open apollo stream. Not for merging into main.

Rebuilt on 2026-09-11 after the whole stack moved onto #1138's current head
(`d658731b`) and picked up a first pass of review fixes on #1139 and #1140. Reset to
`feat/apollo-react-guardrail-list` and re-merged `feat/apollo-react-guardrail-palette`
(#1147, AL-576).

The chip files both branches carry merged clean, being byte-identical again. Of the 17
conflicts, `i18n.ts`, `i18n.test.ts` and the 13 locale catalogs are unchanged on both
sides since the previous merge (`8856e4e0`), so its resolution was reused verbatim.
`index.ts` is the union of both barrels, biome-sorted, and checked for a lost export.
The README was rebuilt from `8856e4e0`'s merged copy with the three deltas since then
reapplied (the new base's, #1139's review fixes, #1140's review fixes), all cleanly, so
the sections still run along the data flow: definitions layer, list, palette, builder.

Verified after the merge: Guardrails suite 453 passing (26 files), tsc and biome clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
andreizdrali-uipath added a commit that referenced this pull request Sep 11, 2026
…AL-574]

Review of #1139 against the #1107/#1138 threads (plan/review-2026-09-11 §3.2).

- Type the fetch mocks as `vi.fn<typeof fetch>`: `vi.fn(async () => ...)` infers a
  zero-parameter mock, so every `mock.calls[i]?.[1]` assertion was a TS2493/TS2339
  under the repo's strict config. CI cannot see it (tests are excluded from `tsc`
  and biome does not typecheck), so it is checked with a throwaway tsconfig.
- `loading` starts `true` when the hook is about to fetch, so a host rendering
  `loading ? <Spinner/> : <Empty/>` no longer flashes the empty state on first paint.
- `refetch` is a no-op while the hook is disabled. It used to issue a real request
  whose result `parsed` then discarded in favour of `options.definitions`.
- JSDoc and README: `options.definitions` is compared by identity (pass a stable
  reference), a failed request keeps the previous results, and the zod boundary is
  pinned by a source-level check plus two tests, not by shipped runtime assertions.
- Name the map-enum `0..1` step `0.1` default as a product assumption and pin what
  keeps it safe: after the rebase onto `d658731b`, `min`/`max` are enforced through
  `validation` in `onChange` mode, but both that path and `getOutOfRangeParameterIds`
  are number-only, so a synthesized map-enum bound cannot reject a threshold map
  whose real range is different (harmful content is 0..6).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 11, 2026 12:41
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-definitions-layer branch from 6c9b54a to 74a4be4 Compare September 11, 2026 12:41
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

📦 Dev Packages

Package Status Updated (PT)
@uipath/apollo-react@6.45.0-pr1139.5728c82 🟢 Published Sep 14, 2026, 01:32:44 AM
@uipath/apollo-wind@2.49.0-pr1139.5728c82 🟢 Published Sep 14, 2026, 01:31:39 AM

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved review findings include a critical stale-data risk and multiple parser, validation, synchronization, and accessibility defects.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (9)

packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx:73

  • This custom numeric input is the control that renders map-enum validation errors, but it never receives aria-invalid. When error is present, the visible FormFieldError is not accompanied by the invalid state on the input, so assistive technology can miss which control is affected. Pass the error state through as aria-invalid.
            <Input
              aria-label={`${paramDef.label}: ${sourceDef?.optionLabels?.[key] ?? key}`}
              type="number"
              value={currentMap[key] ?? defaults[key] ?? paramDef.min ?? 0}
              onChange={(e) => handleThresholdChange(key, Number.parseFloat(e.target.value) || 0)}

packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx:58

  • getRequiredEmptyParameterIds marks a required map-enum with no keys as invalid, and the builder passes that message through error, but this early return removes the whole FormField and its FormFieldError. Clicking Save can therefore block a required empty map-enum with no visible error. Keep a label/error shell when the source has no keys, or suppress validation for this dependent field until its source has a selection.
  if (keys.length === 0) return null;

packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx:25

  • InfoTooltip renders a real <button>, so putting it inside Label nests a labelable control inside a label. Clicking the tooltip can activate the associated input and exposes ambiguous label semantics to assistive technology. Render the tooltip as a sibling of the label, as FormFieldLabel does.
      {paramDef.tooltip && (
        <InfoTooltip content={paramDef.tooltip} aria-label={labels.moreInformation} />
      )}

packages/apollo-react/src/canvas/components/Guardrails/definitions-enrich.ts:145

  • copy is a normal object, so an otherwise-unrecognised wire validator such as toString resolves to an inherited Object.prototype member instead of undefined. With any parameter, toParameterDefinition then reads curated.paramLabels[param.id] from that function and enrichment throws, even though the parser accepted the definition. Check that the validator is an own key before using it as curated copy so unknown wire values remain renderable.
  const curated = isByo ? undefined : copy[wire.validator];

packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.ts:170

  • The parser's contract says malformed entries never escape as exceptions, but readValidator(entry) is called after the safeParse try/catch. A hostile object or proxy with a throwing validator getter will therefore throw while constructing the parse issue instead of being reported in invalid. Make this helper catch property-access failures (returning undefined) before it is used for an invalid entry.
    packages/apollo-react/src/canvas/components/Guardrails/guardrail-builder.tsx:415
  • The host-owned saveDisabled gate is applied to the primary button at line 428, but not to the secondary Save as new action here. When saveDisabled is true (for example while a host slot is resolving), Save as new remains enabled and handleSaveAsNew can still invoke onSaveAsNew, bypassing the documented host save gate. Include hostSaveDisabled in this action's disabled condition.
    packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts:72
  • Object.is only provides reference equality, so a controlled host that echoes an equivalent cloned array or map treats it as changed on every render. The effect then calls setValue repeatedly despite the hook's deep-equality contract, causing unnecessary React Hook Form updates and potentially disturbing focused list/map editors. Compare structured values before calling setValue (or normalize them to stable references).
    packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts:74
  • The controlled-value sync only iterates keys present in values; it never clears a field that the host removes from parameters. After a host drops an optional parameter (for example when normalizing empty values), the old array/map/text value remains in React Hook Form and is still shown, so the UI no longer reflects the controlled source and a later edit can re-submit stale data. Reconcile removed fields to their current defaults/empty state as well as syncing present keys.
    packages/apollo-wind/src/components/forms/validation-converter.ts:59
  • Because this condition skips the refinement whenever minLength is configured, a required string with minLength: 3 accepts ' ' after satisfying the length rule. That contradicts the shared isEmptyFieldValue semantics described immediately above and lets whitespace-only required values through; the refinement should be applied regardless of minLength.
  • Files reviewed: 93/94 changed files
  • Comments generated: 4
  • Review effort level: Lite

Comment on lines +182 to +193
useEffect(() => {
if (!enabled) {
// Disabled: abort anything in flight and drop previous results, so a host that
// switches to its own payload never renders stale data or a stuck spinner.
abortRef.current?.abort();
setFetched(EMPTY_RESULT);
setLoading(false);
setError(null);
return undefined;
}
load();
return () => abortRef.current?.abort();
Comment on lines +118 to +120
// `.min(1)` rather than `emptyToUndefined`: an empty string here would make a UiPath
// validator read as bring-your-own, which changes copy resolution and palette grouping.
byoValidatorName: z.string().min(1).optional(),
Comment thread packages/apollo-react/src/canvas/components/Guardrails/guardrail-builder.tsx Outdated
Comment on lines +128 to +131
const [fetched, setFetched] = useState<GuardrailDefinitionsParseResult>(EMPTY_RESULT);
// `true` on the first render of an enabled hook: the effect below is about to fetch, and a
// host that renders `loading ? <Spinner/> : <Empty/>` would otherwise flash the empty state.
const [loading, setLoading] = useState(enabled);
apetraru-uipath and others added 8 commits September 13, 2026 22:52
…airs

Generic forms/ and ui/ enablers extracted from the guardrails work. The
guardrails domain family itself lives in apollo-react (#1138, stacked on this).

Squashed from 20 commits at review request: most were iterations on each other,
and two cancelled out entirely (the controlled-host seam was added and then
removed during review), which would otherwise have cut a major release for
props that no release ever shipped.

New in the forms layer:

- `string-list` field type — repeated rows with Add/Remove (`maxItems`,
  `maxLength`, stable row ids), zod conversion, and `formatTemplate`. Not yet
  offered by FormDesigner; the reason is documented at FIELD_TYPE_METADATA.
- `tooltip` / `tooltipAriaLabel` field metadata rendered by `FormFieldLabel`,
  plus textarea `minRows`/`maxLength` and multiselect `emptyMessage` /
  `searchPlaceholder`.
- `container: 'form' | 'div'` for embedding inside a host's own chrome: submit
  actions become plain buttons wired to the form's handler, and Enter is
  swallowed for single-line inputs so it cannot trigger the host form's
  implicit submission.
- `InfoTooltip` promoted into `components/ui`, with `FormFieldLabel` owning the
  composition so call sites stop reassembling label + indicator + tooltip.
- `MetadataFormProps`, `useWatch` and `CustomValueType` exported, so cross-package custom
  fields share one react-hook-form instance and can declare their value shape.

Repaired — declared in the schema contract but never implemented:

- `ValidationConfig.custom` was typed, documented and serialized, but the
  converter never read it. Now enforced. `RulesEngine.tryEvaluateExpression`
  distinguishes "the evaluator threw" from "the expression returned falsey", so
  an expression it cannot handle is not enforced rather than pinning the field
  permanently invalid. Its scope is documented as single-field; cross-field
  logic belongs in `rules`.
- `FormPlugin.components` was typed and never read, which is why hosts
  registered asynchronously and missed the first paint.
- `plugin.onValueChange` sat behind a mount-lifetime gate that could swallow a
  plugin's first keystroke.
- Custom fields validated as `z.any()`, where `required` and `minItems` are
  no-ops; they can now declare a `valueType`.

Correctness and accessibility:

- A required string field rejected `'   '` on one path and accepted it on the
  other. Both now use `isEmptyFieldValue`, as a `.refine` rather than
  `.trim().min(1)`, which would mutate the submitted value. Behaviour change
  for anyone who relied on whitespace satisfying `required`, called out in the
  PR description.
- `required` was defeated by an explicit `minItems: 0`.
- `aria-invalid` is forwarded by every control that can render an error, with
  matching invalid styling on Select and Textarea.
- Label/control association (`htmlFor` + `id`) across the renderer.
- The schema serializer carries the new field metadata, so a round-trip no
  longer drops it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Guardrails UI, shared by Flow and Agents, as an MUI-free family under
`src/canvas/components/Guardrails` and exported through the narrow
`@uipath/apollo-react/canvas/guardrails` subpath. Members: `GuardrailBuilder`
(the whole Add/Edit screen), `GuardrailFormLayout` (the screen shell), and
`GuardrailValidatorForm` (the validator parameter section).

Built on apollo-wind's forms/ MetadataForm stack rather than its own renderer:
five of the seven parameter types map onto first-class field types, while the
chip-style enum-list, `map-enum` and host `renderParameter` overrides register
as custom components. Strings localize through lingui (`guardrails.*` ids,
14 catalogs).

Squashed from 7 commits at review request, matching #1107.

MetadataForm owns its own state, so this family's controlled contract is
translated onto its plugin seam in exactly one named place,
`useMetadataFormBridge`: it registers the custom components from the first
paint, pushes host values in structurally compared (so an echo of the form's
own emission performs no write and focus survives), pushes host errors in as
`type: 'external'`, and suppresses its own echo while writing. A sync arriving
before `onFormInit` is replayed rather than dropped.

Validation is shared, and the split is deliberate: the schema declares
`required`/`min`/`max` from the definitions with messages from the label
catalog, so they translate; the host owns domain rules and the save-time gate
through `getRequiredEmptyParameterIds` / `getOutOfRangeParameterIds`. Where the
two disagree the host's verdict is what renders — a `text-list` of
whitespace-only rows passes the array's `.min(1)` but counts as empty for the
host predicate — and a test pins that rather than leaving it to whichever ran
last. Custom fields declare a `valueType` so those constraints bind to them
too; `map-enum` has no counterpart shape, so its required check stays the
host's alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ilDefinitions

Adds the definitions layer the guardrails family was missing: the seam
between the `/api/execution/guardrails/definitions` payload and the
`GuardrailDefinition`s `GuardrailBuilder` renders. Flow and Agents each
carry their own copy of this today, and the two have drifted.

- `definitions-wire.ts` mirrors the payload as hand-written types, reusing
  `GuardrailScope` and `GuardrailDefinitionStatus` from `builder-types` so
  the wire and display layers cannot diverge. It admits both products'
  nullability variants.
- `definitions-parse.ts` validates unknown input and never throws: a
  non-array payload sets `inputError`, a bad definition is dropped whole
  and reported in `invalid`, unknown keys are stripped. zod is private to
  this module, pinned to the public mirror by a bidirectional assignability
  check on the hot path, a runtime key-set assertion and a source-level
  guard, so the folder's emitted declarations carry no schema types.
- `definitions-copy.ts` holds the canonical copy for the six built-in
  validators as 63 lingui messages in the shared canvas catalog, keyed by
  raw wire values. Translations harvested from both products, 62 of 63 in
  each of the 12 locales.
- `definitions-enrich.ts` resolves that copy onto the wire shape. Pure and
  React-free; `EnrichedGuardrailDefinition extends GuardrailDefinition`, so
  its output feeds the builder with no mapping.
- `useGuardrailDefinitions` composes the three over `useState` + `fetch` +
  `AbortController`, following `useDiscoveryModels`. `options.definitions`
  skips the request entirely, which is how each product keeps its own
  transport. Unlike `useDiscoveryModels` the context is compared by content,
  not identity: keying the effect off identity made an inline context object
  refetch on every render without terminating.

Where the products' English differed, all 17 choices are declared with a
reason in `definitions-parity.test.ts` and asserted against both products'
transcribed copy, so the shared table cannot quietly drift from the tables
it replaces.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…AL-574]

Review of #1139 against the #1107/#1138 threads (plan/review-2026-09-11 §3.2).

- Type the fetch mocks as `vi.fn<typeof fetch>`: `vi.fn(async () => ...)` infers a
  zero-parameter mock, so every `mock.calls[i]?.[1]` assertion was a TS2493/TS2339
  under the repo's strict config. CI cannot see it (tests are excluded from `tsc`
  and biome does not typecheck), so it is checked with a throwaway tsconfig.
- `loading` starts `true` when the hook is about to fetch, so a host rendering
  `loading ? <Spinner/> : <Empty/>` no longer flashes the empty state on first paint.
- `refetch` is a no-op while the hook is disabled. It used to issue a real request
  whose result `parsed` then discarded in favour of `options.definitions`.
- JSDoc and README: `options.definitions` is compared by identity (pass a stable
  reference), a failed request keeps the previous results, and the zod boundary is
  pinned by a source-level check plus two tests, not by shipped runtime assertions.
- Name the map-enum `0..1` step `0.1` default as a product assumption and pin what
  keeps it safe: after the rebase onto `d658731b`, `min`/`max` are enforced through
  `validation` in `onChange` mode, but both that path and `getOutOfRangeParameterIds`
  are number-only, so a synthesized map-enum bound cannot reject a threshold map
  whose real range is different (harmful content is 0..6).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… enforces [AL-574]

`getOutOfRangeParameterIds` used to look at `number` parameters only, so the 0..1
step 0.1 this layer synthesized for an unbounded threshold map was an editor hint
nothing could reject a value against. The guardrails family has since widened that
check to `map-enum` and hosts gate Save on it, which turns a bound nobody stated
into a blocked save on a scale the backend never published.

Pass the wire's `min`/`max` through when it sends them and leave them off when it
does not, so only real constraints reach the check. `step` stays: neither the check
nor `buildFieldValidation` reads it, and without it a 0..1 score steps by 1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Promote the read-only status chip and the chip geometry constant it reuses.

Copied verbatim from the AL-575 list branch (#1140), which introduced them: the
palette needs the same chip for an unauthorized definition, and both PRs branch
off the definitions layer rather than stacking, so each carries the shared files
and whichever merges second drops its duplicate on rebase. Keep the two copies
byte-identical.
… with #1140

wind's `Badge` renders a `<div>`, and the palette entry puts these chips inside
its `<button>`, where flow content is invalid. Compose from wind's exported
`badgeVariants` on a `<span>` instead: same classes, an element that may live
there, `ComponentPropsWithoutRef<'span'>` and `HTMLSpanElement` on the ref.

The chip is byte-identical on #1140 and #1147, so this commit lands on both.
…[AL-574]

Every guardrails component's i18n test re-implements the same three checks over its
own id prefix: English parity, orphaned ids, and translation coverage. The catalog
is hand-authored and harvested by a one-off script, so these scans stand in for
`lingui extract` and a translation pipeline, and each new component copy-pasted
them.

They move to `__fixtures__/catalog-coverage` as reporting functions, so a failure
still points at the calling test's own line. The definitions layer is the first
caller and gains what it was missing: the orphan sweep now covers all thirteen
catalogs rather than English alone, and a coverage check that pins the one
harvested-English-only entity label instead of leaving the gap invisible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 14, 2026 08:24
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-definitions-layer branch from 74a4be4 to 9fa041b Compare September 14, 2026 08:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved moderate findings remain across parsing, hook behavior, form synchronization, accessibility, renderer identity, and validation.

Get a fresh assessment by requesting another Copilot review.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (9)

packages/apollo-react/src/canvas/components/Guardrails/fixtures/definitions-wire.fixtures.ts:52

  • This fixture says enrichment supplies a 0..1 bound, but definitions-enrich.ts intentionally leaves an unbounded map without min/max and only supplies the editor step hint. The stale comment contradicts the save-time validation contract documented by the new tests and can lead hosts to assume an invented range is enforced; describe the value as unbounded with a step hint instead.
      // The backend omits the bounds here; enrichment supplies 0..1 step 0.1.

packages/apollo-react/src/canvas/components/Guardrails/components/escalate-action-fields.tsx:182

  • In the built-in static-recipient fallback, this Label is a sibling with no htmlFor, while the fallback Input below has neither an id nor an accessible label. Screen readers therefore cannot associate “Email address”/“Group name” with the required value control (and the searchable fallback has the same gap). Give the built-in inputs stable ids and associate the label, while keeping custom render slots responsible for their own controls.
      {/* Recipient value */}
      <FormField>
        <Label>
          {recipientTypeLabels[displayedRecipientType] ?? labels.recipientFallbackLabel}
          <RequiredIndicator />
        </Label>

packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.ts:95

  • The shared seeding contract explicitly handles defaultValue: null for boolean parameters, but this schema rejects such a payload before enrichment. A valid definition with a nullable boolean default is therefore dropped as invalid, so the nullability support is incomplete; accept the nullable form here and update the hand-written wire mirror to match.
    packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.ts:120
  • Using .min(1).optional() here rejects the entire definition when the backend sends byoValidatorName: ''; it does not normalize the empty marker to an absent value as the parser documentation claims. That removes an otherwise valid non-BYO validator from definitions instead of preserving it. Use the existing emptyToUndefined schema here and assert that the parsed result still contains the definition.
    packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:126
  • provided === undefined cannot distinguish an omitted override from an explicitly supplied but not-yet-loaded payload. A normal SWR/React Query call such as useGuardrailDefinitions(ctx, { definitions: data }) has data === undefined on its first render, so this hook starts its own request, violating the documented no-fetch override path and potentially issuing duplicate requests. Track whether the definitions key is present and use that presence flag for both enabled and the parsed-result branch.
    packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:131
  • useState(enabled) is evaluated only on the initial mount. If the hook starts disabled and later receives a fetch context, or if a loaded hook switches to a new request context, loading is still false for the render before the effect calls setLoading(true). This violates the documented loading contract and lets hosts flash an empty state or render stale results as settled; derive/reset loading for each new enabled request rather than only initializing it once.
    packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts:90
  • Controlled external updates use setValue without requesting validation, so an earlier resolver error can remain in RHF after the host replaces an invalid value with a valid one. Since this bridge explicitly runs the form in onChange mode, validate the field when syncing a changed value (or explicitly clear/recompute its error).
    packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts:145
  • setError(..., { type: 'external' }) on a registered field is removed by RHF's resolver when a later validation passes, but this effect only re-applies the message when the errors prop changes. If a caller keeps an error prop unchanged (for example, it does not provide onClearError), editing the field makes the host error disappear despite the documented "cleared only when the prop drops" contract. Re-apply host errors after validation/value changes or use an error overlay that the resolver cannot replace.
    packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts:90
  • The sync loop only visits keys present in next, so removing a parameter from the controlled parameters array leaves its previous value registered in RHF and still visible. This violates the controlled contract for external updates; track the previous field keys and clear/reset fields that disappear (using the schema/default value) rather than treating omission as no-op.
  • Files reviewed: 99/100 changed files
  • Comments generated: 6
  • Review effort level: Lite

Comment on lines +69 to +78
<Input
aria-label={`${paramDef.label}: ${sourceDef?.optionLabels?.[key] ?? key}`}
type="number"
value={currentMap[key] ?? defaults[key] ?? paramDef.min ?? 0}
onChange={(e) => handleThresholdChange(key, Number.parseFloat(e.target.value) || 0)}
min={paramDef.min}
max={paramDef.max}
step={paramDef.step}
className="flex-1"
/>
Comment on lines +19 to +35
const content = (
<>
{paramDef.label}
{paramDef.required && <RequiredIndicator />}
{paramDef.tooltip && (
<InfoTooltip content={paramDef.tooltip} aria-label={labels.moreInformation} />
)}
</>
);
if (asTextHeader) {
return (
<div data-slot="guardrail-parameter-label" className="text-xs font-medium text-foreground">
{content}
</div>
);
}
return <Label htmlFor={htmlFor}>{content}</Label>;
Comment on lines +166 to +170
function readValidator(entry: unknown): string | undefined {
if (typeof entry !== 'object' || entry === null) return undefined;
const validator = (entry as { validator?: unknown }).validator;
return typeof validator === 'string' && validator !== '' ? validator : undefined;
}
Comment on lines +31 to 35
const getApolloMessageRenderers = (locale: SupportedLocale) => [
{
name: DEFAULT_MESSAGE_RENDERER,
component: AutopilotChatMarkdownRenderer,
},
Comment on lines 48 to +49
schema = applyNumberConstraints(schema, config, fieldType);
schema = applyArrayConstraints(schema, config, fieldType);
schema = applyArrayConstraints(schema, config, fieldType, customValueType);
Comment on lines +104 to +108
// Bounds only when the backend states them. This layer used to invent 0..1 for the
// threshold maps that arrive unbounded, which was safe while the bound was an editor
// hint nothing enforced. #1138's `getOutOfRangeParameterIds` now range-checks
// `map-enum` rows and hosts gate Save on it, so an invented bound would reject a
// threshold on a scale the backend never stated. It is enforcement, so the numbers
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dev-packages Adds dev package publishing on pushes to this PR pkg:apollo-react pkg:apollo-wind size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants