Skip to content

feat(one): add candidate and job-posting entity-ref renderers#3800

Open
ReddyWish wants to merge 6 commits intomainfrom
feat/ats-entity-ref
Open

feat(one): add candidate and job-posting entity-ref renderers#3800
ReddyWish wants to merge 6 commits intomainfrom
feat/ats-entity-ref

Conversation

@ReddyWish
Copy link
Copy Markdown
Contributor

@ReddyWish ReddyWish commented Mar 31, 2026

Summary

Add entity-ref support for ATS candidates and job postings so their names render as clickable hover-card links in the AI chat UI, matching the existing PersonEntityRef pattern.

New entity-ref types

Type Trigger style Avatar Hover card info
candidate Full Name Person (initials/photo) Name, source
job-posting Job Title (no @) None Title, status, location

Refactor: entityResolversentityRefs

Replaces the entityResolvers prop with a new entityRefs prop that groups:

  • resolvers — async data-fetching functions (same as before: person, candidate, jobPosting, searchPersons)
  • urls — URL builder functions per entity type (e.g. person: (id) => /employees/${id}``)

This is a breaking change — consumers must migrate from entityResolvers={...} to entityRefs={{ resolvers: {...}, urls: {...} }}.

Key behavior changes:

  • Hardcoded entity URLs removed — hover cards only show a "View" link when the corresponding urls builder is provided
  • When no URL builder exists for an entity type, the hover card omits the navigation action entirely
  • useMentions now receives searchPersons directly instead of the full EntityResolvers object

New types exported: EntityRefs, EntityUrlBuilders

Changes

Commit 1 — Entity-ref feature:

  • CandidateEntityRef.tsx — Renderer + 6 tests
  • JobPostingEntityRef.tsx — Renderer + 6 tests
  • entityRefRegistry.ts — Register both new types
  • types.ts — Add candidate and jobPosting resolvers to EntityResolvers
  • i18n-provider-defaults.ts — Add viewCandidate and viewJobPosting keys

Commit 2 — Bug fix (independent):

  • ChatTextarea.tsx + F0MessageCreditsWarning.tsx — Add optional chaining to creditWarning translation accesses to prevent crash when consuming apps don't provide those translations

Commit 3 — entityRefs refactor:

  • entityRef/types.ts — Add EntityUrlBuilders and EntityRefs types
  • types.ts, internal-types.ts — Replace entityResolvers with entityRefs
  • AiChatStateProvider.tsx, F0AiChat.tsx — Thread entityRefs through context
  • PersonEntityRef.tsx, CandidateEntityRef.tsx, JobPostingEntityRef.tsx — Use URL builders from context, hide action when no URL
  • ChatTextarea.tsx, useMentions.ts — Pass searchPersons directly
  • index.stories.tsx — Updated examples with entityRefs including URL builders
  • ENTITY_REFS.md — Updated architecture docs
  • All entity-ref tests updated

Related PRs

Testing

  • 33/33 entity-ref tests pass (12 new + 21 existing)
  • Build passes
  • Unknown entity-ref types degrade gracefully to plain <span> text
  • Each repo can merge independently — unknown types fall back safely

Add entity-ref support for ATS candidates and job postings so their
names render as clickable hover-card links in the AI chat UI.

New renderers follow the existing PersonEntityRef pattern:
- CandidateEntityRef: @-prefixed trigger, person avatar, source info
- JobPostingEntityRef: plain text trigger, title + status/location

Also registers both types in entityRefRegistry, extends EntityResolvers
type, and adds i18n keys for view links.
Add optional chaining to creditWarning translation accesses in
ChatTextarea and F0MessageCreditsWarning. Without this, apps that
don't provide creditWarning translations crash on render.
@ReddyWish ReddyWish requested a review from a team as a code owner March 31, 2026 07:58
Copilot AI review requested due to automatic review settings March 31, 2026 07:58
@github-actions github-actions bot added feat react Changes affect packages/react labels Mar 31, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

✅ No New Circular Dependencies

No new circular dependencies detected. Current count: 0

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds ATS entity-reference renderers to the AI chat markdown pipeline so candidate and job-posting references render as hover-card links (following the existing PersonEntityRef pattern), plus a small fix to avoid crashes when credit-warning translations are missing.

Changes:

  • Introduce CandidateEntityRef and JobPostingEntityRef (with profile types) that fetch via entityResolvers.* and render an EntityRefHoverCard.
  • Register the new entity-ref renderer types in the entity-ref registry and extend EntityResolvers accordingly.
  • Add i18n default keys for the new hover-card “view” actions and harden credit-warning translation access in AI chat UI.

Reviewed changes

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

Show a summary per file
File Description
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/types.ts Extends EntityResolvers with candidate and jobPosting resolvers.
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/jobPosting/types.ts Adds JobPostingProfile type for hover-card mapping.
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/jobPosting/JobPostingEntityRef.tsx New job-posting entity-ref renderer using EntityRefHoverCard.
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/jobPosting/tests/JobPostingEntityRef.test.tsx Coverage for job-posting entity-ref behavior (trigger, loading/error, caching).
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/candidate/types.ts Adds CandidateProfile type for hover-card mapping.
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/candidate/CandidateEntityRef.tsx New candidate entity-ref renderer using EntityRefHoverCard.
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/candidate/tests/CandidateEntityRef.test.tsx Coverage for candidate entity-ref behavior (trigger, loading/error, caching).
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/components/entityRefRegistry.ts Registers candidate and job-posting renderer types.
packages/react/src/sds/ai/F0AiChat/components/input/ChatTextarea.tsx Adds optional chaining / fallbacks for credit-warning translations.
packages/react/src/sds/ai/F0AiChat/actions/core/messageCreditsWarning/F0MessageCreditsWarning.tsx Adds optional chaining for credit-warning banner translations.
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts Adds default translations for ai.viewCandidate and ai.viewJobPosting.
Comments suppressed due to low confidence (3)

packages/react/src/sds/ai/F0AiChat/components/input/ChatTextarea.tsx:139

  • creditWarningConfig.soft.text can now be undefined when consuming apps omit ai.creditWarning translations, which results in an empty warning banner. Prefer using the i18n helper (translation.t("ai.creditWarning.soft")) or a non-empty fallback so the banner always has user-visible text when creditWarning is enabled.
  const creditWarningConfig = {
    soft: {
      text: translation.ai?.creditWarning?.soft,
      bg: "bg-f1-background-info",
      fontColor: "text-f1-foreground-info",
      formBorder: "[&_form]:border-f1-border-info",
    },

packages/react/src/sds/ai/F0AiChat/components/input/ChatTextarea.tsx:365

  • F0Button label is required for accessibility, but these changes can set it (and tooltip) to an empty string when translations are missing. Consider using translation.t(...) (which returns a safe fallback) or skipping rendering the buttons when the label translation is unavailable, to avoid unlabeled interactive controls.
          {onGetCredits && (
            <F0Button
              label={translation.ai?.creditWarning?.getCredits ?? ""}
              size="sm"
              variant="outline"
              tooltip={translation.ai?.creditWarning?.getCredits ?? ""}
              onClick={onGetCredits}
            />
          )}
          {onDismissCreditWarning && (
            <F0Button
              label={translation.ai?.creditWarning?.dismiss ?? ""}
              size="sm"
              variant="ghost"
              icon={Cross}
              hideLabel
              onClick={onDismissCreditWarning}

packages/react/src/sds/ai/F0AiChat/actions/core/messageCreditsWarning/F0MessageCreditsWarning.tsx:25

  • F0Alert requires title/description strings, but optional chaining here can still produce undefined when translations are missing, leaving the banner blank (and weakening type safety). Prefer translations.t("ai.creditWarning.messageBanner.title") / .description / .actionLabel (or explicit fallbacks) so the alert always renders meaningful content.
    <F0Alert
      title={translations?.ai?.creditWarning?.messageBanner?.title}
      variant="warning"
      description={translations?.ai?.creditWarning?.messageBanner?.description}
      action={
        actionHref
          ? {
              label: translations?.ai?.creditWarning?.messageBanner?.actionLabel,
              onClick: () => {
                window.location.href = actionHref
              },
            }
          : undefined
      }

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

📦 Alpha Package Version Published

Use pnpm i github:factorialco/f0#npm/alpha-pr-3800 to install the package

Use pnpm i github:factorialco/f0#13f506d336df60fe3f8ecc3d8488011fcc0d9b1f to install this specific commit

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

🔍 Visual review for your branch is published 🔍

Here are the links to:

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 31, 2026

Coverage Report for packages/react

Status Category Percentage Covered / Total
🔵 Lines 45.52% 11024 / 24213
🔵 Statements 44.81% 11367 / 25363
🔵 Functions 37.55% 2498 / 6652
🔵 Branches 37.22% 7195 / 19329
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/react/src/lib/providers/i18n/i18n-provider-defaults.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/F0AiChat.tsx 33.33% 0% 0% 33.33% 41-63, 71-77, 82-111
packages/react/src/sds/ai/F0AiChat/index.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/internal-types.ts 25% 100% 25% 33.33% 200-209
packages/react/src/sds/ai/F0AiChat/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/input/ChatTextarea.tsx 1.75% 0% 0% 1.81% 42-371
packages/react/src/sds/ai/F0AiChat/components/input/useMentions.ts 1.04% 0% 0% 1.12% 73-114, 167-195, 209-495
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/components/entityRefRegistry.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/candidate/CandidateEntityRef.tsx 100% 66.66% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/candidate/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/jobPosting/JobPostingEntityRef.tsx 100% 66.66% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/jobPosting/types.ts 100% 100% 100% 100%
packages/react/src/sds/ai/F0AiChat/components/markdownRenderers/entityRef/entities/person/PersonEntityRef.tsx 100% 66.66% 100% 100%
packages/react/src/sds/ai/F0AiChat/providers/AiChatStateProvider.tsx 4.95% 0% 0% 5.1% 37-45, 69-310, 317-374
Generated in workflow #12469 for commit d2b2bde by the Vitest Coverage Report Action

@ReddyWish ReddyWish changed the title feat(ai): add candidate and job-posting entity-ref renderers feat(one): add candidate and job-posting entity-ref renderers Mar 31, 2026
ask: "Ask One",
viewProfile: "View profile",
viewCandidate: "View candidate",
viewJobPosting: "View job posting",
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.

Maybe we need to change all the copies and just put "view", if not we are going to have a million of copies. Can you change it for employee to?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@siguenzaraul 100% agree.addressed

return (
<F0Alert
title={translations?.ai?.creditWarning.messageBanner?.title}
title={translations?.ai?.creditWarning?.messageBanner?.title}
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.

Why?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@siguenzaraul Had annoying type error locally due to undefined, did you skip the optional chaining on purpose here? (for loud fail for exmpl) if so I will revert it right away

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.

To be honest, this change isn't mine, it just seemed odd to me in this PR

const creditWarningConfig = {
soft: {
text: translation.ai.creditWarning.soft,
text: translation.ai?.creditWarning?.soft,
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.

Same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@siguenzaraul same as above

…move @ from candidate trigger

Address reviewer feedback:
- Replace viewProfile, viewCandidate, viewJobPosting with single 'view: View' key
- Update PersonEntityRef, CandidateEntityRef, JobPostingEntityRef to use ai.view
- Remove @ prefix from candidate trigger (only employees get @)
- Update candidate tests accordingly
…stories

- Add mockCandidates, mockCandidateResolver, mockJobPostings, mockJobPostingResolver
- Wire candidate and jobPosting resolvers into Default and FullscreenWithActions stories
- Re-export CandidateProfile and JobPostingProfile from F0AiChat barrel types
…ations

The ?. on creditWarning was introduced to prevent crashes but
TranslationsType is fully required — creditWarning is never undefined.
Revert to the pre-existing access style in both files.
Copilot AI review requested due to automatic review settings April 1, 2026 12:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 13 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines +351 to +360
label={translation.ai.creditWarning.getCredits ?? ""}
size="sm"
variant="outline"
tooltip={translation.ai.creditWarning.getCredits}
tooltip={translation.ai.creditWarning.getCredits ?? ""}
onClick={onGetCredits}
/>
)}
{onDismissCreditWarning && (
<F0Button
label={translation.ai.creditWarning.dismiss}
label={translation.ai.creditWarning.dismiss ?? ""}
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

This change still dereferences translation.ai.creditWarning directly. If a consuming app provides an ai translation object without creditWarning (the crash described in the PR), this will still throw before the ?? "" fallback runs. Use optional chaining at the creditWarning level (or switch these reads to translation.t("ai.creditWarning.getCredits") / translation.t("ai.creditWarning.dismiss")), and ensure the earlier creditWarning.soft access is handled the same way.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +25
focusRing()
)}
{...props}
>
{label}
</button>
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

The PR description specifies candidates should render with an @Full Name trigger (matching the existing PersonEntityRef pattern), but the trigger here renders {label} without the @ prefix. Either add the @ prefix in the trigger (and update the test expectations), or update the PR description/spec so the UI behavior is consistent and unambiguous.

Copilot uses AI. Check for mistakes.
deleteChat: "Delete chat",
ask: "Ask One",
viewProfile: "View profile",
view: "View",
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

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

Renaming the translation key from ai.viewProfile to ai.view is a breaking API change for consumers that call i18n.t("ai.viewProfile") or override that key in their translations. Consider keeping viewProfile as a deprecated alias (pointing to the same string) for backwards compatibility, or clearly documenting this breaking change for downstream apps.

Suggested change
view: "View",
view: "View",
viewProfile: "View", // Deprecated alias for backwards compatibility

Copilot uses AI. Check for mistakes.
const resolver = entityResolvers?.candidate
const i18n = useI18n()

const candidateUrl = `/recruitment/candidates/${id}/applications`
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.

I think we need to stop doing this, was my fault, but probably we need to put it in the entities configuration. Now we have EntityResolvers, maybe we can have EntitUrls or something like that

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.

let my modify this PR and add it, also to the .md

Group resolvers and URL builders under a single entityRefs prop so
both data fetching and navigation are configurable by the consumer.
Removes hardcoded entity URLs — hover cards omit the navigation
action when no URL builder is provided.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat react Changes affect packages/react

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants