feat(one): add candidate and job-posting entity-ref renderers#3800
feat(one): add candidate and job-posting entity-ref renderers#3800
Conversation
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.
✅ No New Circular DependenciesNo new circular dependencies detected. Current count: 0 |
There was a problem hiding this comment.
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
CandidateEntityRefandJobPostingEntityRef(with profile types) that fetch viaentityResolvers.*and render anEntityRefHoverCard. - Register the new entity-ref renderer types in the entity-ref registry and extend
EntityResolversaccordingly. - 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.textcan now beundefinedwhen consuming apps omitai.creditWarningtranslations, 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 whencreditWarningis 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
labelis required for accessibility, but these changes can set it (and tooltip) to an empty string when translations are missing. Consider usingtranslation.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
F0Alertrequirestitle/descriptionstrings, but optional chaining here can still produceundefinedwhen translations are missing, leaving the banner blank (and weakening type safety). Prefertranslations.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
}
📦 Alpha Package Version PublishedUse Use |
🔍 Visual review for your branch is published 🔍Here are the links to: |
| ask: "Ask One", | ||
| viewProfile: "View profile", | ||
| viewCandidate: "View candidate", | ||
| viewJobPosting: "View job posting", |
There was a problem hiding this comment.
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?
| return ( | ||
| <F0Alert | ||
| title={translations?.ai?.creditWarning.messageBanner?.title} | ||
| title={translations?.ai?.creditWarning?.messageBanner?.title} |
There was a problem hiding this comment.
@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
There was a problem hiding this comment.
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, |
…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.
| 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 ?? ""} |
There was a problem hiding this comment.
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.
| focusRing() | ||
| )} | ||
| {...props} | ||
| > | ||
| {label} | ||
| </button> |
There was a problem hiding this comment.
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.
| deleteChat: "Delete chat", | ||
| ask: "Ask One", | ||
| viewProfile: "View profile", | ||
| view: "View", |
There was a problem hiding this comment.
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.
| view: "View", | |
| view: "View", | |
| viewProfile: "View", // Deprecated alias for backwards compatibility |
| const resolver = entityResolvers?.candidate | ||
| const i18n = useI18n() | ||
|
|
||
| const candidateUrl = `/recruitment/candidates/${id}/applications` |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
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
PersonEntityRefpattern.New entity-ref types
candidateFull Namejob-postingJob Title(no @)Refactor:
entityResolvers→entityRefsReplaces the
entityResolversprop with a newentityRefsprop 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={...}toentityRefs={{ resolvers: {...}, urls: {...} }}.Key behavior changes:
urlsbuilder is provideduseMentionsnow receivessearchPersonsdirectly instead of the fullEntityResolversobjectNew types exported:
EntityRefs,EntityUrlBuildersChanges
Commit 1 — Entity-ref feature:
CandidateEntityRef.tsx— Renderer + 6 testsJobPostingEntityRef.tsx— Renderer + 6 testsentityRefRegistry.ts— Register both new typestypes.ts— AddcandidateandjobPostingresolvers toEntityResolversi18n-provider-defaults.ts— AddviewCandidateandviewJobPostingkeysCommit 2 — Bug fix (independent):
ChatTextarea.tsx+F0MessageCreditsWarning.tsx— Add optional chaining tocreditWarningtranslation accesses to prevent crash when consuming apps don't provide those translationsCommit 3 — entityRefs refactor:
entityRef/types.ts— AddEntityUrlBuildersandEntityRefstypestypes.ts,internal-types.ts— ReplaceentityResolverswithentityRefsAiChatStateProvider.tsx,F0AiChat.tsx— ThreadentityRefsthrough contextPersonEntityRef.tsx,CandidateEntityRef.tsx,JobPostingEntityRef.tsx— Use URL builders from context, hide action when no URLChatTextarea.tsx,useMentions.ts— PasssearchPersonsdirectlyindex.stories.tsx— Updated examples withentityRefsincluding URL buildersENTITY_REFS.md— Updated architecture docsRelated PRs
entityRefs)Testing
<span>text