[Payment due @brunovjk] Surface RuleBot in the Agent rules section#94131
Conversation
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index a45d36e6027..8c54084ed69 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -7464,7 +7464,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
agentRules: {
title: 'Agentenregeln',
subtitle: 'Legen Sie Regeln fest, wie KI-Agenten mit Ausgaben in diesem Workspace umgehen.',
- enforcedBy: 'Agentenregeln werden durchgesetzt von',
+ enforcedBy: 'Agentregeln werden erzwungen durch',
ruleBotName: 'RuleBot',
addRule: 'Agentenregel hinzufügen',
findRule: 'Agentenregel finden',
@@ -9720,7 +9720,6 @@ Hier ist ein *Testbeleg*, um dir zu zeigen, wie es funktioniert:`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 805ca98ce21..a846c2c164f 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -7357,7 +7357,7 @@ ${amount} para ${merchant} - ${date}`,
agentRules: {
title: 'Reglas del agente',
subtitle: 'Configura reglas para cómo los agentes de IA gestionan los gastos en este espacio de trabajo.',
- enforcedBy: 'Las reglas de agente son aplicadas por',
+ enforcedBy: 'Las reglas del agente se aplican mediante',
ruleBotName: 'RuleBot',
addRule: 'Añadir regla de agente',
findRule: 'Encontrar regla de agente',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 52ac3d9e667..be90897653b 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -7491,7 +7491,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e
agentRules: {
title: 'Règles d’agent',
subtitle: 'Définissez des règles pour déterminer comment les agents IA gèrent les dépenses dans cet espace de travail.',
- enforcedBy: 'Les règles d’agent sont appliquées par',
+ enforcedBy: 'Les règles des agents sont appliquées par',
ruleBotName: 'RuleBot',
addRule: 'Ajouter une règle d’agent',
findRule: 'Rechercher une règle d’agent',
@@ -9752,7 +9752,6 @@ Voici un *reçu test* pour vous montrer comment ça fonctionne :`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 019cd933b85..1e0ea7cab36 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -7449,7 +7449,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`,
agentRules: {
title: 'Regole agente',
subtitle: 'Imposta le regole su come gli agenti IA gestiscono le spese in questo spazio di lavoro.',
- enforcedBy: 'Le regole dell’agente sono applicate da',
+ enforcedBy: 'Le regole degli agenti sono applicate da',
ruleBotName: 'RuleBot',
addRule: 'Aggiungi regola agente',
findRule: 'Trova regola agente',
@@ -9708,7 +9708,6 @@ Ecco una *ricevuta di prova* per mostrarti come funziona:`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 2fd321c57ad..068a3e82bab 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -7364,7 +7364,7 @@ ${reportName}`,
agentRules: {
title: 'エージェントルール',
subtitle: 'このワークスペースで AI エージェントが経費を処理する方法のルールを設定します。',
- enforcedBy: 'エージェントルールの適用者:',
+ enforcedBy: 'エージェントルールは次によって適用されます',
ruleBotName: 'RuleBot',
addRule: 'エージェントルールを追加',
findRule: 'エージェントルールを検索',
@@ -9585,7 +9585,6 @@ ${reportName}`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 4bab008c3ef..d6056034b1a 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -9674,7 +9674,6 @@ Hier is een *proefbon* om je te laten zien hoe het werkt:`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index c459c60173c..73df8a8c7da 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -7416,7 +7416,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`,
agentRules: {
title: 'Zasady agenta',
subtitle: 'Ustaw zasady dotyczące tego, jak agenci AI obsługują wydatki w tym obszarze roboczym.',
- enforcedBy: 'Reguły agenta są egzekwowane przez',
+ enforcedBy: 'Zasady agenta są egzekwowane przez',
ruleBotName: 'RuleBot',
addRule: 'Dodaj regułę agenta',
findRule: 'Znajdź regułę agenta',
@@ -9659,7 +9659,6 @@ Oto *paragon testowy*, żeby pokazać Ci, jak to działa:`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index fd8aea647ea..1e4ff576e59 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -7418,7 +7418,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`,
agentRules: {
title: 'Regras do agente',
subtitle: 'Defina regras para como os agentes de IA lidam com despesas neste workspace.',
- enforcedBy: 'As regras de agente são aplicadas por',
+ enforcedBy: 'As regras do agente são aplicadas por',
ruleBotName: 'RuleBot',
addRule: 'Adicionar regra de agente',
findRule: 'Encontrar regra de agente',
@@ -9663,7 +9663,6 @@ Aqui está um *comprovante de teste* para mostrar como funciona:`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index a8c8cd803bb..7197edde494 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -7223,7 +7223,7 @@ ${reportName}`,
agentRules: {
title: '代理规则',
subtitle: '为此工作区设置 AI 代理处理报销的规则。',
- enforcedBy: '代理规则的执行者:',
+ enforcedBy: '代理规则强制执行者:',
ruleBotName: 'RuleBot',
addRule: '添加代理规则',
findRule: '查找代理规则',
@@ -9403,7 +9403,6 @@ ${reportName}`,
pdfFailedBody: 'Your file could not be generated. Try again, or reach out to Concierge for help.',
readyPartialBody: ({count, total}: {count: number; total: number}) =>
`${count} of ${total} reports exported. If it didn't automatically download, use the button below. See which reports failed in <concierge-link>Concierge</concierge-link>.`,
-
close: 'Close',
},
domain: {
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
| renderTitle={renderTitle} | ||
| subtitle={translate('workspace.rules.agentRules.subtitle')} | ||
| subtitleMuted | ||
| renderSubtitle={renderSubtitle} |
There was a problem hiding this comment.
❌ CLEAN-REACT-PATTERNS-1 (docs)
This migrates the consumer from the subtitle string prop to the renderSubtitle render-function prop. Render-function props are configuration disguised as flexibility — Section must own the slot and manage the fallback between renderSubtitle() and the default subtitle. The rule calls out renderSubtitle explicitly as a Case 5 anti-pattern, and each new consumer cements the config-heavy API and makes a future compositional refactor harder. New consumers of config-heavy APIs should be flagged at the same severity as the creator.
Prefer composing the subtitle as a child block rather than passing a render function, e.g.:
<Section isCentralPane renderTitle={renderTitle} childrenStyles={[styles.gap3]}>
<Section.Subtitle>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.rules.agentRules.subtitle')}</Text>
{!!ruleBotAccountID && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.rules.agentRules.enforcedBy')}</Text>
<UserPill accountID={ruleBotAccountID} avatar={ruleBot?.avatar} displayName={ruleBotDisplayName} email={ruleBot?.login} />
</View>
)}
</Section.Subtitle>
{/* ...rest of section content... */}
</Section>If Section does not yet expose subtitle composition, advocate for a compositional sub-component instead of adopting the render-function prop.
Reviewed at: 4f4a045 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
(Yuwen's Agent) Keeping renderSubtitle here for consistency — the sibling IndividualExpenseRulesSection in this same folder uses the identical renderSubtitle={() => (…)} pattern, and Section only exposes subtitle/renderSubtitle (there's no compositional subtitle slot). Adopting a Section.Subtitle API would be a separate Section refactor, out of scope for this PR.
| </View> | ||
| ); | ||
|
|
||
| const renderSubtitle = () => ( |
There was a problem hiding this comment.
❌ CLEAN-REACT-PATTERNS-4 (docs)
renderSubtitle is an internal helper that builds a deeply nested JSX tree inline (nested Views, two Text nodes, a conditional {!!ruleBotAccountID && ...} block, and a UserPill), hiding the real render tree from the return statement. Internal functions that return non-trivial nested JSX should be extracted into their own focused component with explicit props.
Extract this into a dedicated component (e.g. AgentRulesSubtitle) that receives ruleBotAccountID, ruleBot, and ruleBotDisplayName (or fetches them itself), and render it directly in the JSX tree:
function AgentRulesSubtitle({ruleBotAccountID, ruleBot, ruleBotDisplayName}: AgentRulesSubtitleProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
return (
<View style={[styles.mt2, styles.gap2]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.rules.agentRules.subtitle')}</Text>
{!!ruleBotAccountID && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.rules.agentRules.enforcedBy')}</Text>
<UserPill accountID={ruleBotAccountID} avatar={ruleBot?.avatar} displayName={ruleBotDisplayName} email={ruleBot?.login} />
</View>
)}
</View>
);
}This keeps the section's render tree visible and makes the subtitle independently testable and memoizable by React Compiler.
Reviewed at: 4f4a045 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
(Yuwen's Agent) Leaving the subtitle inline to match the sibling IndividualExpenseRulesSection, which builds its renderSubtitle the same way. The tree here is small (one conditional row with a UserPill), so a one-off extracted component would diverge from the established pattern in this folder. Happy to revisit if we standardize subtitle extraction across the rules sections.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
|
Reviewing ASAP. |
| <View style={[styles.mt2, styles.gap2]}> | ||
| <Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.rules.agentRules.subtitle')}</Text> | ||
| {!!ruleBotAccountID && ( | ||
| <View style={[styles.flexRow, styles.alignItemsCenter, styles.gap2]}> |
There was a problem hiding this comment.
Instead of gap2, which is 8px, we might want to try gap1.5 to give us 6px? Not sure if that exists, but it might feel a little better.
There was a problem hiding this comment.
@shawnborton here it is with 6px - is this moving the needle for ya? Maybe we put it in line with the above description, or at the bottom of the card?
There was a problem hiding this comment.
That looks good to me 👍
|
I noticed two details:
Screen.Recording.2026-06-23.at.12.15.34.mov@yuwenmemon is this expected? Thanks. |
|
@brunovjk yeah, I think both are fine. |
Reviewer Checklist
Screenshots/VideosAndroid: HybridApp94131_android_native.movAndroid: mWeb Chrome94131_android_web.moviOS: HybridApp94131_ios_native.moviOS: mWeb Safari94131_ios_web.movMacOS: Chrome / Safari94131_web_chrome.mov |
brunovjk
left a comment
There was a problem hiding this comment.
LGTM, taking into account the points mentioned here. @yuwenmemon do you think we should update the PR description to take these details into account? Thanks.
I asked to confirm the translation here.
|
🎯 @brunovjk, thanks for reviewing and testing this PR! 🎉 A payment issue will be created for your review once this PR is deployed to production. If payment is not needed (e.g., regression PR review fix etc), react with 👎 to this comment to prevent the payment issue from being created. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🚧 puneetlath has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/puneetlath in version: 9.4.20-0 🚀
|
📚 Help site reviewYes — a small help site update is warranted. This PR surfaces RuleBot directly in the Agent rules section under Workspace > Rules (the new Agent rules are enforced by [RuleBot] line). The existing Agent Rules articles already document that RuleBot is created automatically and enforces Agent Rules, but they didn't mention that the section now visually surfaces the RuleBot agent. Draft PR: #94636 I added one sentence to @yuwenmemon, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
Deploy Blocker #94642 was identified to be related to this PR. |
|
Deploy Blocker #94643 was identified to be related to this PR. |
|
🚀 Deployed to production by https://github.com/blimpich in version: 9.4.20-1 🚀
Bundle Size Analysis (Sentry): |
|
🤖 Payment issue created: #94734 |
@puneetlath @brunovjk please review
cc @shawnborton
Explanation of Change
When an admin creates their first Agent rule, the backend provisions a "RuleBot" agent and adds it to the workspace, but nothing ties that bot to the rules it enforces — so it feels disconnected.
This surfaces RuleBot in Workspace > Rules: under the Agent rules header, a line reads "Agent rules are enforced by [RuleBot]", where RuleBot is rendered as a
UserPill(avatar + name) built from the actual agent stored on the policy (policy.ruleBotAccountID). The line appears once a RuleBot has been provisioned (i.e. after the first Agent rule); before that, only the section subtitle shows.Depends on https://github.com/Expensify/Auth/pull/22411, which sends
ruleBotAccountIDto the client on a full OpenApp/ReconnectApp load — that should deploy first. Until then the field only arrives incrementally on the device that created the rule.Fixed Issues
$ https://github.com/Expensify/Expensify/issues/647929
PROPOSAL: N/A (internal)
Tests
customAgent) beta, open a workspace and go to Rules.Offline tests
QA Steps
Same as Tests (requires the
customAgentbeta on the account).PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Empty state does not show this UI:
