Skip to content

feat(apollo-vertex): add ConfidenceSignal component - #1001

Open
ChloeDalyUiPath wants to merge 1 commit into
UiPath:mainfrom
ChloeDalyUiPath:feat/confidence-signal
Open

feat(apollo-vertex): add ConfidenceSignal component#1001
ChloeDalyUiPath wants to merge 1 commit into
UiPath:mainfrom
ChloeDalyUiPath:feat/confidence-signal

Conversation

@ChloeDalyUiPath

@ChloeDalyUiPath ChloeDalyUiPath commented Aug 3, 2026

Copy link
Copy Markdown

Adds ConfidenceSignal, a signal-bar AI confidence chip (high/medium/low/unknown) with three
density variants (min/med/max), a tooltip on every chip, and a popover for factor breakdowns
and next-step CTAs, plus an opt-in one-time "acquire" animation. Registered in registry.json with
docs at /components/confidence-signal.

Visuals, popover structure/behaviour, and animation timing were matched against the team demo at
ai-confidence-demo-kappa.vercel.app: rounded signal-bar pills, faded same-hue tracks for unfilled
bars, per-level default explanations, and per-factor status tints.

Note on the nextStep requirement

An earlier internal demo site documented the action CTA as required for low/unknown. Per the
live team decision (Peter + Haidy), this PR requires it for medium/low instead (optional for
high/unknown), enforced at compile time via a discriminated union on ConfidenceSignalProps.
Both interpretations were considered; this follows the call, not the demo site.

Accessibility and conventions

  • Every chip carries a tooltip, so variant="min" (icon only) is never unlabelled.
  • Interactive detail lives in a popover, not the tooltip, so nextStep stays reachable by keyboard
    and on touch. The tooltip is suppressed while the popover is open.
  • CTAs accept href (rendered as a link, so navigable/middle-clickable) and/or onClick.
  • The acquire animation is skipped under prefers-reduced-motion: reduce.
  • Level labels and default explanations go through react-i18next under the confidence_signal_*
    prefix in locales/en.json, per AGENTS.md. Only en.json was touched.
  • Split into focused files (-levels, -bars, -chip, -factors, -cta) with target fields on
    every registry.json file entry, per the multi-file registry guidance in AGENTS.md.

Scope

Component only, per the agreed two-PR split. A follow-up PR adds usage guidance to
app/guidelines/ai-toolkit and the components overview entry.

Checks

pnpm install, pnpm registry:build, pnpm format, pnpm lint, pnpm lint:deps, and
pnpm typecheck all pass (lint:deps reports only the 10 pre-existing repo-wide warnings, 0
errors). Server-rendered output verified at /components/confidence-signal: all variants and levels
render, i18n labels resolve, and the SVG markup matches the demo. No runtime errors in the dev
server log.

Opened from a fork — I currently have read-only access to this repo.

Copilot AI lite review requested due to automatic review settings August 3, 2026 13:39

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.

Pull request overview

Adds a new ConfidenceSignal UI component to the Apollo Vertex registry and documentation. The component is intended to communicate AI confidence levels (high/medium/low/unknown) via a signal-bar chip, optionally showing a hover/click popover with explanations, factor breakdowns, and CTAs.

Changes:

  • Introduces ConfidenceSignal + SignalBars, including an optional “acquire” animation and hover-open / 150ms-close popover behavior.
  • Registers the component in apps/apollo-vertex/registry.json for the Vertex registry build pipeline.
  • Adds a new docs page at /components/confidence-signal and adds it to the components nav.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx Implements the ConfidenceSignal chip, popover content, and signal-bar SVG/animation.
apps/apollo-vertex/registry.json Registers the new registry:ui entry for confidence-signal.
apps/apollo-vertex/app/components/confidence-signal/page.mdx Adds component documentation and usage examples.
apps/apollo-vertex/app/components/_meta.ts Adds “Confidence Signal” to the components navigation.
Suppressed comments (5)

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:328

  • Same issue for nextStep: href is part of the prop type but isn’t used, so link-style next steps can’t be implemented without custom wrappers.
              onClick={nextStep.onClick}
            >
              {nextStep.label}
              <ArrowUpRight className="size-3" />
            </Button>

apps/apollo-vertex/app/components/confidence-signal/page.mdx:33

  • Same as above: nextStep is shown without href/onClick, producing a no-op CTA in the docs example.
<div className="p-4 border rounded-lg mt-4 flex flex-wrap gap-4 items-center">
  <ConfidenceSignal level="high" variant="med" />
  <ConfidenceSignal level="medium" variant="med" nextStep={{ label: 'Review' }} />
  <ConfidenceSignal level="low" variant="med" nextStep={{ label: 'Review' }} />
  <ConfidenceSignal level="unknown" variant="med" />
</div>

apps/apollo-vertex/app/components/confidence-signal/page.mdx:42

  • Same as above: nextStep is shown without href/onClick, producing a no-op CTA in the docs example.
<div className="p-4 border rounded-lg mt-4 flex flex-wrap gap-4 items-center">
  <ConfidenceSignal level="high" variant="max" />
  <ConfidenceSignal level="medium" variant="max" nextStep={{ label: 'Review' }} />
  <ConfidenceSignal level="low" variant="max" nextStep={{ label: 'Review' }} />
  <ConfidenceSignal level="unknown" variant="max" />
</div>

apps/apollo-vertex/app/components/confidence-signal/page.mdx:74

  • The popover example provides nextStep without href/onClick, which renders a no-op CTA in the docs.
      { label: 'Document match', value: '2 / 5', status: 'error' },
      { label: 'Historical accuracy', value: '61%' },
    ]}
    nextStep={{ label: 'Review manually' }}
  />

apps/apollo-vertex/app/components/confidence-signal/page.mdx:84

  • The acquire-animation example passes nextStep without href/onClick, producing a no-op CTA in the docs.
<div className="p-4 border rounded-lg mt-4 flex flex-wrap gap-4 items-center">
  <ConfidenceSignal level="high" variant="max" animateIn />
  <ConfidenceSignal level="medium" variant="max" animateIn nextStep={{ label: 'Review' }} />
</div>

Comment on lines +53 to +59
const ACQUIRE_KEYFRAMES = `
@keyframes confidence-signal-acquire {
0% { transform: scaleY(1); }
30% { transform: scaleY(0.05); }
100% { transform: scaleY(1); }
}
`;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. The SVG now carries a local @media (prefers-reduced-motion: reduce) rule that overrides the inline animation, so the bars render at their final state with no motion.

Comment on lines +128 to +133
const LEVEL_TEXT_CLASS: Record<ConfidenceLevel, string> = {
high: "text-success",
medium: "text-amber-700",
low: "text-destructive",
unknown: "text-foreground",
};

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Medium now uses text-warning-foreground dark:text-warning, matching badge.tsx/alert.tsx. The split is needed because --warning is a light amber that fails contrast on a light background while --warning-foreground is near-black in both themes. Note the bar fills themselves stay literal hues on purpose: the signal metaphor relies on a fixed green/amber/red ramp reading identically in both themes, the way a battery or wifi icon does.

Comment on lines +306 to +316
{explainCta && (
<Button
variant="outline"
size="sm"
className="w-full"
onClick={explainCta.onClick}
>
{explainCta.label}
<ArrowUpRight className="size-3" />
</Button>
)}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. ConfidenceSignalCta now renders an anchor via Button asChild when href is present, so the target is navigable, middle-clickable, and copyable. onClick still fires. Also tightened the type so at least one of href/onClick is now required.

Comment on lines +19 to +24
<div className="p-4 border rounded-lg mt-4 flex flex-wrap gap-4 items-center">
<ConfidenceSignal level="high" variant="min" />
<ConfidenceSignal level="medium" variant="min" nextStep={{ label: 'Review' }} />
<ConfidenceSignal level="low" variant="min" nextStep={{ label: 'Review' }} />
<ConfidenceSignal level="unknown" variant="min" />
</div>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed. Every example CTA now passes a real href. This is also enforced at the type level now, so a CTA with neither href nor onClick fails to compile rather than silently rendering a no-op.

{ label: 'Source quality', value: 'High', status: 'success' },
{ label: 'Data recency', value: '< 30 days', status: 'success' },
]}
explainCta={{ label: 'View audit trail' }}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed, same as above. explainCta in the popover examples now points at a real target.

@ChloeDalyUiPath
ChloeDalyUiPath marked this pull request as ready for review August 5, 2026 13:31
@ChloeDalyUiPath
ChloeDalyUiPath requested a review from a team as a code owner August 5, 2026 13:31
Comment thread apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx Outdated
Comment thread apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx Outdated
Comment thread apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx
Comment thread apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx Outdated
Copilot AI review requested due to automatic review settings August 7, 2026 09:07
@ChloeDalyUiPath
ChloeDalyUiPath force-pushed the feat/confidence-signal branch from 2303d26 to bce2dca Compare August 7, 2026 09:07

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (5)

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:113

  • tooltipOpen is preserved while the popover is open. If the tooltip was open before opening the popover, closing the popover (e.g. by clicking outside) will immediately reopen the tooltip even when the pointer/focus is no longer on the trigger. Resetting tooltipOpen as part of the popover onOpenChange avoids this stale-state reopen/flash.
    <Popover open={detailsOpen} onOpenChange={setDetailsOpen}>

apps/apollo-vertex/registry/confidence-signal/confidence-signal-factors.tsx:32

  • key={factor.label} is not guaranteed to be unique (labels can repeat), which can cause React key collisions and unstable row reconciliation. Use a stable unique key (e.g. include the index, or introduce an id on ConfidenceFactor).
      {factors.map((factor) => (
        <ConfidenceSignalFactorRow key={factor.label} factor={factor} />
      ))}

apps/apollo-vertex/registry/confidence-signal/confidence-signal-levels.ts:17

  • ConfidenceCta currently allows providing neither href nor onClick, which renders a CTA that looks interactive but does nothing (and the rest of this component set assumes at least one action). Consider enforcing “at least one of href/onClick” at the type level to prevent invalid CTAs.
  label: string;
  /** Renders the CTA as a link. Takes precedence over `onClick` alone. */
  href?: string;
  onClick?: () => void;
}

apps/apollo-vertex/registry/confidence-signal/confidence-signal-cta.tsx:28

  • if (cta.href) treats an empty-string href as “no href”, which would render a <button> instead of an <a> and drop link affordances. Checking href !== undefined matches the intended optionality and is robust against empty strings.
  if (cta.href) {

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:46

  • aria-label and type="button" are currently set before {...props}, so callers can accidentally override them via props spread (which contradicts the “always announced” accessibleLabel contract and can reintroduce default submit-button behavior in forms). Spread props first, then set type/aria-label so the component guarantees these attributes.
    <button
      type="button"
      className={cn(
        "inline-flex items-center gap-1.5 text-xs font-semibold focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
        LEVEL_CONFIG[level].textClass,
        interactive && "cursor-pointer",
        className,
      )}
      aria-label={accessibleLabel}
      {...props}
      // After the spread: as a Tooltip/Popover trigger this chip is cloned with
      // the trigger's own `data-slot`, which would otherwise mask its identity.
      data-slot="confidence-signal"
      data-level={level}
    >

Copilot AI review requested due to automatic review settings August 7, 2026 12:04
@ChloeDalyUiPath
ChloeDalyUiPath force-pushed the feat/confidence-signal branch from bce2dca to b6e36a1 Compare August 7, 2026 12:04
@ChloeDalyUiPath

Copy link
Copy Markdown
Author

Pushed an update addressing all review feedback. Rebased into the single commit rather than stacking fixups, per the repo's git workflow.

@frankkluijtmans — your four points:

Request Change
Use the existing Tooltip component Now imports @/components/ui/tooltip; the hand-rolled hover logic is gone
Split for readability Now 5 files: -bars, -chip, -factors, -cta, -levels. Main file is composition only
Extract into its own component with props ConfidenceSignalChip, taking label/accessibleLabel/level/animateIn/interactive
These should be translation keys confidence_signal_* keys via useTranslation(), added to locales/en.json alphabetically. No other locales touched

Copilot's earlier pass: prefers-reduced-motion now honoured, text-amber-700 replaced with text-warning-foreground dark:text-warning, href actually renders an anchor, and the docs examples no longer show no-op CTAs.

Copilot's latest pass (3 suppressed comments), also fixed:

  • Tooltip could flash back open after dismissing the popover, because tooltipOpen kept its pre-popover state. The popover's onOpenChange now clears it on close.
  • key={factor.label} could collide when two factors share a label. Now keyed on label:value; rows matching on both are indistinguishable to the reader anyway.
  • ConfidenceCta permitted neither href nor onClick. Now a union requiring at least one, so a dead CTA fails to compile. Verified with a negative test: { label } alone errors, { label, href } and { label, onClick } both pass.

Checks: format, lint, lint:deps (0 errors; the 10 warnings are pre-existing repo-wide), typecheck (fresh, uncached), and registry:build all pass. Page renders clean at /components/confidence-signal with no runtime errors, i18n resolving, and no leaked keys.

Two notes for reviewers:

  1. The label / Label PR size checks fail with Resource not accessible by integration. That's the fork-PR limitation — labeling actions get a read-only token and can't attach labels. Not caused by anything in this diff.
  2. Interactive hover/click behaviour was verified in an earlier round; this round's verification was server-rendered output plus type-level tests, as my browser tooling dropped mid-session. The tooltip-flash fix in particular is worth a quick manual poke before merge.

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (3)

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:123

  • The popover currently relies on nesting PopoverTrigger inside TooltipTrigger (via the tooltip variable). After making the tooltip trigger directly on the chip, the popover needs its own trigger wrapper. Wrapping the tooltip+chip in a PopoverTrigger asChild on a simple DOM element avoids nested Radix triggers while still allowing clicks/Enter on the inner button to bubble and open the popover.
    <Popover open={detailsOpen} onOpenChange={handleDetailsOpenChange}>
      {tooltip}
      <PopoverContent align="start" className="flex w-64 flex-col gap-3">

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:26

  • ConfidenceSignalChip is used as the child of Radix TooltipTrigger asChild / PopoverTrigger asChild. For Radix asChild to work correctly (positioning, focus management), the child must accept a ref. This component is a plain function component, so it doesn’t forward refs and can cause runtime warnings or broken tooltip/popover behavior. Convert it to React.forwardRef<HTMLButtonElement, ConfidenceSignalChipProps> and pass the ref to the <button>.
function ConfidenceSignalChip({
  level,
  label,
  accessibleLabel,
  animateIn = false,

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:103

  • TooltipTrigger asChild is currently given a PopoverTrigger element when hasDetails is true. Radix asChild requires the child to be a DOM element or a forwardRef component; PopoverTrigger here is a wrapper component (not forwardRef), so the tooltip trigger ref/handlers won’t attach reliably. Consider making the tooltip always trigger directly on the chip, and move the popover trigger wrapper to the popover render path instead.

This issue also appears on line 121 of the same file.

    <Tooltip open={tooltipOpen && !detailsOpen} onOpenChange={setTooltipOpen}>
      <TooltipTrigger asChild>
        {hasDetails ? <PopoverTrigger asChild>{chip}</PopoverTrigger> : chip}
      </TooltipTrigger>

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:108

  • Tooltips won’t reliably show on a disabled <button> (disabled elements don’t receive pointer/focus events). Since ConfidenceSignalProps inherits button props (including disabled) and you’re guaranteeing “every chip carries a tooltip”, this should follow the existing pattern used elsewhere in apollo-vertex: wrap the disabled trigger in a non-disabled <span> and make the tooltip trigger target that wrapper.
  const tooltip = (
    <Tooltip open={tooltipOpen && !detailsOpen} onOpenChange={setTooltipOpen}>
      <TooltipTrigger asChild>
        {hasDetails ? <PopoverTrigger asChild>{chip}</PopoverTrigger> : chip}
      </TooltipTrigger>

apps/apollo-vertex/registry/confidence-signal/confidence-signal.tsx:126

  • text-muted-foreground on the <span> wrapping <SignalBars /> has no effect because the SVG uses explicit fill hex values (it doesn’t inherit currentColor). This makes the styling misleading and harder to maintain. Either remove the unused text color class or update SignalBars to use currentColor if you actually want it tinted via text classes.
          <span className="shrink-0 text-muted-foreground">
            <SignalBars level={level} />

Comment thread apps/apollo-vertex/registry/confidence-signal/confidence-signal-bars.tsx Outdated
Copilot AI review requested due to automatic review settings August 10, 2026 08:51

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (4)

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:41

  • In ConfidenceSignalChip, {...props} comes after type="button" and aria-label={accessibleLabel}, so callers can override them (e.g., accidentally turning the chip into a submit button inside a form). Spread props first and set type/aria-label after to enforce the intended behavior.
    <button
      type="button"
      className={cn(
        "inline-flex items-center gap-1.5 text-xs font-semibold focus-visible:outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50",
        LEVEL_CONFIG[level].textClass,
        interactive && "cursor-pointer",
        className,
      )}
      aria-label={accessibleLabel}
      {...props}

apps/apollo-vertex/registry/confidence-signal/confidence-signal-factors.tsx:37

  • ConfidenceSignalFactors uses a composite key of ${label}:${value}. If the same factor appears twice (same label and value), React keys collide and rows can be reused incorrectly. Include the index (or add an explicit id field) to guarantee uniqueness.
      {factors.map((factor) => (
        // Labels are not guaranteed unique, so pair label with value: two rows
        // that match on both are indistinguishable to the reader anyway.
        <ConfidenceSignalFactorRow
          key={`${factor.label}:${factor.value}`}
          factor={factor}
        />

apps/apollo-vertex/registry/confidence-signal/confidence-signal-cta.tsx:28

  • The CTA rendering checks if (cta.href), which treats an empty string as “no href” and falls back to the button path (where onClick may be undefined). Since the type distinguishes by presence (href vs no href), check for undefined instead of truthiness.
  if (cta.href) {

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:11

  • ConfidenceSignalChipProps currently allows passing type and aria-label via the button props, but the component already manages these internally (type="button" and accessibleLabel). Omitting them from the public props avoids accidental overrides and clarifies the API contract.

This issue also appears on line 32 of the same file.

export interface ConfidenceSignalChipProps
  extends Omit<React.ComponentProps<"button">, "children"> {
  level: ConfidenceLevel;

Copilot AI review requested due to automatic review settings August 10, 2026 09:02

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:46

  • {...props} is spread after type and aria-label, so callers can override type="button" (risking accidental form submits) and replace the computed accessible label. Spread props first, then set the non-overridable attributes after.
      aria-label={accessibleLabel}
      {...props}

apps/apollo-vertex/registry/confidence-signal/confidence-signal-cta.tsx:24

  • The ArrowUpRight icon is decorative and should be hidden from assistive tech to avoid being announced alongside the CTA label.
      <ArrowUpRight className="size-3" />

Copilot AI review requested due to automatic review settings August 10, 2026 09:18
@ChloeDalyUiPath

Copy link
Copy Markdown
Author

Pushed de83d527 addressing Copilot's two latest points.

1. Prop spread order in confidence-signal-chip.tsx — fixed, with one deliberate exception:

  • type="button" now sits after the spread so it can't be overridden. This was the real bug in the pair: a chip inheriting type="submit" would submit any form it sits in.
  • aria-label I've deliberately left before the spread, so it stays overridable. A consumer putting this in a table row may legitimately want "Confidence for Acme Health Plan: high" rather than the generic "High confidence", and blocking that would be worse for accessibility, not better. The computed label remains the default when nothing is passed. Happy to lock it too if reviewers disagree.

2. Decorative ArrowUpRight in confidence-signal-cta.tsx — fixed, now aria-hidden="true", matching how decorative icons are handled elsewhere in the registry. The CTA label already carries the meaning.

Verified in rendered output: all 16 chips emit type="button" and an aria-label, and the CTA arrows are hidden from assistive tech.

Checks all pass (format, lint, lint:deps 0 errors, typecheck fresh, registry:build).

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:33

  • When converting ConfidenceSignalChip to forwardRef, ensure the forwarded ref is attached to the underlying <button> so Radix triggers can measure and focus it.
    <button
      className={cn(

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:55

  • After wrapping the component in forwardRef(...), the closing brace needs to close the forwardRef call (and optionally set displayName). Otherwise this file will not compile.
}

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:26

  • TooltipTrigger/PopoverTrigger are used with asChild and receive <ConfidenceSignalChip /> as the child. Since ConfidenceSignalChip is a function component that doesn’t forward refs, the Radix trigger ref will not reach the underlying <button>, which can break popover positioning and focus handling. Convert the chip to forwardRef and accept the ref parameter.
function ConfidenceSignalChip({
  level,
  label,
  accessibleLabel,
  animateIn = false,

@@ -0,0 +1,57 @@
"use client";

import type * as React from "react";

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Not acting on this one — I believe it's a false positive for React 19, but flagging my reasoning in case a reviewer disagrees.

forwardRef isn't needed here. This app is on React 19.2.3, where ref is passed to function components as an ordinary prop and forwardRef is no longer required. ConfidenceSignalChipProps extends React.ComponentProps<"button">, which in @types/react 19 includes ref, and the component spreads {...props} onto the <button> — so the Radix trigger's ref does reach the element.

Verified three ways:

  1. Type test<ConfidenceSignalChip ref={useRef<HTMLButtonElement>(null)} ... /> typechecks cleanly, so ref is part of the props contract and correctly typed.
  2. House patterngrep -rl forwardRef registry/ returns zero files. The repo's own Button, used with asChild throughout, is a plain function component spreading {...props}. Adding forwardRef here would make this the only component doing so.
  3. Behaviour — the popover opens, positions against the trigger, and returns focus correctly.

Worth noting the suggestion is also self-inconsistent: the accompanying suppressed comments warn that the forwardRef conversion would break compilation unless the closing brace and displayName are also fixed — which is a hazard introduced by the change, not by the current code.

Happy to convert it if anyone prefers the explicit form, but on React 19 it'd be redundant.

@petervachon

Copy link
Copy Markdown
Collaborator

Since checks are gated on head.repo.fork == false and this is running from a fork, Build/Lint/Format/Dependency-Consistency never actually run here (they show skipped, not passing) — that's an access-permissions issue we're tracking separately, not a problem with this PR.

To get real CI signal in the meantime, I mirrored this commit onto #1034 (same commit, same authorship) directly in this repo rather than a fork. This PR stays open and untouched — nothing here is being closed or merged, #1034 is just to unblock CI while the write-access question gets sorted out.

@petervachon

Copy link
Copy Markdown
Collaborator

Update: with real CI now running on #1034, it caught a genuine bug — the registry check (which never ran here due to the fork gate) failed on:

```
@uipath/confidence-signal uses Apollo CSS variables that are not provided by its registry item or registryDependencies:

  • cssVars.light["warning-foreground"]
  • cssVars.dark["warning-foreground"]
    ```

confidence-signal's cssVars.theme maps color-warning-foreground to var(--warning-foreground), but never supplies the literal light/dark value the way every other entry using this token does (e.g. alert). Fix, matching the existing convention exactly:

```json
"cssVars": {
"theme": { ... unchanged ... },
"light": {
"warning-foreground": "oklch(0.1660 0.0283 203.3400)"
},
"dark": {
"warning-foreground": "oklch(0.1660 0.0283 203.3400)"
}
}
```

Same value in both themes (already true for this token everywhere else in registry.json). Pushed this to #1034, and it's green there now — Build/Lint/Format/Typecheck/Check Dependency Consistency/registry check all pass. Feel free to pull the same one-line change onto this branch whenever your access is sorted.

@frankkluijtmans frankkluijtmans left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Check @petervachon 's comments. Do we need some format/lint fixups?

Adds a signal-bar AI confidence chip (high/medium/low/unknown) with
min/med/max density variants, a tooltip on every chip, and a popover for
factor breakdowns and next-step CTAs, plus an opt-in one-time acquire
animation that respects prefers-reduced-motion.

The action CTA (nextStep) is required for medium/low confidence per team
decision, and optional for high/unknown, enforced via a discriminated
union type. Level labels and default explanations resolve through
react-i18next under the confidence_signal_* prefix.

Registered in registry.json with docs at /components/confidence-signal.
Copilot AI review requested due to automatic review settings August 18, 2026 15:29
@ChloeDalyUiPath

Copy link
Copy Markdown
Author

Thanks @petervachon — pulled your fix onto this branch (21e7b1e0).

First: you were right that the registry check never actually ran here. I'd been reporting "Apollo Vertex Registry Check ✓" from the check-run list, but that was the fork-gated job reporting a skip, not a real pass. My mistake, and it's exactly why the missing cssVars slipped through. Mirroring to #1034 was the right call.

I reproduced the failure locally to confirm before fixing:

$ node --experimental-strip-types .github/scripts/test-registry/check-css-vars.ts
@uipath/confidence-signal uses Apollo CSS variables that are not provided by its registry item or registryDependencies:
  - cssVars.light["warning-foreground"]
  - cssVars.dark["warning-foreground"]

Applied the light/dark blocks exactly as you specified, same value in both, matching alert and badge. Now:

$ node --experimental-strip-types .github/scripts/test-registry/check-css-vars.ts
Registry CSS variable check passed.

@frankkluijtmans — on your question about format/lint fixups: no, nothing outstanding. Ran them just now against this branch:

  • pnpm format:checkChecked 574 files. No fixes applied.
  • pnpm lintFound 0 warnings and 0 errors.
  • pnpm typecheck → 7/7 passing, forced fresh (no Turbo cache)
  • pnpm lint:deps → 0 errors (the 10 warnings are pre-existing repo-wide circular-import notices, not from this PR)
  • pnpm registry:build → clean

So the only real defect was the cssVars one, and that's now fixed here too. Since this branch still can't run CI from a fork, #1034 remains the source of truth for green checks.

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.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

apps/apollo-vertex/registry/confidence-signal/confidence-signal-cta.tsx:29

  • Branching on if (cta.href) is a truthiness check, so an empty string (href: \"\") will incorrectly fall through to the button path (and can yield onClick undefined). Prefer checking defined-ness (cta.href !== undefined) or discriminating via 'href' in cta to match the TypeScript intent of the union.
  if (cta.href) {
    return (

apps/apollo-vertex/registry/confidence-signal/confidence-signal-factors.tsx:34

  • React keys must be unique among siblings for reconciliation to work correctly. If two factors share the same label and value, this key collides and can cause incorrect row updates. Use a stable unique id if available, or include the array index as a last resort (e.g., ${label}:${value}:${index}) to guarantee uniqueness.
      {factors.map((factor) => (
        // Labels are not guaranteed unique, so pair label with value: two rows
        // that match on both are indistinguishable to the reader anyway.
        <ConfidenceSignalFactorRow
          key={`${factor.label}:${factor.value}`}
          factor={factor}
        />

apps/apollo-vertex/registry/confidence-signal/confidence-signal-chip.tsx:42

  • The comment is misleading: placing aria-label before {...props} means a caller-provided aria-label will override accessibleLabel (the default is lost). Either update the comment to reflect override behavior, or change the code to preserve the default when the caller doesn’t provide one (e.g., derive aria-label from props['aria-label'] ?? accessibleLabel).
      // Before the spread, so a caller can supply a more contextual label
      // ("Confidence for Acme Health Plan: high") without losing the default.
      aria-label={accessibleLabel}
      {...props}

Comment on lines +98 to +103
// Suppressed while the popover is open so the two never stack.
const tooltip = (
<Tooltip open={tooltipOpen && !detailsOpen} onOpenChange={setTooltipOpen}>
<TooltipTrigger asChild>
{hasDetails ? <PopoverTrigger asChild>{chip}</PopoverTrigger> : chip}
</TooltipTrigger>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants