feat(apollo-react): add-guardrail palette [AL-576] - #1147
feat(apollo-react): add-guardrail palette [AL-576]#1147andreizdrali-uipath wants to merge 6 commits into
Conversation
… field Generic forms-engine enablers extracted from the guardrails work (apollo-ui#1107 review): controlled values/onValuesChange/errors/disableValidation/container props on MetadataForm (MetadataFormProps now exported), a string-list field type with tooltip/textarea/multiselect metadata additions, label association and custom-component ref handling in the field renderer, and a useWatch re-export so cross-package custom fields share the RHF context. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ling InfoTooltip moves out of the guardrails prototype into components/ui with its a11y test; select and textarea get aria-invalid error styling; the root barrel exposes the new forms/ui surface. The guardrails domain family itself moves to apollo-react (canvas) per the #1107 review decision. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Moves the guardrails family out of apollo-wind's custom/ prototype shelf into apollo-react per the #1107 review decision: canvas-adjacent (MUI-free, built on wind primitives and the forms/ MetadataForm engine), exported from the canvas components barrel plus a narrow ./canvas/guardrails subpath. Strings move to lingui: useSafeLingui labels-hooks with explicit guardrails.* ids replace the wind-local catalogs and loader; the 60 keys ship translated in the shared canvas catalog for 13 locales (ru falls back to English per key). Localized templates that cross into plain-string APIs are ICU messages formatted with sentinel values, preserving the {{token}} convention. The family's Tailwind classes ride the existing tailwind.canvas.css scan; adds class-variance-authority (dep) and jest-axe (dev, matcher registered in the shared test setup). Also silences Radix's aria-describedby warning on the description-less builder dialog. Co-Authored-By: Claude Fable 5 <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>
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.
Add `GuardrailPalette`, the picker both products open from their guardrails list: grouped definitions, an optional create-custom entry, and the loading, failed and empty states. The host filters and the palette offers. Definitions arrive pre-filtered (flags, entitlements, `FeatureDisabled`/`Disabled`, Tool scope), both callbacks are intents, and no telemetry, flag or product type crosses the boundary. Definitions are generic over the eight fields the palette reads, so `EnrichedGuardrailDefinition` and a product's own type both go in and come back out of `onSelectOotb` unchanged. Grouping and entry identity are re-derived from what both products already ship: one unheaded group in payload order without BYO definitions, otherwise a group per BYO folder or connector plus a trailing UiPath group, and BYO entries keyed by validator name and connection id. An `Unauthorised` definition is offered, chipped and not choosable (`aria-disabled`, so it stays reachable), which is Flow's behaviour; Agents lets it through to a builder that then refuses to save. Only the picker ships. Flow's dialog and inline overlay and Agents' sidebar takeover are host orchestration, shown in the stories rather than modelled in a wrapper. Nine `guardrails.palette.*` lingui ids, all harvested from the two products' own catalogs at 9 of 9 coverage in the 12 translated locales, asserted both ways against the shared canvas catalog. Three label resolvers now share one `mergeLabels` helper.
|
Apollo Coded App preview deployments are ready.
|
Dependency License Review
License distribution
Excluded packages
|
📦 Dev Packages
|
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect selection safety, key identity, error-state rendering, and accessibility.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds a reusable Apollo React guardrail palette for built-in and BYO guardrails, with localization, grouping utilities, status chips, stories, and tests.
Changes:
- Adds generic palette rendering, types, callbacks, and grouping/identity helpers.
- Adds localized palette labels and catalog coverage.
- Adds shared status-chip UI and supporting fixtures/tests.
File summaries
| File | Review summary |
|---|---|
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/index.ts |
Reviewed; no final comment. |
packages/apollo-wind/src/components/forms/validation-converter.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/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/field-renderer.tsx |
Moderate (1 vote): associate the MultiSelect label and control for an accessible field name. |
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/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-guardrail-definitions.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/palette-types.ts |
Reviewed; no final comment. |
packages/apollo-react/src/canvas/components/Guardrails/index.ts |
Reviewed; no final comment. |
packages/apollo-react/src/canvas/components/Guardrails/i18n.test.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-palette.tsx |
Moderate (1 vote each): suppress the empty state when a load error exists and distinguish rendered group keys to avoid collisions. |
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette.test.tsx |
Reviewed; no final comment. |
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette.stories.tsx |
Reviewed; no final comment. |
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette-utils.ts |
Moderate (2 votes each): make item IDs collision-free and disambiguate synthetic and user-provided group keys. |
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette-utils.test.ts |
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/form-schema-builder.ts |
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-palette-item.tsx |
Critical (1 vote): prevent spread props from overriding the guarded onClick for unauthorized entries. |
packages/apollo-react/src/canvas/components/Guardrails/components/guardrail-palette-item.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 |
Reviewed; no final comment. |
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__/guardrail-palette.fixtures.ts |
Reviewed; no final comment. |
packages/apollo-react/src/canvas/components/Guardrails/__fixtures__/definitions-wire.fixtures.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
Suppressed comments (3)
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette.tsx:86
- When a load fails before any definitions are available,
erroris truthy butisEmptyis still true, so the palette renders both “Failed to load built-in validators” and “No guardrails available.” The latter incorrectly presents a transport failure as a valid empty catalog; suppress the empty state when an error is present while preserving stale definitions under the banner.
packages/apollo-react/src/canvas/components/Guardrails/guardrail-palette.tsx:118 group.keyis used directly as the React key, but named BYO groups take their key from user/connection data while__uipath__is reserved for the synthetic UiPath group. A valid folder or connector named__uipath__therefore produces duplicate React keys, which can cause unstable reconciliation when the catalog changes. Namespace the rendered key by group kind and distinguish the unnamed BYO group instead of usinggroup.keyalone.
packages/apollo-wind/src/components/forms/field-renderer.tsx:474MultiSelectdeliberately uses itsidto allow a<label>to name the trigger, but this renderer omits both sides of that association. As a result, enum-list fields rendered through this path (including guardrail lists with more than eight options) are announced as the generic “Select options...”/selection count instead of their field label. Pass the field name to both the label and the control, and cover the accessible name in the renderer test.
- Files reviewed: 93/94 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| onClick={disabled ? undefined : onSelect} | ||
| {...props} |
| return definition.byoValidatorName === undefined | ||
| ? definition.validator | ||
| : `${definition.byoValidatorName}:${definition.byoGuardrailConnectionId ?? ''}`; |
| .map(([key, list]) => ({ | ||
| // A BYO definition with neither a folder nor a connector name has nothing to head its | ||
| // group with; both products still render it, unheaded. | ||
| key: key ?? '__byo__', |
📊 Coverage + size by packagePer-package coverage and bundle size on this PR. New-line coverage = of the source lines this PR adds or changes, the % hit by tests.
"Coverage" is each package's own |
|
Published previews, both green:
Host sessions want #1148's pair: this PR's preview has no list, and #1140's has no palette. CI: Test Coverage, Typecheck, Lint, Format and Build green. The two red |
Storybook visual diffBaseline is the deployed main Storybook, so changes merged to main after this branch was last updated can also appear here. Logs Updated (PT): Sep 10, 2026, 04:50:15 AM |
Review only the last two commits (
25f5751dand27643cd4). The three below them are#1107 and #1138 and #1139, which this is branched off; the diff against
maincollapses oncethose merge.
AL-576, the add-guardrail palette, third member of the shared guardrails family in
packages/apollo-react/src/canvas/components/Guardrails/. Branched offfeat/apollo-react-guardrail-definitions-layer(#1139) rather than stacked on #1140, so thestreams stay a tree: stack only on what you import from.
What ships
guardrail-palette.tsxGuardrailPalette,GuardrailPalettePropsguardrail-palette-utils.tsgroupGuardrailsForPalette,getGuardrailPaletteItemId. Pure, React-free.palette-types.tsGuardrailPaletteDefinition,GuardrailPaletteGroupcomponents/guardrail-palette-item.tsxGuardrailPaletteItem(not exported fromindex.ts: the narrow surface is the palette)i18n.ts(extended)GuardrailPaletteLabels,GUARDRAIL_PALETTE_EN_LABELS,GUARDRAIL_PALETTE_EN_MESSAGES,resolveGuardrailPaletteLabels,useGuardrailPaletteLabelscomponents/guardrail-status-chip.tsxGuardrailStatusChip(commit 1, shared with #1140)Commit 1 is shared with #1140
GuardrailStatusChip, its test, and theGUARDRAIL_CHIP_GEOMETRYconstant inguardrail-chip.tsxare copied byte-identically from the AL-575 list branch, whichintroduced them. The palette needs the same chip for an unauthorized definition and for a BYO
connector name. Whichever of the two PRs merges second drops its duplicate on rebase and
resolves the
index.tsappend. If review changes the chip in one PR, mirror it in the other.The commit also carries the chip's test file, which the brief for this PR did not list: an
exported component with no test in the PR that introduces it is a gap, and the file is
byte-identical to #1140's, so it merges the same way the component does.
Decisions worth arguing with
Select. Flow's picker is a windSelect; Agents' is a scrollablesidebar list. The list is the more capable of the two: it composes into a dialog, an inline
overlay or a sidebar, and it shows the descriptions without a popover. Flow's own
SelectContentalready fights the primitive to get there (multi-line items,disableditems, sticky group headers,
h-auto min-h-9on the trigger, two!importantviewportoverrides), which is the evidence. QA-visible for Flow: the type dropdown becomes a
one-step list.
divwithrole="listitem",tabIndex={0}and its own Enter/Space handler, which puts a list item outside a list andreimplements a button. An
Unauthorisedentry isaria-disabled, notdisabled, so itkeeps its place in the tab order and a keyboard user reaches the chip that says why.
Unauthorisedis not choosable. Flow's behaviour. QA-visible for Agents: today thatentry opens a builder that then refuses to save.
GuardrailPaletteDefinition(the eight fields the palettereads), for the same reason the list keeps its own definition shape.
EnrichedGuardrailDefinitionsatisfies it andonSelectOotbhands the host's own objectback,
parametersand all, so there is nothing to look up again. Agents'OutOfTheBoxGuardrailDefinition, whosedescriptionis optional, also fits.and an inline overlay that renders the chosen builder underneath, Agents takes over the
sidebar with a back button and its own create affordance. The
InADialogandInAHostSidebarstories are each about fifteen lines.onCreateCustomis opt-in by presence. Flow offers custom guardrails forscope === 'Tool'only, so it passes the prop conditionally; Agents keeps the affordance in its ownheader, so it omits the prop and gets no duplicate entry.
groupGuardrailsForPalette,which returns one empty group. That is what makes an empty state reachable at all: Flow
guards its own empty message on
groups.length === 0, which its implementation can neverreturn, so the message is dead code there.
mergeLabels: with a third label set, the merge loop is factored out. The helper isbyte-identical to the one feat(apollo-react): guardrail list section [AL-575] #1140 introduces for the same reason, so the two merge clean.
Strings
Nine
guardrails.palette.*ids, one builder function holding every_({ id, message })call(the
definitions-copy.tsidiom). All nine harvested from the products' own catalogs: eightfrom Flow's
addGuardrailPalette_*i18next keys,list-aria-labelfrom Agents'guardrails.palette.list_aria_labellingui id, with English parity asserted against thesource product before writing. 9 of 9 in each of the 12 translated locales;
ruleftempty per this catalog's convention.
i18n.test.tsasserts catalog parity both ways plusfull translation coverage, which is what
lingui extractwould otherwise do (src/canvasuses no macros, so extraction does not feed this catalog).
One loc review item: German takes "Leitplanke" from Flow for eight ids and "Leitlinien" from
Agents for the ninth, because the two products picked different words for "guardrail".
Verification
Guardrails suite 362 passing (23 files, 49 new), jest-axe on both rendering suites,
tsc/ biome /rslib buildclean, no zod in the folder's.d.ts, no icon churn.Open question
Should the palette also render
FeatureDisabled/Disableddefinitions as disabled entries?It does not today: both products filter them out before rendering, and
Unauthorisedis theonly non-
Availablestatus that reaches a correctly filtered palette by design. Say the wordand the status-to-chip mapping generalizes.