Skip to content

refactor(platform): extract organization switcher into dedicated nav button#1640

Merged
Israeltheminer merged 4 commits into
mainfrom
refactor/organization-button-extraction
Apr 27, 2026
Merged

refactor(platform): extract organization switcher into dedicated nav button#1640
Israeltheminer merged 4 commits into
mainfrom
refactor/organization-button-extraction

Conversation

@Israeltheminer

@Israeltheminer Israeltheminer commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add OrganizationButton and shared OrganizationListPanel so org switching/deletion lives in the nav rail instead of the user-button dropdown and the organization settings page.
  • Slim down user-button and organization-settings to remove the org list rendering, switching, and delete flow they no longer own.
  • Add remove-rule confirmation copy to governance editors (budget, chat filter, feature flags, model access, etc.) and keep en/de/fr message catalogs in parity.

Test plan

  • Verify the nav rail shows the new org switcher button alongside the user button on desktop and mobile.
  • Switch organizations from the new dropdown — selected team should clear and routing should land on the new org.
  • Delete a non-default organization from the new panel and confirm the success toast + redirect.
  • Open governance editors and confirm the remove-rule confirm dialog renders translated copy in en, de, and fr.

Summary by CodeRabbit

  • New Features

    • Added organization switcher button for easier account switching
    • Introduced confirmation dialogs for destructive actions across governance settings
    • Enhanced language selection UI in user menu
  • Improvements

    • Added loading indicators for model selector data fetching
    • Improved chat history sidebar animations
    • Streamlined organization and settings management workflows

…button

Move org list, switching, and deletion out of the user-button dropdown and
organization settings page into a standalone OrganizationButton fed by a
shared OrganizationListPanel. Adds remove-rule confirmation copy across
governance editors and aligns en/de/fr message catalogs.
@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors organization management by introducing an OrganizationButton component integrated into the main navigation and creating an OrganizationListPanel for switching and deleting organizations. The UserButton component is simplified by removing organization switching logic and navigation callbacks, while adding localized language selection via radio groups. Organization management is removed from the settings page. Multiple governance editor components now use confirmation dialogs for destructive actions instead of immediate deletions. Navigation components are updated to include the new organization switcher. UI improvements are made to chat history sidebar animations and model selector loading states, and translation keys are added/removed accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: extracting the organization switcher into a dedicated navigation button component.
Description check ✅ Passed The description covers the main objectives, implementation details, and test plan. The pre-merge checklist is appropriately marked where applicable, including message catalog updates for multi-language support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/organization-button-extraction

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@services/platform/app/components/organization-button.tsx`:
- Around line 51-63: The icon-only org switcher trigger (the JSX stored in
triggerContent used by DropdownMenu) lacks an accessible name when label is
falsy; add a translated aria-label on the actual trigger element (the <div>
returned as triggerContent) whenever label is absent using the project's i18n
helper (e.g., useTranslations or t) instead of hardcoded English, and apply the
same change to the second icon-only trigger around the code at the other
occurrence (lines 76-95) so screen readers receive an accessible name separate
from the tooltip.

In `@services/platform/app/components/ui/navigation/mobile-navigation.tsx`:
- Around line 165-168: The bottom button container currently uses "py-2" which
yields ~36px height for UserButton and OrganizationButton; update the
container's tailwind padding from "py-2" to "py-3" (or a value that ensures
≥44px total height) in the div that wraps UserButton and OrganizationButton so
both components meet the 44×44 CSS pixel mobile touch target; verify the final
computed height and adjust to "py-4" only if needed to guarantee the minimum
touch size.

In `@services/platform/app/components/user-button.tsx`:
- Around line 237-242: The current mapping forces any non-en/de locale to 'fr';
change currentLocaleValue to derive the base locale (const base =
locale.split('-')[0]) and then map explicitly only for supported options: if
base === 'en' -> 'en', else if base === 'de' -> 'de', else if base === 'fr' ->
'fr', otherwise use base (or an empty/unsupported value) so unknown locales
(e.g., 'es', 'it') are not incorrectly treated as French; apply the same change
to the other occurrence around the currentLocaleValue logic at the 252-260
block.

In `@services/platform/app/features/chat/components/chat-history-sidebar.tsx`:
- Around line 426-427: The added rotation/transition classes in
chat-history-sidebar.tsx need motion-reduce fallbacks: update the class strings
that include 'transition-transform duration-300 ease-out' and the conditional
'-rotate-90' (around the archivedExpanded toggle) to also include Tailwind
motion-reduce variants (e.g., prefacing overrides with 'motion-reduce:') so
animations are disabled/reduced for users who prefer reduced motion; apply the
same change to the other occurrence mentioned (the class group at the 444-447
block) so both the open/close rotation and any duration/transform transitions
respect prefers-reduced-motion.
- Around line 442-450: The collapsed archived section currently only sets
aria-hidden based on archivedExpanded, but its descendant interactive elements
(buttons/links) remain tabbable; update the container div and rendering logic
around archivedExpanded so the collapsed state is truly non-interactive: when
archivedExpanded is false, either remove the archived panel from the DOM (only
render when true) or mark the container with inert (or hidden) plus aria-hidden
and ensure descendant interactive controls are not focusable (e.g., set
tabIndex=-1 or avoid rendering Button components) — change the container div
(the element using archivedExpanded and aria-hidden) and/or the descendant
button/link render paths so that when !archivedExpanded they cannot receive
focus or pointer events.

In
`@services/platform/app/features/organization/components/organization-list-panel.tsx`:
- Around line 179-197: The delete button is hidden by default and has too small
a hit area for touch devices; in organization-list-panel.tsx update the button's
className so it is always visible on touch/small screens and has a larger hit
target: replace the current opacity and padding utilities (e.g., 'opacity-0
group-hover/org:opacity-100 ... p-1') with responsive variants such that
small/coarse pointers show the button (for example use 'sm:opacity-0
sm:group-hover/org:opacity-100 opacity-100' or equivalent
media-query/coarse-pointer Tailwind variants) and increase padding to meet
touch-target guidance (e.g., 'p-3 sm:p-1' or otherwise ensure 44×44 on mobile
and >=24×24 elsewhere); keep the existing aria-label, onClick handler and Trash2
icon (references: setDeleteTarget, org.organizationId, org.name, Trash2) so
keyboard/focus behavior remains intact.

In
`@services/platform/app/features/settings/governance/components/chat-filter-config.tsx`:
- Around line 168-176: In confirmDeleteCategory update the editor index handling
so it doesn't become off-by-one when removing a category before the currently
edited one: after computing index and next, call setEditorIndex with a function
that if prev is null returns null, if prev === index returns null (we deleted
the edited item), if prev > index returns prev - 1 (shift editor down one),
otherwise returns prev; keep the rest of the logic (setCategories,
setDeletingIndex, saveWith(buildConfig(...))) unchanged and reference the
confirmDeleteCategory, setEditorIndex, deletingIndex and categories identifiers
when making the change.

In
`@services/platform/app/features/settings/governance/components/guardrails-overview.tsx`:
- Around line 82-96: The disabled state currently hides the explanation inside a
non-focusable Tooltip on the Badge; change the JSX in the guardrails-overview
component so that when enabled is false you render the disabledReason visibly
(e.g., display the Badge plus an adjacent text element like a <span> or <p>
showing disabledReason) instead of tooltip-only, or if you must keep the Tooltip
keep its trigger keyboard-focusable and associated with the description (make
the Badge a focusable element/button or add tabIndex={0} and aria-describedby
linking to the tooltip content). Update the conditional that renders
Tooltip/Badge around enabled to render an accessible visible copy of
disabledReason (reference variables: enabled, disabledReason and components
Tooltip/Badge in guardrails-overview.tsx).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8dc33dbf-3d19-42dd-bd73-a221c435da83

📥 Commits

Reviewing files that changed from the base of the PR and between 7141896 and 63e9050.

📒 Files selected for processing (20)
  • services/platform/app/components/organization-button.tsx
  • services/platform/app/components/ui/navigation/mobile-navigation.tsx
  • services/platform/app/components/ui/navigation/navigation.tsx
  • services/platform/app/components/user-button.tsx
  • services/platform/app/features/chat/components/chat-history-sidebar.tsx
  • services/platform/app/features/chat/components/model-selector.tsx
  • services/platform/app/features/organization/components/organization-list-panel.tsx
  • services/platform/app/features/settings/governance/components/budget-editor.tsx
  • services/platform/app/features/settings/governance/components/chat-filter-config.tsx
  • services/platform/app/features/settings/governance/components/default-model-editor.tsx
  • services/platform/app/features/settings/governance/components/feature-flags-editor.tsx
  • services/platform/app/features/settings/governance/components/guardrails-overview.tsx
  • services/platform/app/features/settings/governance/components/model-access-editor.tsx
  • services/platform/app/features/settings/governance/components/moderation-provider-config.tsx
  • services/platform/app/features/settings/organization/components/organization-settings.tsx
  • services/platform/app/hooks/use-navigation-items.ts
  • services/platform/app/routes/dashboard/$id/automations/$amId.tsx
  • services/platform/messages/de.json
  • services/platform/messages/en.json
  • services/platform/messages/fr.json

Comment on lines +51 to +63
const triggerContent = (
<div
className={cn(
'relative flex items-center rounded-lg transition-colors hover:bg-muted cursor-pointer',
label ? 'gap-3 px-3 py-2 w-full' : 'justify-center p-2',
)}
>
<Building2 className="text-muted-foreground size-5 shrink-0" />
{label && (
<span className="text-sm leading-none font-medium">{label}</span>
)}
</div>
);

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.

⚠️ Potential issue | 🟠 Major

Add an accessible name to the icon-only org switcher trigger.

When label is absent, this control is just an icon plus a tooltip. The tooltip text does not give the trigger its accessible name, so screen readers can end up announcing an unlabeled button/menu trigger. Please thread a translated aria-label onto the actual trigger element used by DropdownMenu.

As per coding guidelines, "Every icon-only button must have a translated aria-label — never hardcode English in ARIA."

Also applies to: 76-95

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/organization-button.tsx` around lines 51 -
63, The icon-only org switcher trigger (the JSX stored in triggerContent used by
DropdownMenu) lacks an accessible name when label is falsy; add a translated
aria-label on the actual trigger element (the <div> returned as triggerContent)
whenever label is absent using the project's i18n helper (e.g., useTranslations
or t) instead of hardcoded English, and apply the same change to the second
icon-only trigger around the code at the other occurrence (lines 76-95) so
screen readers receive an accessible name separate from the tooltip.

Comment on lines 165 to 168
<div className="border-border flex flex-shrink-0 flex-col gap-1 border-t px-4 py-2">
<UserButton label={tNav('account')} />
<OrganizationButton label={tNav('orgSwitcher.label')} />
</div>

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check UserButton and OrganizationButton for size/padding that affects touch targets
rg -n "className" services/platform/app/components/user-button.tsx | head -10
rg -n "className" services/platform/app/components/organization-button.tsx | head -10

Repository: tale-project/tale

Length of output: 1182


🏁 Script executed:

cat -n services/platform/app/components/user-button.tsx

Repository: tale-project/tale

Length of output: 14163


🏁 Script executed:

cat -n services/platform/app/components/organization-button.tsx

Repository: tale-project/tale

Length of output: 3602


🏁 Script executed:

sed -n '155,180p' services/platform/app/components/ui/navigation/mobile-navigation.tsx

Repository: tale-project/tale

Length of output: 713


Increase button padding to meet 44×44 CSS pixel mobile touch target requirement.

The UserButton and OrganizationButton components render with approximately 36px height (16px vertical padding + 20px icon), falling short of the 44×44 CSS pixel minimum for mobile touch targets required by WCAG 2.1 Level AA. Increase py-2 to py-3 (12px top/bottom) or higher to reach the minimum touch target size.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/ui/navigation/mobile-navigation.tsx` around
lines 165 - 168, The bottom button container currently uses "py-2" which yields
~36px height for UserButton and OrganizationButton; update the container's
tailwind padding from "py-2" to "py-3" (or a value that ensures ≥44px total
height) in the div that wraps UserButton and OrganizationButton so both
components meet the 44×44 CSS pixel mobile touch target; verify the final
computed height and adjust to "py-4" only if needed to guarantee the minimum
touch size.

Comment on lines +237 to +242
const currentLocaleValue =
locale === 'en' || locale.startsWith('en-')
? 'en'
: locale.startsWith('de')
? 'de'
: 'fr';

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.

⚠️ Potential issue | 🟡 Minor

Don't default unknown locales to French.

This normalization maps every non-en/de locale to fr, so values like es, it, or any other valid stored locale will render the French option as selected even though French is not active.

💡 Suggested fix
-    const currentLocaleValue =
-      locale === 'en' || locale.startsWith('en-')
-        ? 'en'
-        : locale.startsWith('de')
-          ? 'de'
-          : 'fr';
+    const currentLocaleValue = locale.startsWith('de')
+      ? 'de'
+      : locale.startsWith('fr')
+        ? 'fr'
+        : 'en';

Also applies to: 252-260

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/components/user-button.tsx` around lines 237 - 242, The
current mapping forces any non-en/de locale to 'fr'; change currentLocaleValue
to derive the base locale (const base = locale.split('-')[0]) and then map
explicitly only for supported options: if base === 'en' -> 'en', else if base
=== 'de' -> 'de', else if base === 'fr' -> 'fr', otherwise use base (or an
empty/unsupported value) so unknown locales (e.g., 'es', 'it') are not
incorrectly treated as French; apply the same change to the other occurrence
around the currentLocaleValue logic at the 252-260 block.

Comment on lines 426 to 427
'size-3.5 shrink-0 transition-transform duration-300 ease-out',
!archivedExpanded && '-rotate-90',

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.

⚠️ Potential issue | 🟠 Major

Add reduced-motion fallbacks for the new animations.

These new transitions should include motion-reduce: variants so users with reduced-motion preference aren’t forced through animations.

Suggested fix
-                'size-3.5 shrink-0 transition-transform duration-300 ease-out',
+                'size-3.5 shrink-0 transition-transform duration-300 ease-out motion-reduce:transition-none',
...
-              'grid transition-[grid-template-rows,opacity] duration-300 ease-out',
+              'grid transition-[grid-template-rows,opacity] duration-300 ease-out motion-reduce:transition-none',

As per coding guidelines: "Respect prefers-reduced-motion: reduce on every animation. Use the motion-reduce: Tailwind prefix for CSS overrides."

Also applies to: 444-447

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/components/chat-history-sidebar.tsx`
around lines 426 - 427, The added rotation/transition classes in
chat-history-sidebar.tsx need motion-reduce fallbacks: update the class strings
that include 'transition-transform duration-300 ease-out' and the conditional
'-rotate-90' (around the archivedExpanded toggle) to also include Tailwind
motion-reduce variants (e.g., prefacing overrides with 'motion-reduce:') so
animations are disabled/reduced for users who prefer reduced motion; apply the
same change to the other occurrence mentioned (the class group at the 444-447
block) so both the open/close rotation and any duration/transform transitions
respect prefers-reduced-motion.

Comment on lines +442 to +450
<div
className={cn(
'grid transition-[grid-template-rows,opacity] duration-300 ease-out',
archivedExpanded
? 'grid-rows-[1fr] opacity-100'
: 'grid-rows-[0fr] opacity-0',
)}
aria-hidden={!archivedExpanded}
>

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.

⚠️ Potential issue | 🟠 Major

Collapsed archived section can still expose hidden interactive controls to keyboard users.

When collapsed, this region is only aria-hidden; its descendant buttons can still be tabbable. Please make the collapsed state non-interactive and non-focusable.

Suggested fix
           <div
             className={cn(
               'grid transition-[grid-template-rows,opacity] duration-300 ease-out',
               archivedExpanded
                 ? 'grid-rows-[1fr] opacity-100'
-                : 'grid-rows-[0fr] opacity-0',
+                : 'grid-rows-[0fr] opacity-0 pointer-events-none',
             )}
             aria-hidden={!archivedExpanded}
           >
             <Stack
               gap={1}
-              className="max-h-64 min-h-0 overflow-y-auto pt-1 pb-2"
+              className={cn(
+                'max-h-64 min-h-0 overflow-y-auto pt-1 pb-2',
+                !archivedExpanded && 'invisible',
+              )}
             >

As per coding guidelines: "Everything interactive must be keyboard-reachable." and "Accessibility is Level AA, not a nice-to-have."

Also applies to: 451-492

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@services/platform/app/features/chat/components/chat-history-sidebar.tsx`
around lines 442 - 450, The collapsed archived section currently only sets
aria-hidden based on archivedExpanded, but its descendant interactive elements
(buttons/links) remain tabbable; update the container div and rendering logic
around archivedExpanded so the collapsed state is truly non-interactive: when
archivedExpanded is false, either remove the archived panel from the DOM (only
render when true) or mark the container with inert (or hidden) plus aria-hidden
and ensure descendant interactive controls are not focusable (e.g., set
tabIndex=-1 or avoid rendering Button components) — change the container div
(the element using archivedExpanded and aria-hidden) and/or the descendant
button/link render paths so that when !archivedExpanded they cannot receive
focus or pointer events.

Comment on lines +179 to +197
<button
type="button"
aria-label={tSettings('organization.deleteAriaLabel', {
name: org.name,
})}
onClick={(e) => {
e.stopPropagation();
setDeleteTarget({
organizationId: org.organizationId,
name: org.name,
});
}}
className={cn(
'absolute top-1/2 right-2 -translate-y-1/2 rounded p-1 transition-opacity',
'text-muted-foreground hover:text-destructive hover:bg-destructive/10',
'opacity-0 group-hover/org:opacity-100 focus-visible:opacity-100',
)}
>
<Trash2 className="size-3.5" />

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.

⚠️ Potential issue | 🟠 Major

Make the delete action visible and tappable on touch devices.

The trash button stays hidden unless the row is hovered or receives focus, so it is effectively undiscoverable on touch screens. Its p-1 hit area is also below the repo's minimum touch-target guidance for destructive controls.

As per coding guidelines, "Everything interactive must be keyboard-reachable. Test with Tab and Shift+Tab before merging." and "Minimum 24×24 CSS pixel touch target (44×44 on mobile)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/organization/components/organization-list-panel.tsx`
around lines 179 - 197, The delete button is hidden by default and has too small
a hit area for touch devices; in organization-list-panel.tsx update the button's
className so it is always visible on touch/small screens and has a larger hit
target: replace the current opacity and padding utilities (e.g., 'opacity-0
group-hover/org:opacity-100 ... p-1') with responsive variants such that
small/coarse pointers show the button (for example use 'sm:opacity-0
sm:group-hover/org:opacity-100 opacity-100' or equivalent
media-query/coarse-pointer Tailwind variants) and increase padding to meet
touch-target guidance (e.g., 'p-3 sm:p-1' or otherwise ensure 44×44 on mobile
and >=24×24 elsewhere); keep the existing aria-label, onClick handler and Trash2
icon (references: setDeleteTarget, org.organizationId, org.name, Trash2) so
keyboard/focus behavior remains intact.

Comment on lines +168 to +176
const confirmDeleteCategory = useCallback(() => {
if (deletingIndex === null) return;
const index = deletingIndex;
const next = categories.filter((_, i) => i !== index);
setCategories(next);
setEditorIndex((prev) => (prev === index ? null : prev));
setDeletingIndex(null);
void saveWith(buildConfig({ categories: next }));
}, [buildConfig, categories, deletingIndex, saveWith]);

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.

⚠️ Potential issue | 🟠 Major

Adjust editor index when deleting an earlier row

At Line 173, editorIndex is only cleared on exact match. If a category before the currently edited one is deleted, the edited index becomes off-by-one and can point to the wrong category.

Suggested fix
-    setEditorIndex((prev) => (prev === index ? null : prev));
+    setEditorIndex((prev) => {
+      if (prev === null || prev === 'new') return prev;
+      if (prev === index) return null;
+      return prev > index ? prev - 1 : prev;
+    });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/settings/governance/components/chat-filter-config.tsx`
around lines 168 - 176, In confirmDeleteCategory update the editor index
handling so it doesn't become off-by-one when removing a category before the
currently edited one: after computing index and next, call setEditorIndex with a
function that if prev is null returns null, if prev === index returns null (we
deleted the edited item), if prev > index returns prev - 1 (shift editor down
one), otherwise returns prev; keep the rest of the logic (setCategories,
setDeletingIndex, saveWith(buildConfig(...))) unchanged and reference the
confirmDeleteCategory, setEditorIndex, deletingIndex and categories identifiers
when making the change.

Comment on lines +82 to +96
{enabled ? (
<ul className="text-xs">
{details.map((detail) => (
<li key={detail} className="py-0.5">
{detail}
</li>
))}
</ul>
) : (
<Tooltip content={disabledReason}>
<Badge variant="outline" icon={Info} className="mt-auto cursor-help">
{tCommon('status.disabled')}
</Badge>
</Tooltip>
)}

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.

⚠️ Potential issue | 🟠 Major

Keep the disabled reason visible instead of tooltip-only.

This change removes the inline explanation and leaves it behind a tooltip on a non-interactive badge. That makes the reason easy to miss for keyboard and touch users. Please keep the disabled copy visible in the card, or make the trigger explicitly focusable and associated with the description.

💡 Suggested fix
-      ) : (
-        <Tooltip content={disabledReason}>
-          <Badge variant="outline" icon={Info} className="mt-auto cursor-help">
-            {tCommon('status.disabled')}
-          </Badge>
-        </Tooltip>
-      )}
+      ) : (
+        <div className="space-y-2">
+          <Badge variant="outline" icon={Info} className="mt-auto">
+            {tCommon('status.disabled')}
+          </Badge>
+          <p className="text-muted-foreground text-xs">{disabledReason}</p>
+        </div>
+      )}
As per coding guidelines, "Everything interactive must be keyboard-reachable" and "Everything Tale ships must meet WCAG 2.1 Level AA."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{enabled ? (
<ul className="text-xs">
{details.map((detail) => (
<li key={detail} className="py-0.5">
{detail}
</li>
))}
</ul>
) : (
<Tooltip content={disabledReason}>
<Badge variant="outline" icon={Info} className="mt-auto cursor-help">
{tCommon('status.disabled')}
</Badge>
</Tooltip>
)}
{enabled ? (
<ul className="text-xs">
{details.map((detail) => (
<li key={detail} className="py-0.5">
{detail}
</li>
))}
</ul>
) : (
<div className="space-y-2">
<Badge variant="outline" icon={Info} className="mt-auto">
{tCommon('status.disabled')}
</Badge>
<p className="text-muted-foreground text-xs">{disabledReason}</p>
</div>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@services/platform/app/features/settings/governance/components/guardrails-overview.tsx`
around lines 82 - 96, The disabled state currently hides the explanation inside
a non-focusable Tooltip on the Badge; change the JSX in the guardrails-overview
component so that when enabled is false you render the disabledReason visibly
(e.g., display the Badge plus an adjacent text element like a <span> or <p>
showing disabledReason) instead of tooltip-only, or if you must keep the Tooltip
keep its trigger keyboard-focusable and associated with the description (make
the Badge a focusable element/button or add tabIndex={0} and aria-describedby
linking to the tooltip content). Update the conditional that renders
Tooltip/Badge around enabled to render an accessible visible copy of
disabledReason (reference variables: enabled, disabledReason and components
Tooltip/Badge in guardrails-overview.tsx).

…action

Removes auth.userButton.settings, settings.organization.switchAction,
currentBadge, and roleLabel from en/de/fr — all callers were deleted in
the user-button slim-down. Aligns user-button test mocks with the new
key set so the i18n usage check passes.
- Add aria-label to icon-only org switcher trigger so screen readers get a name
  beyond the tooltip.
- Bump mobile nav button container to py-3 so user/org buttons clear the 44×44
  touch-target floor on mobile.
- Default unsupported locales to English instead of French in user-button.
- Add motion-reduce variants to archived-section animations and mark the
  collapsed pane as inert + pointer-events-none so descendants leave the tab
  order.
- Always show the org-list delete button on touch (sm:hover-only on desktop)
  and grow it to a 44×44 touch target on mobile.
- Shift editorIndex when an earlier category is removed in chat-filter-config
  so the editor stays bound to the right row.
- Surface guardrail disabledReason inline alongside the Disabled badge instead
  of hiding it behind a tooltip on a non-focusable trigger.
Lint requires a real <button> over role="button"; switch the trigger to
button so screen readers and keyboards still get a labeled control.
@Israeltheminer Israeltheminer merged commit 2f9f9f3 into main Apr 27, 2026
16 checks passed
@Israeltheminer Israeltheminer deleted the refactor/organization-button-extraction branch April 27, 2026 10:32
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.

1 participant