Skip to content

feat(apollo-react): guardrail action and escalation section [AL-579] - #1172

Draft
andreizdrali-uipath wants to merge 2 commits into
mainfrom
feat/apollo-react-guardrail-action-section
Draft

andreizdrali-uipath wants to merge 2 commits into
mainfrom
feat/apollo-react-guardrail-action-section

Conversation

@andreizdrali-uipath

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

Copy link
Copy Markdown

AL-579, epic AL-526. Review the last commit; the one before it is PR #1139, which this branches off.

What

GuardrailActionSection and EscalateActionFields ship inside GuardrailBuilder but were never exported, so the only way to edit a guardrail action was the whole OOTB screen. The custom-rules builder edits the action on its own in both products (Agents' GuardrailActionBuilder + GuardrailEscalationAssetRecipient, Flow's GuardrailActionSection + EscalateActionBuilder), so both are exported here with the props a standalone caller needs.

Export What it is
GuardrailActionSection / GuardrailActionSectionProps action-type select plus the field that type needs
EscalateActionFields / EscalateActionFieldsProps the escalation layout: recipient type, recipient, action app
GuardrailActionErrors, GuardrailEscalateActionErrors the action slice of GuardrailBuilderErrors
GuardrailEscalateAction the escalate arm of GuardrailAction
GuardrailActionLabels, GuardrailActionLabelKey, GUARDRAIL_ACTION_EN_LABELS, GUARDRAIL_ACTION_LABEL_KEYS, resolveGuardrailActionLabels, useGuardrailActionLabels the label block

Standalone the section needs action and onActionChange; everything else is optional, so nothing changes for the builder:

  • labels is optional and partial. GuardrailActionLabels is a Pick over the builder's own keys and resolves the same guardrails.builder.* ids, so there are no new strings and neither path can word one differently. The builder keeps passing its full labels object.
  • EscalateActionFields takes actionTypeSelect as optional and gains asGridItems, so a host can lay the three cells out in its own grid or stack them. Flow's own component has both modes. With actionTypeSelect given, which is what the section passes, the markup is unchanged.
  • Typed errors. The inline error literals become GuardrailActionErrors / GuardrailEscalateActionErrors, and GuardrailEscalateAction names the escalate arm for a host holding one in state.
  • className on both.

Contract unchanged: the escalation target stays slot-driven (renderRecipientSearch, renderStaticRecipient, renderAppPicker, escalateHelp), filter stays host territory through showFilter / filterContent, callbacks are intents, and no flags, telemetry, product types or zod cross the boundary.

Two fixes in passing, both on the escalation fields:

  • The recipient field's <label> now points at the input it names, and a slot gets ctx.labelId to name its own control. Neither fallback input had an accessible name (#1138 thread), and it matters more here because the fallbacks are what a standalone caller gets.
  • mt-0 on the app-picker note, whose AlertDescription offset assumes a title above it. Same one-line workaround as the family's other title-less alerts, pending the alertVariants fix.

Plus a README section, stories under Components/UiPath/Guardrail Action Section with mock slot renderers for the escalate variants, and both suites extended for the standalone defaults.

Open questions

Props both hosts' action builders have and this does not. Flagged rather than guessed at.

  1. No disabled. Agents disables the Assign-to select when group assignment is off; Flow disables its whole editor while deliberately keeping the drag handle live. A plain disabled?: boolean with the flag staying host-side, a narrower recipientTypeDisabled?, or leave it to a host fieldset?
  2. No section header. Agents renders an "Action" heading with an info tooltip and wraps the section in role="group" aria-labelledby. This ships content only. Host chrome, as with the centralized section's hideHeader, or an opt-in header pair here?
  3. escalateHelp is static. On an app error Agents swaps the marketplace line for a different one, so a host has to condition the node on the same errors.actionApp it passes in. Enough, or should the slot see the error state?
  4. A slot control with no error prop of its own. Flow's DirectorySearchInput has none, so its slot returns control + FormFieldError as a fragment, which lands outside the FormField anatomy (raised on flow-workbench#4025). The README now states the rule; a ctx.errorNode would make it structural instead.

Smaller, same reasoning: Flow gates error display with a showErrors boolean where a host here withholds errors instead; Agents lays the escalate row out at 20/20/25/rest against this 2-column grid; and six copy divergences reach Agents only, inherited from the family's wording ("Blocking reason" for "Block reason", "Assign to" for "Assign app to", "Action App" for "Action app", "Email address" / "Group name" for their capitalized twins, and the recipient label taking the type's name where Agents always says "Recipient"). Flow's wording is unchanged throughout.

Verification

  • Guardrails suite 403 passing / 19 files, jest-axe on both rendering suites including the stacked layout.
  • tsc -p tsconfig.json clean, and a strict pass over the folder's tests and stories reports nothing in this commit's files.
  • biome lint / format clean on them, rslib build clean, no zod in the emitted .d.ts, no lockfile change.
  • Preview: apollo-react only, no wind build, now that feat(apollo-wind): string-list field, tooltip metadata, and forms repairs #1107 is merged and the release carries it. The version is on the dev-packages comment below and is named after refs/pull/1172/merge, not the head sha. The host PRs that pinned 6.45.2-pr1172.2f2cc67 (flow-workbench#4025, Agents#6278) need a re-pin, since dev-publish unpublishes the superseded build.

🤖 Generated with Claude Code

@andreizdrali-uipath andreizdrali-uipath added the dev-packages Adds dev package publishing on pushes to this PR label Sep 15, 2026
Copilot AI lite review requested due to automatic review settings September 15, 2026 08:30

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

Two moderate issues remain unresolved: fallback actionApp errors are not rendered, and the escalate path lacks the section root hook.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Promotes guardrail action and escalation UI to standalone public APIs for custom-rules builders.

Changes:

  • Adds public action/escalation components, types, labels, exports, layouts, and slots.
  • Updates supporting form infrastructure, localization, tests, stories, and documentation.
  • Applies the app-picker alert spacing fix.
  • Review follow-ups: Two moderate findings (2 votes each): render actionApp errors when no picker is provided, and preserve the section root hook on the escalate path.
File summaries
File Review note
pnpm-lock.yaml Reviewed; no final comment.
packages/apollo-wind/src/index.ts Reviewed; no final comment.
packages/apollo-wind/src/components/ui/textarea.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/select.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/info-tooltip.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/info-tooltip.test.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/info-tooltip.stories.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/index.ts Reviewed; no final comment.
packages/apollo-wind/src/components/ui/form-field.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/form-field.test.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/ui/datetime-picker.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/forms/validation-converter.test.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/string-list-field.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/forms/schema-serializer.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/schema-serializer.test.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/rules-engine.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/README.md Reviewed; no final comment.
packages/apollo-wind/src/components/forms/metadata-form.stories.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/forms/index.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/form-schema.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/form-designer.tsx Reviewed; no final comment.
packages/apollo-wind/src/components/forms/demo-mocks.ts Reviewed; no final comment.
packages/apollo-wind/src/components/forms/custom-controls.stories.tsx Reviewed; no final comment.
packages/apollo-react/src/test/setup.ts Reviewed; no final comment.
packages/apollo-react/src/i18n/index.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/locales/ru.json Reviewed; no final comment.
packages/apollo-react/src/canvas/components/index.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/utils.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/use-metadata-form-bridge.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/types.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/render-parameter-bridge.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/index.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-validator-form.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-form-layout.stories.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/definitions-wire.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/definitions-parse.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/definitions-parity.test.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/definitions-copy.test.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/parameter-label.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/mixed-scopes-banner.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/map-enum-field.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-chip.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-chip.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-status-banner.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-scope-selector.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-scope-selector.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-chip.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.tsx Moderate (2 votes): restore the section root hook on the escalate path.
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/field-shell.test.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/components/enum-list-chips-field.tsx Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/builder-utils.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/builder-types.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/host-copy-baselines.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/definitions-wire.fixtures.ts Reviewed; no final comment.
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/catalog-coverage.ts Reviewed; no final comment.
packages/apollo-react/package.json Reviewed; no final comment.
Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file
  • Files reviewed: 99/100 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 +99 to +104
if (action.$actionType === 'escalate') {
return (
<EscalateActionFields
action={action}
onChange={onActionChange}
actionTypeSelect={actionTypeSelect}
Copilot AI review requested due to automatic review settings September 15, 2026 10:56
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-action-section branch from 84ef308 to c07d3a9 Compare September 15, 2026 10:56
@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Apollo Coded App preview deployments are ready.

Project Status Preview Updated (PT)
apollo-design Ready Preview · Logs Sep 18, 2026, 02:53:23 AM
apollo-docs Ready Preview · Logs Sep 18, 2026, 02:53:23 AM
apollo-landing Ready Preview · Logs Sep 18, 2026, 02:53:23 AM
apollo-vertex Ready Preview · Logs Sep 18, 2026, 02:53:23 AM

@github-actions

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

@github-actions

github-actions Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

📦 Dev Packages

Package Status Updated (PT)
@uipath/apollo-react@6.48.0-pr1172.0cbd32a 🟢 Published Sep 18, 2026, 02:56:24 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.

🔵 Needs a closer look

The two unresolved review issues must be addressed before approval.

Review details

Files not reviewed (1)

  • pnpm-lock.yaml: Generated file

Suppressed comments (2)

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

  • The recipient-value fallbacks still render a visible Label without htmlFor, while the Inputs below have no id; because they are siblings, the standalone escalation fields give the recipient control no accessible name even though their validation message is associated. Add a generated label/control association for both fallback paths, and provide an equivalent accessible-label seam for replacement slots.
        {/* One rule across all three slots, matching `renderAppPicker`: a slot receives `error`
            and owns rendering it, so the form renders no message of its own for a claimed field.
            The sibling `FormFieldError` used to sit outside this ternary, so a host doing the
            obvious `<Input error={ctx.error} />` got the message twice. Every fallback goes
            through `Input`'s `error` prop, which renders the message *and* wires

packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-action-section.tsx:119

  • This slot is only rendered for non-escalation actions: the escalate branch above returns EscalateActionFields, whose root is data-slot="guardrail-escalate-fields". Consumers using the newly added section slot to style or locate GuardrailActionSection will therefore fail whenever the action is escalate; keep the section slot on the root for both branches.
    <div data-slot="guardrail-action-section" className={cn('@container', className)}>
  • Files reviewed: 73/74 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@github-actions

github-actions Bot commented Sep 15, 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.16 MB ±0
@uipath/apollo-react 45.4% 79.6% (257/323) 7.70 MB 29.82 MB +33.1 KB
@uipath/apollo-ui-icons 2.86 MB 6.96 MB ±0
@uipath/apollo-wind 468.9 KB 2.96 MB +1 B
@uipath/ap-chat 85.8% 43.94 MB 56.79 MB +1.4 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 15, 2026

Copy link
Copy Markdown
Contributor

Storybook visual diff

⚠️ Visual changes detected: 8 changed, 15 added (of 415 compared, 392 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 18, 2026, 03:08:27 AM

…ilDefinitions [AL-574]

Turns the `GET /api/execution/guardrails/definitions` payload into 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 by hand, admitting both products'
  nullability variants, and reuses `GuardrailScope`/`GuardrailDefinitionStatus`
  so wire and display cannot drift.
- `definitions-parse.ts` validates with zod and never throws: a non-array sets
  `inputError`, one bad definition is dropped whole into `invalid`. Blank
  display strings are the single normalization, since one would beat curated
  copy and render an empty label; blank identifiers fail the entry instead of
  silently changing a definition's identity. zod stays private, pinned to the
  hand-written mirror by a bidirectional assignability check on the hot path
  plus a key-set test and a source-level import guard, so no schema type
  reaches the emitted `.d.ts`.
- `definitions-copy.ts` carries the six built-in validators' display copy as 63
  lingui messages in the shared canvas catalog, replacing Agents'
  `OOB_GUARDRAILS_I8N` and Flow's `buildValidatorDisplayInfo`. Ids use raw wire
  values, never a transcribed slug, which is how the two products ended up
  keying the same entity as `finNationalId` and `fiNationalId`. English only:
  the l10n sync owns the other catalogs, as it does for every other string here.
- `definitions-enrich.ts` resolves copy onto validated wire definitions. Pure
  and React-free, so Flow's vsix bridge calls it directly. Curated wins at
  definition level, wire wins at parameter level, BYO takes no curated copy.
- `use-guardrail-definitions.ts` composes the three. `options.definitions`
  skips the request entirely, so each product keeps its own transport. The
  context is compared by content rather than identity, unlike
  `useDiscoveryModels`, where an inline object refetches every render and never
  settles. Results carry the request key that produced them, so a tenant switch
  cannot keep serving the previous tenant's guardrails.

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

Also lands two things the leaf PRs were each carrying their own copy of, since
#1140, #1147 and #1161 all branch from here. `GuardrailStatusChip` is a
read-only pill for a guardrail row: deliberately not `GuardrailChip`, which
wraps a Radix `Toggle` and would put fake buttons in the tab order, and a
`<span>` composed from wind's `badgeVariants` rather than `Badge`, which renders
a `<div>` that is invalid inside the palette entry's `<button>`.
`GUARDRAIL_CHIP_GEOMETRY` is extracted so the interactive and read-only pills
stay one system. Its tones are harvested, not invented: `success` is the green
both products give a BYO origin or connector chip, `info` the blue both give a
"Preview" lifecycle label (Flow `bg-blue-100 text-blue-700`, Agents
`semantic.colorInfoBackground`), so neither product's colour changes when the
leaves adopt it. The label truncates with the full text on the chip's `title`,
because the pill is a fixed height and the text is the host's: a governance
label or a connector name long enough to wrap rendered two lines and spilled
out of its own background. The label sits in an inner span so it can truncate
at all, since `text-overflow` does not reach the anonymous flex item bare text
becomes inside `inline-flex`. `__fixtures__/catalog-coverage.ts` holds the catalog scans that
stand in for `lingui extract`, which never sees `src/canvas` because it uses no
macros.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 18, 2026 09:38
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-action-section branch from c07d3a9 to 1eea16e Compare September 18, 2026 09:38
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-action-section branch from 1eea16e to 19b7570 Compare September 18, 2026 09:40
…n [AL-579]

`GuardrailActionSection` and `EscalateActionFields` have shipped inside
`GuardrailBuilder` since the family landed, but only the builder could reach
them. Both hosts need them on their own for the custom-rules builder, where the
action is edited outside the OOTB screen.

The components are unchanged for the builder; what this adds is the standalone
surface:

- `labels` is optional and partial. `GuardrailActionLabels` is a `Pick` over the
  builder's own keys and resolves the same `guardrails.builder.*` ids, so there
  are no new strings to translate and neither path can word one differently.
- `GuardrailActionErrors` / `GuardrailEscalateActionErrors` replace the inline
  error literals, and `GuardrailEscalateAction` names the escalate arm of
  `GuardrailAction` for hosts holding one in state.
- `EscalateActionFields` takes `actionTypeSelect` as optional and gains
  `asGridItems`, so a host can lay the three cells out in its own grid or stack
  them. With `actionTypeSelect` given, nothing changes.
- `className` on both.

The recipient field's `<label>` now points at the input it names, and slots get
`ctx.labelId` to name their own control; previously neither fallback input had an
accessible name. The app-picker note gets `mt-0`, since `AlertDescription`'s top
offset assumes a title above it.

Contract otherwise unchanged: the escalation target stays slot-driven, filter
stays host territory through `showFilter` / `filterContent`, and no flags,
telemetry, product types or zod cross the boundary.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@andreizdrali-uipath
andreizdrali-uipath force-pushed the feat/apollo-react-guardrail-action-section branch from 19b7570 to e53e58f Compare September 18, 2026 09:42

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.

🔵 Needs a closer look

Three moderate findings remain in the definitions enrichment and loading paths.

Review details

Suppressed comments (3)

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

  • param.id is wire-controlled, but these plain-object lookups also read inherited keys. A new parameter named toString, constructor, or __proto__ on a curated validator therefore gets a function/object as its label or tooltip instead of the humanized fallback (and can break the form). Use own-property lookups for parameter labels, tooltips, and option-label groups, just as the validator lookup above does.
    label:
      param.displayName ?? curated?.paramLabels[param.id] ?? humanizeGuardrailParameterId(param.id),

packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:206

  • Disabling through options.definitions does not change requestKey, so the stamped result remains current even though this branch only clears inFlight. When the override is later removed, the hook can immediately expose the old request's definitions with loading: false until the effect starts the new fetch; clear the settled result in this disabled path.
      // Only the in-flight flag needs clearing; the results are already unreachable, since
      // their stamp cannot match a disabled hook's request.
      abortRef.current?.abort();
      setInFlight(false);

packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:137

  • When a fetch for the current request fails, settled.error is retained. If a caller then supplies the documented options.definitions override, requestKey and isCurrent are unchanged, so this returns the old transport error alongside the valid supplied data. The error should be hidden whenever fetching is disabled.
  const error = isCurrent ? settled.error : null;
  • Files reviewed: 28/28 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI review requested due to automatic review settings September 18, 2026 09:47

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

Three moderate hook findings remain unresolved; the README contract qualification is also outstanding.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (3)

packages/apollo-react/src/canvas/components/Guardrails/README.md:76

  • This contract is broader than the implementation: a failed request only retains previous results when it is a refetch of the same request key. When the context changes, the hook deliberately clears the old tenant's results before reporting the new request's error. Please document that qualification here so hosts do not rely on stale data remaining visible across tenant/context switches.
- **A failed request keeps the previous results.** `error` is set and `definitions` still hold
  the last good payload, so a transient 503 on a `refetch` does not empty a list the user is
  looking at. Render on `error` first if you want it to replace the data. Disabling the hook
  does clear the fetched state.

packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:206

  • The disabled branch only makes the old stamp unreachable while requestKey is ''; it does not clear settled. If the host disables the hook and later enables the same context again, isCurrent becomes true immediately, so the old tenant's definitions and a stale error are shown with loading: false while the new request is in flight. Clear the settled state when disabling, as the README contract promises.
      // Only the in-flight flag needs clearing; the results are already unreachable, since
      // their stamp cannot match a disabled hook's request.
      abortRef.current?.abort();
      setInFlight(false);

packages/apollo-react/src/canvas/components/Guardrails/use-guardrail-definitions.ts:124

  • definitions?: unknown can be explicitly supplied as undefined while a host-owned SWR/react-query request is still loading. Because this checks the value rather than whether the property was supplied, the hook treats that state as transport-enabled, starts a second fetch, and can expose fetched data instead of the host result, contrary to the documented "when it is present no request is made" contract. Track property presence (and use the same flag when selecting parsed) so an explicitly supplied undefined remains host-controlled.
  // A host-provided payload replaces the request entirely rather than racing it.
  const enabled = provided === undefined && request !== null;
  • Files reviewed: 28/28 changed files
  • Comments generated: 1
  • Review effort level: Lite


const isCurrent = settled.key === requestKey;
const fetched = isCurrent ? settled.result : EMPTY_RESULT;
const error = isCurrent ? settled.error : null;
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 size:XXL 1,000+ changed lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants