feat(apollo-react): guardrail list section [AL-575] - #1140
Draft
andreizdrali-uipath wants to merge 5 commits into
Draft
feat(apollo-react): guardrail list section [AL-575]#1140andreizdrali-uipath wants to merge 5 commits into
andreizdrali-uipath wants to merge 5 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>
Contributor
Dependency License Review
License distribution
Excluded packages
|
Adds `GuardrailList` to the canvas guardrails family: the applied-guardrails
section both products own today (Agents `GuardrailsListingSection`, Flow
`GuardrailsEditor` + `SortableGuardrailItem`), extracted once.
The host filters and the list renders. Rows and definitions arrive already
filtered by feature flags, entitlements and scope, every callback is an intent
(dialogs, persistence and telemetry stay with the product), and `onReorder`
reports the visible array plus `{ from, to, id }` so a host rendering a filtered
view can splice the result back. Every addition beyond what both products show
today is opt-in, so adopting the list behind a flag renders what the host
renders now.
Reorder is real dnd-kit with pointer and keyboard sensors; the drag handle is a
real button, where Agents' handle today is an `aria-hidden` icon carrying the
listeners. With reorder off, no drag machinery mounts at all.
`GuardrailStatusChip` is `Badge`-based rather than the Radix-Toggle
`GuardrailChip`: these are read-only labels. `GuardrailStatusBanner` and
`MixedScopesBanner` are promoted to exports additively.
Chrome strings resolve through `useGuardrailListLabels` on `useSafeLingui` with
`guardrails.list.*` ids; 12 of the 17 are harvested from Flow's canvas catalog
into all 12 translated locales, the other 5 fall back to English per key.
Also here: `@dnd-kit/modifiers` added to apollo-react (wind already pinned it,
so no new install weight), the three label resolvers share one merge helper, and
`__fixtures__` no longer reaches the published bundle.
andreizdrali-uipath
force-pushed
the
feat/apollo-react-guardrail-list
branch
from
September 9, 2026 13:35
eed9161 to
4ac1aa1
Compare
andreizdrali-uipath
changed the base branch from
feat/apollo-react-guardrails-family
to
feat/apollo-react-guardrail-definitions-layer
September 9, 2026 13:36
andreizdrali-uipath
changed the base branch from
feat/apollo-react-guardrail-definitions-layer
to
main
September 9, 2026 13:58
Contributor
📦 Dev Packages
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements AL-575 (epic AL-526): the guardrails list
section, built fresh in the apollo-react guardrails family.
Draft, stacked on #1139 (AL-574), which sits on #1138. Opened against
mainsopr-checksand thedev-packagespreview run (the #1138 pattern); the commits below the last one are #1107, #1138 and #1139. Review only the last commit,feat(apollo-react): guardrail list section [AL-575]. Rebased ontomainonce the PRs beneath it merge.The list takes no dependency on the definitions layer (see the
definitionsbullet below); it is stacked there so that one preview version carries both, which is what a host needs to try the list against real, parsed definitions.What it replaces
GuardrailsListingSection.tsxin Agents (968 LOC), andGuardrailsEditor.tsx(734) +SortableGuardrailItem.tsx(126) in flow-workbench. Prop shape follows Flow's, per the design docrecipe. Both hosts' components are orchestrators (palette, builder, remove dialog, save-as-new,
telemetry) wrapped around the rendering; this replaces the rendering, the reorder and the section
chrome, and the orchestration stays where it is.
The contract
The host filters, the component renders.
GuardrailListnever drops a row.enableGuardrail*booleans and Flow'scanvas.guardrailsentitlement plus scope filter stay in the adapters; hosts pass the survivorsas
guardrailsanddefinitions.onRemovereports the request; the confirmation dialog, thescoped-removal unwind and the write stay host-side, because the two products confirm with
different copy and unwind tool-scoped guardrails differently. Same for
onAdd/onEdit.onTrackprop; I did not add one.feat(apollo-react): guardrails component family under canvas #1138 has no telemetry seam, the two products' event taxonomies do not overlap
(
guardrails.reorderedvsGUARDRAILS_REORDER_BUTTON_CLICKED), and wrapping the intents is whatboth adapters do anyway. Easy to add later if you disagree.
onReorderreports the reordered visible array plus the move ({ from, to, id }), so a hostrendering a filtered view (Flow's per-tool view over an agent-level list) can splice it back
without the package knowing a fuller list exists.
definitionsis read for three things only: the provider line, the two BYO notices and thestatus chip. Matching is the rule both products already ship, converged here: a BYO row matches on
validator name alone, so an admin rebinding a configuration to another connection still
resolves. The list's definition type is the minimum it reads
(
validator,status,byoValidatorName?,byoConnectorName?), whichGuardrailDefinitionandAL-574's
EnrichedGuardrailDefinitionboth satisfy. SouseGuardrailDefinitions' output feedsthe list unchanged, and the list still compiles and tests green without the definitions layer
present: the two stacks are independent and can merge in either order.
Divergences, resolved as one shape plus a seam
nameidid ?? name, override withgetItemIdisReadOnlydisableddisableddisabled; Flow passesreorderDisabled={false}renderItemActionsswaps in the menu and receivesdefaultActionsrowActivatesEditopts informatScopes/formatActionlocalize host-siderenderRowTooltipadds the tooltipBehaviour parity first
Every addition is opt-in, so an adopting host gets a swap that renders what it renders today and
turns the new UI on deliberately.
statusChipsfalsepreviewChipfalsereorderDisableddisabledunstyledfalseSectionAccordion, where the card border is extra chrome.hideHeaderfalsefooteraddSlotcannot express.emptyState=== undefined, so an explicitnullis honoured.rowActivatesEditfalserenderRowTooltipformatScopes/formatAction() => nullhides the line.So each host passes:
previewChip,reorderDisabled={false}. Everything else already matches.previewChip,unstyled,hideHeader,emptyState={null},footer,rowActivatesEdit,formatAction={() => null},formatScopes,renderItemActions,renderRowTooltip.One deliberate exception I did not preserve: Agents' drag handle is an
aria-hiddenicon with thelisteners on it, so it is unreachable by keyboard. Mine is a real button carrying
setActivatorNodeRef, so focus also returns to it after a drop. That is a fix, not a regression.Reuse, and one deliberate non-reuse
GuardrailStatusBannerandMixedScopesBannerare promoted from internal pieces to exports, as theticket asks. Both additions are additive; nothing existing changed shape.
The status chip is not
GuardrailChip. That wraps a RadixToggle, so it is a focusable buttoncarrying pressed state; these chips are read-only labels and would otherwise put fake buttons in the
tab order and misreport to screen readers.
GuardrailStatusChipisBadge-based and shares thechip family's pill geometry through a new
GUARDRAIL_CHIP_GEOMETRYconstant (feedingguardrailChipVariants()into aBadgedrags the Toggle appearance'sbg-*anddata-[state]classes along, and they win over the badge variant). Reviewers who read the ticket line as "reuse
the chip UX" get what they expected; reviewers who meant the component literally, please push back.
Chips and notices
Availablepluslocaladministration renders nothing, so the common row stays quiet.FeatureDisabledandUnauthorisedchip as warnings,Disabledand the syntheticUnavailable(a BYO row whose definition stopped resolving) as errors. The mapping is the pure, exported
getGuardrailListChips, so AL-578's centralized section can reuse it.The local-vs-governance axis is called
administration, never "origin": Agents'GuardrailOriginChipand Flow'sCentralizedGuardrailOriginBadgeboth already mean BYO versusUiPath-managed, which is a different axis and travels on
byoValidatorName. Governance rows stillbelong to each product's separate centralized section (AL-578); this ships the chip and the seam so
either layout stays possible.
i18n
17
guardrails.list.*ids, resolved throughuseGuardrailListLabelsonuseSafeLingui. Onebuilder function holds every
_({ id, message })call, with the English record and the flatid-to-message record derived from it, so the three cannot drift (the
definitions-copy.tsidiom).12 of the 17 are harvested from Flow's canvas catalog into all 12 translated locales, at 100%
coverage, so this adds no translation work: the harvest asserted our English equals Flow's for every
mapped id before writing, and rewrote i18next
{{name}}to ICU{name}. The 5 with no hostequivalent are English-only for now and fall back per key (
edit-row,status-feature-disabled,status-disabled,status-unavailable,administration-governance); hosts can supply them throughlabelstoday. Flagged in the README.remove-itemtakes Flow's remove-dialog translation ("Remove guardrail") rather than its rowbutton ("Delete guardrail"), so the row action, the
onRemoveintent and AL-577's dialog all saythe same thing.
i18n.test.tsasserts catalog-to-source parity both ways, which is whatlingui extractwould doif
src/canvasused macros.Tests
84 new tests (the Guardrails suite is 288 passing), jest-axe on all three rendering suites.
The keyboard reorder runs the real dnd-kit sensor rather than a mock: happy-dom does no layout,
so every rect is zero and collision detection cannot tell two rows apart. The suite lays the rows on
a synthetic vertical grid and awaits a macrotask between keystrokes, because dnd-kit measures inside
requestAnimationFrameand the canvas test setup implements that assetTimeout(cb, 0), whichact()does not flush. Both halves are in__fixtures__/dnd-geometry.tswith the reason writtendown.
Two things worth a look outside the component
@dnd-kit/modifiers@^9.0.0added to apollo-react forrestrictToVerticalAxis/restrictToParentElement, which both hosts already use. apollo-wind pins the same version, sothere is no new install weight; the lockfile diff is the 3-line importer entry.
!./src/**/__fixtures__/**added torslib.config.ts's entry list. The geometry helperimports
@testing-library/react, and without the exclusion it was emitted intodist.Local gate
tsc, biome andrslib buildclean for@uipath/apollo-react; the full package suite is2922 passing with 9 failures, all pre-existing and unrelated: 8 are
localStoragebeing undefinedunder happy-dom (
canvas/utils/Storage.test.ts,canvas/hooks/useStorageState.test.ts, theyreproduce on the base branch), and one is a 5s test timeout in
ap-model-pickerthat only appearsunder full-suite load and passes in isolation.
build:iconswas deliberately not run: on Windows it rewrites OS-native path separators into everygenerated icon header, which is 1322 unrelated files. Fix queued separately.
Open review questions
an export, but the list composes banners through one
statusBanner: ReactNode(a definitionsload failure is the real case). Its second line is a "Save as new" hint, which is builder copy,
and both products render it inside a builder rather than over a list. Say the word and it becomes
a typed
mixedScopes={{ scopes, tools }}prop again.onTrackprop, against Confluence §7.5. Reasoning above.GuardrailsListingSectionswapsaffectedScopesandaffectedToolNameswhen building the remove-dialog payload. That code stays host-side eitherway, so this PR neither fixes nor preserves it, but the adapter has to decide.
Next
Agents and flow-workbench adoption behind
EnableApolloGuardrailBuilder/canvas.guardrails.apollo-components, with the existing components kept verbatim as*Legacytwins. Those PRs need a published version (or the #1138-style preview pin), so they follow the
release.