fix(ui): refine UI components, teams management, and settings dialogs#1038
Conversation
- Refine table, dialog, sheet, button, and input component styles - Replace team-members-dialog with inline member checklist in create/edit flows - Improve API keys dialog layout and simplify table config - Update chat input styling and conversation components - Clean up globals.css and update i18n strings
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 WalkthroughWalkthroughThis PR consolidates styling updates across the platform UI by replacing Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
services/platform/app/features/settings/teams/components/team-delete-dialog.tsx (1)
62-71:⚠️ Potential issue | 🟠 MajorRestore the agent-binding check in the delete dialog.
The translation key
teamHasAgentsWarningexists inservices/platform/messages/en.json(line 641) with the message: "This team has agents assigned to it. Reassign or remove agents from this team before deleting." This strongly indicates a previous guard was in place and should be restored.The
DeleteDialogcomponent fully supports thewarningprop anddisableDeleteflag, and the pattern is already in use elsewhere (e.g.,DeleteMemberDialog). The data model showsagentBindingsindexed byteamIdstill exist in the schema, so the guard remains relevant.Add a check for team-bound agents before rendering the delete button. Use the warning message from translations to inform the user they must reassign or remove agents first.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/app/features/settings/teams/components/team-delete-dialog.tsx` around lines 62 - 71, Restore the guard that prevents deleting teams which have agents: compute a boolean (e.g., hasAgents) from the team/agent-bindings data (for example check team.agentBindings?.length > 0 or agentBindingsByTeamId[team.id]?.length > 0) and pass it into the DeleteDialog via disableDelete={hasAgents} and warning={tSettings('teams.teamHasAgentsWarning')}; keep existing props (open, onOpenChange, title, description, isDeleting, onDelete) and only add the warning and disableDelete props so users see the translation message and cannot delete a team with agents still assigned.services/platform/app/features/conversations/components/conversations-list.tsx (1)
261-264:⚠️ Potential issue | 🟡 MinorHardcoded fallback text should be internationalized.
The
'Unknown'fallback text on line 263 should use a translation key for consistency with the coding guidelines.Proposed fix
<Heading level={3} size="sm" weight="semibold" tracking="tight" truncate className="flex-1" > {conversation.customer?.name || conversation?.title || - 'Unknown'} + t('customer.unknown')} </Heading>As per coding guidelines: "Do not hardcode text in UI components. Use translation hooks/functions instead for user-facing UI."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@services/platform/app/features/conversations/components/conversations-list.tsx` around lines 261 - 264, The hardcoded fallback 'Unknown' in the ConversationsList heading should be replaced with a translated string; update the rendering that currently uses conversation.customer?.name || conversation?.title || 'Unknown' to call your i18n translation (e.g., t('unknown') or useTranslation().t('conversations.unknown')) so the fallback uses a translation key, and ensure the component (ConversationsList) imports and invokes the translation hook/provider before use.
🤖 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/ui/data-display/table.tsx`:
- Line 86: Replace the invalid Tailwind classes in the table cell class string:
change `rounded-rt-xl` to `rounded-tr-xl` and `rounded-lt-xl` to `rounded-tl-xl`
inside the class array used in the Table component (the string containing 'h-10
px-3 text-left align-middle font-medium text-muted-foreground
[&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl
text-nowrap'); update those two tokens so they use the correct `-tr-` and `-tl-`
variants.
In `@services/platform/app/features/chat/components/chat-input.tsx`:
- Around line 257-259: The upload-spinner chip is using hardcoded hex colors in
the container's className and the Loader's className; replace those hex values
with semantic theme tokens (e.g., Tailwind or design-system token classes) so
colors adapt to light/dark modes — update the container element's className
(currently containing border-[`#E5E7EB`] bg-[`#F3F4F6`]) to use semantic classes
like border-[token] and bg-[token], and change the Loader's className (currently
text-[`#9CA3AF`]) to a semantic text/token class; ensure you use the app’s
established token names so the spinner follows theme tokens across components
like chat-input.tsx and the Loader component.
In
`@services/platform/app/features/settings/teams/components/team-create-dialog.tsx`:
- Around line 91-106: The current flow builds memberIds from selectedMemberIds
and calls addMember via Promise.all which can reject and leave a created team
with partial membership; replace the Promise.all call in the member-assignment
block with Promise.allSettled over memberIds.map(userId => addMember(...)), then
inspect settled results to separate successful adds from failures, log or
collect failed userIds/errors, and surface a contextual toast: e.g., "Team
created; X members added, Y failed" (or a specific error list) while ensuring
the createTeam success path remains distinct from member-assignment failures;
also handle the no-member branch (authClient.getSession / userId addMember)
similarly to capture single-add failures and show accurate feedback.
In
`@services/platform/app/features/settings/teams/components/team-edit-dialog.tsx`:
- Around line 14-18: Move the local type declaration TeamMemberItem so it is not
interleaved with imports: keep all import statements (useAddTeamMember,
useRemoveTeamMember, useTeamMembers, Team, TeamMemberChecklist) grouped at the
top of team-edit-dialog.tsx, then place the TeamMemberItem type definition
immediately after the imports (before its usage in the component), ensuring
import ordering and file organization follow the coding guidelines.
- Around line 62-69: The code is using forbidden type assertions like "as
TeamMemberItem[]" in the team-edit-dialog component; instead update the source
hook useTeamMembers (in queries.ts) to return the precise typed shape (e.g.,
Array<{ _id: string; userId: string }> | undefined) so consumers can use
teamMembers without casting, then remove the local TeamMemberItem type and the
three casts (used in the useEffect that sets selectedMemberIds and
initialMemberIdsRef, and the other two occurrences at lines 122 and 170);
alternatively implement a narrow type guard for teamMembers and use it before
mapping, but the preferred fix is to export the correct return type from
useTeamMembers and update usages (setSelectedMemberIds, initialMemberIdsRef, and
any map over teamMembers) to rely on that proper type.
- Around line 131-145: Replace the Promise.all usage that runs
addTeamMember.mutateAsync and removeTeamMember.mutateAsync in parallel with
Promise.allSettled so partial failures are captured and handled (log or surface
failed results and only show success when all necessary ops succeeded);
additionally add a guard in the selection handler (the handleToggleMember /
setSelectedMemberIds logic) to prevent removing the last member (e.g., if
selected size <= 1, return previous set) so UI cannot trigger the "Cannot remove
the last team member" server error.
In
`@services/platform/app/features/settings/teams/components/team-member-checklist.tsx`:
- Around line 28-33: Replace hardcoded user-facing strings in the
TeamMemberChecklist component with i18n keys: in the isLoading branch (the
"Loading..." paragraph) and where "Unknown" is rendered (around the 68–70
section). Import and use the project's translation hook/function (e.g.,
useTranslation or t) and replace the literals with calls like t('team.loading')
and t('team.unknown') (or the appropriate key names), ensuring the keys are
added to the locale files.
In
`@services/platform/app/features/settings/teams/hooks/use-teams-table-config.tsx`:
- Around line 85-104: The formatRelativeTime function returns hardcoded English
strings and duplicates date formatting logic; remove or replace it with the
shared i18n-aware utilities by using the useFormatDate() hook (in React
components) or the formatDate() utility from lib/utils/date/format to produce a
localized relative time string, or move relative-time logic into the shared date
utils and call that instead; update any references to formatRelativeTime in this
file to call the chosen i18n-aware function/hook (e.g., useFormatDate or the
new/shared relative formatter) so all user-facing text is translated and
centralized.
- Around line 38-48: The members and createdAt columns are showing defaults
because the listOrgTeams query and Team type lack memberCount/createdAt and code
is using an unsafe cast in the cell renderer; update the listOrgTeams
GraphQL/REST query to include memberCount and createdAt and extend the Team type
accordingly (remove the "as Team & { memberCount?: number }" cast and rely on
the updated type), then update the members cell and createdAt cell to read those
real properties; replace the custom formatRelativeTime implementation with the
existing useFormatDate().formatRelative hook and remove hardcoded English
strings in formatRelativeTime, using translation functions (tSettings) where
needed (refer to useFormatDate, formatRelative, listOrgTeams, Team, and the cell
renderers).
In `@services/platform/app/globals.css`:
- Line 106: The :root CSS variable --muted currently uses a very dark neutral (0
0% 9.02%), causing bg-muted to render nearly black in light mode; change the
--muted value defined in :root in globals.css back to a light neutral (a high
lightness percentage) while leaving the existing .dark { --muted: 0 0% 9.02%; }
unchanged so dark-mode keeps the 9.02% token; this will restore light-mode
panels that rely on bg-muted (and places like the message improvement preview
moved from bg-secondary/20) to display correctly.
---
Outside diff comments:
In
`@services/platform/app/features/conversations/components/conversations-list.tsx`:
- Around line 261-264: The hardcoded fallback 'Unknown' in the ConversationsList
heading should be replaced with a translated string; update the rendering that
currently uses conversation.customer?.name || conversation?.title || 'Unknown'
to call your i18n translation (e.g., t('unknown') or
useTranslation().t('conversations.unknown')) so the fallback uses a translation
key, and ensure the component (ConversationsList) imports and invokes the
translation hook/provider before use.
In
`@services/platform/app/features/settings/teams/components/team-delete-dialog.tsx`:
- Around line 62-71: Restore the guard that prevents deleting teams which have
agents: compute a boolean (e.g., hasAgents) from the team/agent-bindings data
(for example check team.agentBindings?.length > 0 or
agentBindingsByTeamId[team.id]?.length > 0) and pass it into the DeleteDialog
via disableDelete={hasAgents} and
warning={tSettings('teams.teamHasAgentsWarning')}; keep existing props (open,
onOpenChange, title, description, isDeleting, onDelete) and only add the warning
and disableDelete props so users see the translation message and cannot delete a
team with agents still assigned.
🪄 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: ae71b043-6a18-4f36-8694-a5512ec4db8a
📒 Files selected for processing (39)
services/platform/app/components/ui/data-display/table.tsxservices/platform/app/components/ui/data-table/data-table-skeleton.tsxservices/platform/app/components/ui/data-table/data-table.tsxservices/platform/app/components/ui/dialog/dialog.tsxservices/platform/app/components/ui/dialog/item-preview.tsxservices/platform/app/components/ui/forms/input.tsxservices/platform/app/components/ui/overlays/sheet.tsxservices/platform/app/components/ui/primitives/button.tsxservices/platform/app/components/user-button.tsxservices/platform/app/features/automations/components/automation-assistant/chat-input.tsxservices/platform/app/features/chat/components/chat-input.tsxservices/platform/app/features/chat/components/chat-interface.tsxservices/platform/app/features/conversations/components/conversation-header.tsxservices/platform/app/features/conversations/components/conversations-list.tsxservices/platform/app/features/conversations/components/conversations-skeleton.tsxservices/platform/app/features/conversations/components/message-improvement-dialog.tsxservices/platform/app/features/customers/components/customer-import-form.tsxservices/platform/app/features/settings/api-keys/components/api-key-create-dialog.tsxservices/platform/app/features/settings/api-keys/components/api-key-revoke-dialog.tsxservices/platform/app/features/settings/api-keys/components/api-keys-table.tsxservices/platform/app/features/settings/api-keys/hooks/use-api-keys-table-config.tsxservices/platform/app/features/settings/integrations/components/integration-panel.tsxservices/platform/app/features/settings/integrations/components/integration-upload/steps/upload-step.tsxservices/platform/app/features/settings/integrations/components/integrations.tsxservices/platform/app/features/settings/integrations/components/sso-config-dialog.tsxservices/platform/app/features/settings/organization/components/member-table.tsxservices/platform/app/features/settings/providers/components/providers-table.tsxservices/platform/app/features/settings/teams/components/__tests__/team-members-dialog.test.tsxservices/platform/app/features/settings/teams/components/team-create-dialog.tsxservices/platform/app/features/settings/teams/components/team-delete-dialog.tsxservices/platform/app/features/settings/teams/components/team-edit-dialog.tsxservices/platform/app/features/settings/teams/components/team-member-checklist.tsxservices/platform/app/features/settings/teams/components/team-members-dialog.tsxservices/platform/app/features/settings/teams/components/team-row-actions.tsxservices/platform/app/features/settings/teams/components/teams-table.tsxservices/platform/app/features/settings/teams/hooks/use-teams-table-config.tsxservices/platform/app/globals.cssservices/platform/app/routes/dashboard/$id/chat.tsxservices/platform/messages/en.json
💤 Files with no reviewable changes (2)
- services/platform/app/features/settings/teams/components/tests/team-members-dialog.test.tsx
- services/platform/app/features/settings/teams/components/team-members-dialog.tsx
| scope={scope} | ||
| className={cn( | ||
| 'h-8 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl text-nowrap', | ||
| 'h-10 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl text-nowrap', |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify if rounded-rt-xl and rounded-lt-xl are valid Tailwind classes or custom utilities
# Check for any custom utility definitions in the codebase
rg -n "rounded-rt|rounded-lt" --type css
# Check Tailwind config for custom utilities
fd -e js -e ts -e json "tailwind" --exec cat {} 2>/dev/null | head -100Repository: tale-project/tale
Length of output: 474
🏁 Script executed:
# Find and check globals.css for custom theme definitions
fd -name "globals.css" -o -name "globals.scss" | xargs cat 2>/dev/null | head -200Repository: tale-project/tale
Length of output: 291
🏁 Script executed:
# Check the actual file mentioned in the review
cat -n services/platform/app/components/ui/data-display/table.tsx 2>/dev/null | sed -n '80,95p'Repository: tale-project/tale
Length of output: 620
🏁 Script executed:
# Search for usage of rounded-rt or rounded-lt classes across the codebase
rg "rounded-rt|rounded-lt" --type tsx --type ts --type jsx --type jsRepository: tale-project/tale
Length of output: 88
🏁 Script executed:
# Verify standard Tailwind naming conventions by checking if rounded-tr and rounded-tl are used
rg "rounded-tr|rounded-tl" --type tsx --type ts --type jsx --type js | head -20Repository: tale-project/tale
Length of output: 88
🏁 Script executed:
# Search for these classes with correct rg syntax
rg "rounded-rt|rounded-lt" -t css -t jsRepository: tale-project/tale
Length of output: 43
🏁 Script executed:
# Find globals.css
fd "globals.css" -o "global.css"Repository: tale-project/tale
Length of output: 200
🏁 Script executed:
# Check if there are any custom utilities in the entire codebase mentioning these classes
rg "rounded-rt-xl|rounded-lt-xl"Repository: tale-project/tale
Length of output: 276
🏁 Script executed:
# Check for rounded-tr and rounded-tl usage to see which naming convention is used elsewhere
rg "rounded-tr|rounded-tl"Repository: tale-project/tale
Length of output: 222
Typo in Tailwind border-radius classes.
The classes rounded-rt-xl and rounded-lt-xl are invalid. Standard Tailwind uses rounded-tr-xl (top-right) and rounded-tl-xl (top-left).
🔧 Proposed fix
- 'h-10 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl text-nowrap',
+ 'h-10 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-tr-xl first:rounded-tl-xl text-nowrap',📝 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.
| 'h-10 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl text-nowrap', | |
| 'h-10 px-3 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 last:rounded-tr-xl first:rounded-tl-xl text-nowrap', |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/app/components/ui/data-display/table.tsx` at line 86,
Replace the invalid Tailwind classes in the table cell class string: change
`rounded-rt-xl` to `rounded-tr-xl` and `rounded-lt-xl` to `rounded-tl-xl` inside
the class array used in the Table component (the string containing 'h-10 px-3
text-left align-middle font-medium text-muted-foreground
[&:has([role=checkbox])]:pr-0 last:rounded-rt-xl first:rounded-lt-xl
text-nowrap'); update those two tokens so they use the correct `-tr-` and `-tl-`
variants.
| className="flex size-9 items-center justify-center overflow-hidden rounded-lg border border-[#E5E7EB] bg-[#F3F4F6]" | ||
| > | ||
| <LoaderCircleIcon className="size-4 animate-spin" /> | ||
| <Loader className="size-4 animate-spin text-[#9CA3AF]" /> |
There was a problem hiding this comment.
Avoid hardcoded grayscale hex colors in the upload spinner chip.
Using fixed #E5E7EB / #F3F4F6/#9CA3AF`` breaks theme-token consistency and can degrade dark-mode contrast. Use semantic tokens so this stays correct across themes.
🎯 Suggested fix
- className="flex size-9 items-center justify-center overflow-hidden rounded-lg border border-[`#E5E7EB`] bg-[`#F3F4F6`]"
+ className="border-border bg-muted flex size-9 items-center justify-center overflow-hidden rounded-lg border"
>
- <Loader className="size-4 animate-spin text-[`#9CA3AF`]" />
+ <Loader className="text-muted-foreground size-4 animate-spin" />
</div>🤖 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-input.tsx` around lines
257 - 259, The upload-spinner chip is using hardcoded hex colors in the
container's className and the Loader's className; replace those hex values with
semantic theme tokens (e.g., Tailwind or design-system token classes) so colors
adapt to light/dark modes — update the container element's className (currently
containing border-[`#E5E7EB`] bg-[`#F3F4F6`]) to use semantic classes like
border-[token] and bg-[token], and change the Loader's className (currently
text-[`#9CA3AF`]) to a semantic text/token class; ensure you use the app’s
established token names so the spinner follows theme tokens across components
like chat-input.tsx and the Loader component.
| // Add selected members to the team | ||
| const memberIds = Array.from(selectedMemberIds); | ||
| if (memberIds.length === 0) { | ||
| // If no members selected, add the current user as default | ||
| const session = await authClient.getSession(); | ||
| const userId = session.data?.user?.id; | ||
| if (userId) { | ||
| await addMember({ teamId, userId, organizationId }); | ||
| } | ||
| } else { | ||
| await Promise.all( | ||
| memberIds.map((userId) => | ||
| addMember({ teamId, userId, organizationId }), | ||
| ), | ||
| ); | ||
| } |
There was a problem hiding this comment.
Partial failure risk: team created but member assignment may fail.
If Promise.all on line 101 rejects (e.g., one addMember call fails due to "User is already a member" or authorization issues), the team has already been created (line 77-89) but will have incomplete membership. The generic "teamCreateFailed" toast is misleading since the team was actually created.
Consider:
- Using
Promise.allSettledto continue despite individual failures - Providing a more accurate error message distinguishing team creation vs member assignment failures
- Showing partial success feedback when some members couldn't be added
🔧 Suggested approach using Promise.allSettled
- await Promise.all(
- memberIds.map((userId) =>
- addMember({ teamId, userId, organizationId }),
- ),
- );
+ const results = await Promise.allSettled(
+ memberIds.map((userId) =>
+ addMember({ teamId, userId, organizationId }),
+ ),
+ );
+ const failedCount = results.filter((r) => r.status === 'rejected').length;
+ if (failedCount > 0) {
+ console.warn(`Failed to add ${failedCount} of ${memberIds.length} members`);
+ // Consider showing a partial success toast
+ }📝 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.
| // Add selected members to the team | |
| const memberIds = Array.from(selectedMemberIds); | |
| if (memberIds.length === 0) { | |
| // If no members selected, add the current user as default | |
| const session = await authClient.getSession(); | |
| const userId = session.data?.user?.id; | |
| if (userId) { | |
| await addMember({ teamId, userId, organizationId }); | |
| } | |
| } else { | |
| await Promise.all( | |
| memberIds.map((userId) => | |
| addMember({ teamId, userId, organizationId }), | |
| ), | |
| ); | |
| } | |
| // Add selected members to the team | |
| const memberIds = Array.from(selectedMemberIds); | |
| if (memberIds.length === 0) { | |
| // If no members selected, add the current user as default | |
| const session = await authClient.getSession(); | |
| const userId = session.data?.user?.id; | |
| if (userId) { | |
| await addMember({ teamId, userId, organizationId }); | |
| } | |
| } else { | |
| const results = await Promise.allSettled( | |
| memberIds.map((userId) => | |
| addMember({ teamId, userId, organizationId }), | |
| ), | |
| ); | |
| const failedCount = results.filter((r) => r.status === 'rejected').length; | |
| if (failedCount > 0) { | |
| console.warn(`Failed to add ${failedCount} of ${memberIds.length} members`); | |
| // Consider showing a partial success toast | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/components/team-create-dialog.tsx`
around lines 91 - 106, The current flow builds memberIds from selectedMemberIds
and calls addMember via Promise.all which can reject and leave a created team
with partial membership; replace the Promise.all call in the member-assignment
block with Promise.allSettled over memberIds.map(userId => addMember(...)), then
inspect settled results to separate successful adds from failures, log or
collect failed userIds/errors, and surface a contextual toast: e.g., "Team
created; X members added, Y failed" (or a specific error list) while ensuring
the createTeam success path remains distinct from member-assignment failures;
also handle the no-member branch (authClient.getSession / userId addMember)
similarly to capture single-add failures and show accurate feedback.
| import { useAddTeamMember, useRemoveTeamMember } from '../hooks/mutations'; | ||
| import { useTeamMembers, type Team } from '../hooks/queries'; | ||
|
|
||
| type TeamMemberItem = { _id: string; userId: string }; | ||
| import { TeamMemberChecklist } from './team-member-checklist'; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Import ordering issue: type definition mixed with imports.
The local type definition TeamMemberItem on line 17 is placed between imports (lines 14-15 and line 18). Per coding guidelines, imports should be at the top, properly sorted. Move the type definition after all imports.
♻️ Suggested fix
import { useAddTeamMember, useRemoveTeamMember } from '../hooks/mutations';
import { useTeamMembers, type Team } from '../hooks/queries';
-
-type TeamMemberItem = { _id: string; userId: string };
import { TeamMemberChecklist } from './team-member-checklist';
+
+type TeamMemberItem = { _id: string; userId: string };📝 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.
| import { useAddTeamMember, useRemoveTeamMember } from '../hooks/mutations'; | |
| import { useTeamMembers, type Team } from '../hooks/queries'; | |
| type TeamMemberItem = { _id: string; userId: string }; | |
| import { TeamMemberChecklist } from './team-member-checklist'; | |
| import { useAddTeamMember, useRemoveTeamMember } from '../hooks/mutations'; | |
| import { useTeamMembers, type Team } from '../hooks/queries'; | |
| import { TeamMemberChecklist } from './team-member-checklist'; | |
| type TeamMemberItem = { _id: string; userId: string }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/components/team-edit-dialog.tsx`
around lines 14 - 18, Move the local type declaration TeamMemberItem so it is
not interleaved with imports: keep all import statements (useAddTeamMember,
useRemoveTeamMember, useTeamMembers, Team, TeamMemberChecklist) grouped at the
top of team-edit-dialog.tsx, then place the TeamMemberItem type definition
immediately after the imports (before its usage in the component), ensuring
import ordering and file organization follow the coding guidelines.
| useEffect(() => { | ||
| if (teamMembers && open) { | ||
| const members = teamMembers as TeamMemberItem[]; | ||
| const memberIds = new Set(members.map((m) => m.userId)); | ||
| setSelectedMemberIds(memberIds); | ||
| initialMemberIdsRef.current = memberIds; | ||
| } | ||
| }, [teamMembers, open]); |
There was a problem hiding this comment.
Type casts with as violate coding guidelines.
Lines 64, 122, and 170 use as TeamMemberItem[] which is explicitly prohibited. The proper fix is to type the return value of useTeamMembers correctly or use a type guard.
🔧 Suggested approach
Define the proper return type in useTeamMembers hook so consumers don't need to cast:
// In queries.ts
export function useTeamMembers(teamId: string): {
teamMembers: Array<{ _id: string; userId: string }> | undefined;
// ... other fields
}Then remove the casts and the local TeamMemberItem type.
Also applies to: 122-122, 170-170
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/components/team-edit-dialog.tsx`
around lines 62 - 69, The code is using forbidden type assertions like "as
TeamMemberItem[]" in the team-edit-dialog component; instead update the source
hook useTeamMembers (in queries.ts) to return the precise typed shape (e.g.,
Array<{ _id: string; userId: string }> | undefined) so consumers can use
teamMembers without casting, then remove the local TeamMemberItem type and the
three casts (used in the useEffect that sets selectedMemberIds and
initialMemberIdsRef, and the other two occurrences at lines 122 and 170);
alternatively implement a narrow type guard for teamMembers and use it before
mapping, but the preferred fix is to export the correct return type from
useTeamMembers and update usages (setSelectedMemberIds, initialMemberIdsRef, and
any map over teamMembers) to rely on that proper type.
| await Promise.all([ | ||
| ...toAdd.map((userId) => | ||
| addTeamMember.mutateAsync({ | ||
| teamId: team.id, | ||
| userId, | ||
| organizationId, | ||
| }), | ||
| ), | ||
| ...toRemove.map((m) => | ||
| removeTeamMember.mutateAsync({ | ||
| teamMemberId: m._id, | ||
| organizationId, | ||
| }), | ||
| ), | ||
| ]); |
There was a problem hiding this comment.
Partial failure risk with parallel mutations.
Promise.all will reject if any addTeamMember or removeTeamMember call fails, leaving membership in an inconsistent state. The removeMember mutation (context snippet 1) throws on several conditions including "Cannot remove the last team member".
Additionally, if a user deselects all members, the removal operations will fail with the "last member" error, but this isn't prevented in the UI.
🔧 Suggested improvements
- Use
Promise.allSettledto handle partial failures gracefully:
- await Promise.all([
- ...toAdd.map((userId) =>
- addTeamMember.mutateAsync({ ... }),
- ),
- ...toRemove.map((m) =>
- removeTeamMember.mutateAsync({ ... }),
- ),
- ]);
+ const results = await Promise.allSettled([
+ ...toAdd.map((userId) =>
+ addTeamMember.mutateAsync({ ... }),
+ ),
+ ...toRemove.map((m) =>
+ removeTeamMember.mutateAsync({ ... }),
+ ),
+ ]);
+ const failures = results.filter((r) => r.status === 'rejected');
+ if (failures.length > 0) {
+ console.warn('Some membership changes failed:', failures);
+ }- Add validation to prevent deselecting all members:
const handleToggleMember = useCallback((userId: string) => {
setSelectedMemberIds((prev) => {
const next = new Set(prev);
if (next.has(userId)) {
if (next.size <= 1) return prev; // Prevent removing last member
next.delete(userId);
} else {
next.add(userId);
}
return next;
});
}, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/components/team-edit-dialog.tsx`
around lines 131 - 145, Replace the Promise.all usage that runs
addTeamMember.mutateAsync and removeTeamMember.mutateAsync in parallel with
Promise.allSettled so partial failures are captured and handled (log or surface
failed results and only show success when all necessary ops succeeded);
additionally add a guard in the selection handler (the handleToggleMember /
setSelectedMemberIds logic) to prevent removing the last member (e.g., if
selected size <= 1, return previous set) so UI cannot trigger the "Cannot remove
the last team member" server error.
| if (isLoading) { | ||
| return ( | ||
| <div className="flex items-center justify-center py-4"> | ||
| <p className="text-muted-foreground text-sm">Loading...</p> | ||
| </div> | ||
| ); |
There was a problem hiding this comment.
Replace hardcoded UI strings with translations.
"Loading..." and "Unknown" are user-facing text and should come from i18n keys.
🌐 Suggested i18n-safe update
- <p className="text-muted-foreground text-sm">Loading...</p>
+ <p className="text-muted-foreground text-sm">
+ {tSettings('teams.loadingMembers')}
+ </p>
...
- {member.displayName || member.email || 'Unknown'}
+ {member.displayName ||
+ member.email ||
+ tSettings('teams.unknownMember')}Also applies to: 68-70
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/components/team-member-checklist.tsx`
around lines 28 - 33, Replace hardcoded user-facing strings in the
TeamMemberChecklist component with i18n keys: in the isLoading branch (the
"Loading..." paragraph) and where "Unknown" is rendered (around the 68–70
section). Import and use the project's translation hook/function (e.g.,
useTranslation or t) and replace the literals with calls like t('team.loading')
and t('team.unknown') (or the appropriate key names), ensuring the keys are
added to the locale files.
| id: 'members', | ||
| header: tSettings('teams.columns.members'), | ||
| cell: ({ row }) => { | ||
| const team = row.original as Team & { memberCount?: number }; | ||
| const count = team.memberCount ?? 0; | ||
| return ( | ||
| <Text as="span" variant="muted"> | ||
| {tSettings('teams.memberCount', { count })} | ||
| </Text> | ||
| ); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the Team type and getMyTeams query return shape
echo "=== Team type definition ==="
rg -n "export type Team" services/platform/app/features/settings/teams/hooks/queries.ts -A 2
echo -e "\n=== getMyTeams query returns ==="
ast-grep --pattern 'export const getMyTeams = query({
$$$
returns: $_,
$$$
})'Repository: tale-project/tale
Length of output: 778
🏁 Script executed:
# Check formatRelativeTime implementation and the created column
cat -n services/platform/app/features/settings/teams/hooks/use-teams-table-config.tsx | sed -n '50,110p'Repository: tale-project/tale
Length of output: 2155
🏁 Script executed:
# Check if useFormatDate or formatDate is available and used elsewhere
rg "useFormatDate|formatDate" services/platform/app --type tsx -B 1 -A 1 | head -40Repository: tale-project/tale
Length of output: 88
🏁 Script executed:
# Search for useFormatDate or formatDate usage with correct file type
rg "useFormatDate|formatDate" services/platform/app -A 1 | head -50Repository: tale-project/tale
Length of output: 3428
🏁 Script executed:
# Check if there's a standard date formatting utility being used
find services/platform/app -name "*format*" -type f | grep -E "\.(ts|tsx)$"Repository: tale-project/tale
Length of output: 257
🏁 Script executed:
# Check what formatDateSmart does - it might support relative formatting
cat -n services/platform/app/hooks/use-format-date.tsRepository: tale-project/tale
Length of output: 3737
🏁 Script executed:
# Check if memberCount or createdAt are being added to teams somewhere before being passed to the table
rg "memberCount|createdAt" services/platform/app/features/settings/teams -B 3 -A 3Repository: tale-project/tale
Length of output: 3931
🏁 Script executed:
# Check if the Team type is being extended or enriched elsewhere
rg "Team &" services/platform/app/features/settings/teams -B 2 -A 2Repository: tale-project/tale
Length of output: 1253
🏁 Script executed:
# Check the entire use-teams-table-config.tsx file to see where teams data comes from
head -50 services/platform/app/features/settings/teams/hooks/use-teams-table-config.tsxRepository: tale-project/tale
Length of output: 1383
🏁 Script executed:
# Check if there's any parent component or hook that enriches the teams data before it reaches the table
rg "useTeamsTableConfig" services/platform/app/features/settings/teams -B 5 -A 5Repository: tale-project/tale
Length of output: 3599
🏁 Script executed:
# Look for where the teams data query is called
rg "useQuery.*getMyTeams" services/platform/app/features/settings/teams -B 3 -A 5Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
# Check where TeamsTable is used to see how teams data is passed
rg "TeamsTable" services/platform/app/features/settings/teams -B 3 -A 3Repository: tale-project/tale
Length of output: 3555
🏁 Script executed:
# Check the full teams-table.tsx to see how teams are handled
cat -n services/platform/app/features/settings/teams/components/teams-table.tsx | head -80Repository: tale-project/tale
Length of output: 2156
🏁 Script executed:
# Find where TeamsTable component is used
rg "TeamsTable" services/platform/app/features/settings/teams -lRepository: tale-project/tale
Length of output: 212
🏁 Script executed:
# Look for the main page/container component
find services/platform/app/features/settings/teams -name "page.tsx" -o -name "index.tsx" | xargs cat -nRepository: tale-project/tale
Length of output: 43
🏁 Script executed:
# List all files in the teams directory
find services/platform/app/features/settings/teams -type f -name "*.tsx" -o -name "*.ts" | head -20Repository: tale-project/tale
Length of output: 903
🏁 Script executed:
# Search for where teams data comes from - check parent components
rg "getMyTeams\|useQuery.*teams" services/platform/app/features/settings -B 2 -A 5Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
# Check the queries.ts file in teams hooks
cat -n services/platform/app/features/settings/teams/hooks/queries.tsRepository: tale-project/tale
Length of output: 1780
🏁 Script executed:
# Check if there are other queries that might return memberCount or createdAt for teams
rg "listOrgTeams" services/platform/convex -A 10Repository: tale-project/tale
Length of output: 9478
🏁 Script executed:
# Check which query hook is actually used - search for useTeams or useOrgTeams in settings/teams
rg "useTeams\|useOrgTeams" services/platform/app/features/settings/teams -B 2 -A 2Repository: tale-project/tale
Length of output: 43
🏁 Script executed:
# Find the main settings/teams page component
find services/platform/app/features/settings -name "*.tsx" | xargs grep -l "TeamsTable"Repository: tale-project/tale
Length of output: 212
🏁 Script executed:
# Check settings folder structure
ls -la services/platform/app/features/settings/Repository: tale-project/tale
Length of output: 702
🏁 Script executed:
# Look in settings/components
ls -la services/platform/app/features/settings/components/Repository: tale-project/tale
Length of output: 247
🏁 Script executed:
# Find the page that uses TeamsTable
find services/platform/app -path "*/settings/*" -name "*.tsx" | xargs grep -l "TeamsTable"Repository: tale-project/tale
Length of output: 274
🏁 Script executed:
# Check the main teams page
cat -n services/platform/app/routes/dashboard/'$id'/settings/teams.tsxRepository: tale-project/tale
Length of output: 1700
🏁 Script executed:
# Confirm that useOrgTeams is indeed the hook used, check if it maps/enriches data
cat -n services/platform/app/features/settings/teams/hooks/queries.ts | grep -A 10 "useOrgTeams"Repository: tale-project/tale
Length of output: 416
Multiple issues: Missing query fields, hardcoded text, and type casting violations.
The Team type (returned by listOrgTeams query) only includes id and name. The memberCount and createdAt properties don't exist, so these columns will always display default values (0 and null).
Additionally:
formatRelativeTimecontains hardcoded English strings ("year", "month", "just now", etc.) instead of using translation functions—violates i18n guideline.- Should use
useFormatDate().formatRelativefrom the existing hook instead of implementing custom date formatting. - Type casts (
as Team & { memberCount?: number }) violate the no-casting guideline. If these fields are needed, update thelistOrgTeamsquery to return them.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/hooks/use-teams-table-config.tsx`
around lines 38 - 48, The members and createdAt columns are showing defaults
because the listOrgTeams query and Team type lack memberCount/createdAt and code
is using an unsafe cast in the cell renderer; update the listOrgTeams
GraphQL/REST query to include memberCount and createdAt and extend the Team type
accordingly (remove the "as Team & { memberCount?: number }" cast and rely on
the updated type), then update the members cell and createdAt cell to read those
real properties; replace the custom formatRelativeTime implementation with the
existing useFormatDate().formatRelative hook and remove hardcoded English
strings in formatRelativeTime, using translation functions (tSettings) where
needed (refer to useFormatDate, formatRelative, listOrgTeams, Team, and the cell
renderers).
| function formatRelativeTime(timestamp: number): string { | ||
| const now = Date.now(); | ||
| const diff = now - timestamp; | ||
| const seconds = Math.floor(diff / 1000); | ||
| const minutes = Math.floor(seconds / 60); | ||
| const hours = Math.floor(minutes / 60); | ||
| const days = Math.floor(hours / 24); | ||
| const weeks = Math.floor(days / 7); | ||
| const months = Math.floor(days / 30); | ||
| const years = Math.floor(days / 365); | ||
|
|
||
| if (years > 0) return `${years} ${years === 1 ? 'year' : 'years'} ago`; | ||
| if (months > 0) return `${months} ${months === 1 ? 'month' : 'months'} ago`; | ||
| if (weeks > 0) return `${weeks} ${weeks === 1 ? 'week' : 'weeks'} ago`; | ||
| if (days > 0) return `${days} ${days === 1 ? 'day' : 'days'} ago`; | ||
| if (hours > 0) return `${hours} ${hours === 1 ? 'hour' : 'hours'} ago`; | ||
| if (minutes > 0) | ||
| return `${minutes} ${minutes === 1 ? 'minute' : 'minutes'} ago`; | ||
| return 'just now'; | ||
| } |
There was a problem hiding this comment.
Hardcoded English text violates i18n requirements.
The formatRelativeTime function returns hardcoded strings like "year", "month", "just now", etc. Per coding guidelines, all user-facing text must use translation hooks.
Additionally, this duplicates functionality that should use the existing useFormatDate() hook or formatDate() utility from lib/utils/date/format. As per coding guidelines: "Do not use toLocaleDateString(), toLocaleTimeString(), or toLocaleString(). Use useFormatDate() hook (React) or formatDate() from lib/utils/date/format instead."
🔧 Suggested approach
Remove the custom formatRelativeTime function and use the existing date formatting utilities with i18n support. If a relative time format doesn't exist, consider adding it to the shared date utilities rather than implementing it locally with hardcoded strings.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@services/platform/app/features/settings/teams/hooks/use-teams-table-config.tsx`
around lines 85 - 104, The formatRelativeTime function returns hardcoded English
strings and duplicates date formatting logic; remove or replace it with the
shared i18n-aware utilities by using the useFormatDate() hook (in React
components) or the formatDate() utility from lib/utils/date/format to produce a
localized relative time string, or move relative-time logic into the shared date
utils and call that instead; update any references to formatRelativeTime in this
file to call the chosen i18n-aware function/hook (e.g., useFormatDate or the
new/shared relative formatter) so all user-facing text is translated and
centralized.
| --secondary: 220 9% 65%; | ||
| --secondary-foreground: 240 5.9% 10%; | ||
| --muted: 240 4.8% 95.9%; | ||
| --muted: 0 0% 9.02%; |
There was a problem hiding this comment.
Restore a light neutral for --muted.
--muted: 0 0% 9.02% makes bg-muted nearly black in light mode. Because this PR also moves several surfaces from bg-secondary/20 to bg-muted, light-theme panels now render as dark blocks, and places like the message improvement preview end up with dark content on a dark background. Keep the 9.02% token in .dark, but use a light neutral in :root.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@services/platform/app/globals.css` at line 106, The :root CSS variable
--muted currently uses a very dark neutral (0 0% 9.02%), causing bg-muted to
render nearly black in light mode; change the --muted value defined in :root in
globals.css back to a light neutral (a high lightness percentage) while leaving
the existing .dark { --muted: 0 0% 9.02%; } unchanged so dark-mode keeps the
9.02% token; this will restore light-mode panels that rely on bg-muted (and
places like the message improvement preview moved from bg-secondary/20) to
display correctly.
…alog ConvexItemOf resolves to object without codegen in CI, so define the needed shape locally.
- Fix rounded-rt-xl/rounded-lt-xl typos to rounded-tr-xl/rounded-tl-xl - Replace hardcoded hex colors in chat upload spinner with semantic tokens - Restore light-mode --muted CSS variable (was nearly black) - Fix import ordering in team-edit-dialog, move type after imports - Use Promise.allSettled for team member mutations (create + edit) - Prevent removing last team member in edit dialog - Replace hardcoded "Loading..."/"Unknown" with i18n keys - Remove non-functional memberCount/createdAt columns from teams table - Remove custom formatRelativeTime in favor of existing date utilities
The --input CSS variable was changed to pure white in #1038 for Input background fill, which broke border visibility on Textarea (border-input) and Switch track (bg-input) in light mode. Use --border instead, which remains a visible gray in both light and dark modes.
The --input CSS variable was changed to pure white in #1038 for Input background fill, which broke border visibility on Textarea (border-input) and Switch track (bg-input) in light mode. Use --border instead, which remains a visible gray in both light and dark modes.
Summary
Test plan
Summary by CodeRabbit
Release Notes
New Features
Style & Improvements