Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ function MobileNavigationVisual({
className="w-72 p-0"
hideClose
>
<NavigationMenu className="bg-background flex h-full w-full max-w-none flex-col">
<NavigationMenu className="bg-sidebar flex h-full w-full max-w-none flex-col">
<div className="border-border flex h-14 shrink-0 items-center border-b px-4 py-2">
<div className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded text-xs font-bold">
T
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export function MobileNavigation({ organizationId }: MobileNavigationProps) {
className="w-72 p-0"
hideClose
>
<NavigationMenu className="bg-background flex h-full w-full max-w-none flex-col">
<NavigationMenu className="bg-sidebar flex h-full w-full max-w-none flex-col">
<div className="border-border flex h-(--nav-size) flex-shrink-0 items-center border-b px-4 py-2">
<Link
to="/dashboard/$id/chat"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ function SidebarShell({
showLogo?: boolean;
}) {
return (
<NavigationMenu className="bg-background border-border flex h-[500px] w-14 flex-col rounded-lg border">
<NavigationMenu className="bg-sidebar border-border flex h-[500px] w-14 flex-col rounded-lg border">
{showLogo && (
<div className="flex flex-shrink-0 items-center justify-center py-3">
<div className="bg-primary text-primary-foreground flex size-8 items-center justify-center rounded text-xs font-bold">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ export function Navigation({ organizationId }: NavigationProps) {
const navigationItems = useNavigationItems(organizationId);

return (
<NavigationMenu className="bg-background border-border flex h-full flex-col">
<NavigationMenu className="border-border flex h-full flex-col">
<div className="flex flex-shrink-0 items-center justify-center py-3">
<Link
to="/dashboard/$id/chat"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ vi.mock('@/lib/i18n/client', () => ({
'agentSelector.searchPlaceholder': 'Search agents...',
'agentSelector.noResults': 'No agents found',
'agentSelector.addAgent': 'Add agent',
'agentSelector.configureAgent': 'Configure agent',
};
return translations[key] ?? key;
},
Expand Down Expand Up @@ -112,10 +111,8 @@ vi.mock(
}),
);

const mockNavigate = vi.fn();
vi.mock('@tanstack/react-router', () => ({
useSearch: () => ({}),
useNavigate: () => mockNavigate,
useLocation: () => ({ pathname: '/dashboard/org-1/chat' }),
}));

Expand Down Expand Up @@ -226,44 +223,6 @@ describe('AgentSelector', () => {
});
});

it('shows configure button for all agents when user has write permission', async () => {
const { user } = render(<AgentSelector organizationId="org-1" />);

const trigger = screen.getByLabelText('Select agent');
await user.click(trigger);

const configButtons = screen.getAllByLabelText('Configure agent');
expect(configButtons).toHaveLength(2);
});

it('does not show configure button when user lacks write permission', async () => {
mockCanWrite = false;

const { user } = render(<AgentSelector organizationId="org-1" />);

const trigger = screen.getByLabelText('Select agent');
await user.click(trigger);

expect(screen.queryByLabelText('Configure agent')).not.toBeInTheDocument();
});

it('navigates to agent config when configure button is clicked', async () => {
const { user } = render(<AgentSelector organizationId="org-1" />);

const trigger = screen.getByLabelText('Select agent');
await user.click(trigger);

const configButtons = screen.getAllByLabelText('Configure agent');
const customAgentConfig = configButtons[1];
expect(customAgentConfig).toBeDefined();
await user.click(customAgentConfig);

expect(mockNavigate).toHaveBeenCalledWith({
to: '/dashboard/$id/custom-agents/$agentId',
params: { id: 'org-1', agentId: 'root-2' },
});
});

it('only highlights one agent when multiple have isSystemDefault', async () => {
mockAgents = [
{
Expand Down
45 changes: 4 additions & 41 deletions services/platform/app/features/chat/components/agent-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
'use client';

import { useNavigate } from '@tanstack/react-router';
import { Bot, ChevronDown, Plus, Settings } from 'lucide-react';
import { type MouseEvent, useCallback, useMemo, useState } from 'react';
import { Bot, ChevronDown, Plus } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';

import {
SearchableSelect,
type SearchableSelectOption,
} from '@/app/components/ui/forms/searchable-select';
import { SearchableSelect } from '@/app/components/ui/forms/searchable-select';
import { Button } from '@/app/components/ui/primitives/button';
import { IconButton } from '@/app/components/ui/primitives/icon-button';
import { CreateCustomAgentDialog } from '@/app/features/custom-agents/components/custom-agent-create-dialog';
import { useAbility } from '@/app/hooks/use-ability';
import { useDialogSearchParam } from '@/app/hooks/use-dialog-search-param';
Expand All @@ -26,7 +21,6 @@ interface AgentSelectorProps {
export function AgentSelector({ organizationId }: AgentSelectorProps) {
const { t } = useT('chat');
const ability = useAbility();
const navigate = useNavigate();
const { setSelectedAgent } = useChatLayout();
const effectiveAgent = useEffectiveAgent(organizationId);
const { agents: allAgents } = useChatAgents(organizationId);
Expand Down Expand Up @@ -79,35 +73,6 @@ export function AgentSelector({ organizationId }: AgentSelectorProps) {
createAgentDialog.open();
}, [createAgentDialog]);

const handleConfigClick = useCallback(
(option: SearchableSelectOption, e: MouseEvent) => {
e.stopPropagation();
setOpen(false);
void navigate({
to: '/dashboard/$id/custom-agents/$agentId',
params: { id: organizationId, agentId: option.value },
});
},
[navigate, organizationId],
);

const renderOptionAction = useCallback(
(option: SearchableSelectOption) => {
if (!canManageAgents) return null;
return (
<IconButton
icon={Settings}
aria-label={t('agentSelector.configureAgent')}
variant="ghost"
iconSize={4}
className="size-8 shrink-0 rounded-md"
onClick={(e) => handleConfigClick(option, e)}
/>
);
},
[canManageAgents, t, handleConfigClick],
);

return (
<>
<SearchableSelect
Expand All @@ -119,12 +84,10 @@ export function AgentSelector({ organizationId }: AgentSelectorProps) {
align="start"
side="top"
sideOffset={8}
contentClassName="w-[20rem]"
contentClassName="w-[16.25rem]"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using a design token for the dropdown width.

The specific width 16.25rem (260px) works, but if this value is used elsewhere or needs to stay consistent with other UI elements, consider extracting it to a CSS variable or design token for maintainability.

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

In `@services/platform/app/features/chat/components/agent-selector.tsx` at line
87, The dropdown in agent-selector.tsx currently hardcodes
contentClassName="w-[16.25rem]"; replace this literal width with a design token
or CSS variable (e.g., use a Tailwind custom utility class or a CSS var like
var(--dropdown-width)) so the size is centralized and reusable; update the
component to reference that token/class (where contentClassName is assigned) and
ensure any corresponding CSS/Tailwind config defines the token (or utility) to
match the previous 16.25rem value.

searchPlaceholder={t('agentSelector.searchPlaceholder')}
emptyText={t('agentSelector.noResults')}
aria-label={t('agentSelector.label')}
showRadio
optionAction={renderOptionAction}
trigger={
<button
type="button"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export function ActivateConversationsEmptyState({
const { t } = useT('conversations');

return (
<div className="ring-border m-4 flex flex-1 items-center justify-center rounded-xl px-4 py-12 ring-1">
<div className="m-4 flex flex-1 items-center justify-center rounded-xl px-4 py-12">
<div className="flex max-w-md flex-col items-center text-center">
<MessageSquare className="text-muted-foreground mb-4 size-6" />
<Heading level={2} size="lg" className="mb-1">
Expand Down
42 changes: 23 additions & 19 deletions services/platform/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
--color-input: hsl(var(--input));
--color-ring: hsl(var(--ring));

--color-sidebar: hsl(var(--sidebar));

--color-chart-1: hsl(var(--chart-1));
--color-chart-2: hsl(var(--chart-2));
--color-chart-3: hsl(var(--chart-3));
Expand Down Expand Up @@ -117,6 +119,7 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--sidebar: 0 0% 98.8%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
Expand All @@ -128,31 +131,32 @@

/* CSS variables for dark mode */
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 220 12% 75%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--background: 224 71% 4%;
--foreground: 0 0% 100%;
--card: 224 71% 4%;
--card-foreground: 0 0% 100%;
--popover: 224 71% 4%;
--popover-foreground: 0 0% 100%;
--sidebar: 218 31% 7%;
--primary: 0 0% 100%;
--primary-foreground: 222 73% 3%;
--secondary: 218 11% 85%;
--secondary-foreground: 0 0% 100%;
--muted: 217 19% 17%;
--muted-foreground: 218 11% 65%;
--accent: 217 19% 17%;
--accent-foreground: 0 0% 100%;
--destructive: 0 91% 71%;
--destructive-foreground: 0 0% 100%;
--success: 155 85% 35%;
--success-foreground: 0 0% 98%;
--warning: 38 92% 50%;
--warning-foreground: 0 0% 98%;
--info: 214 35% 18%;
--info-foreground: 214 80% 65%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
--border: 217 13% 34%;
--input: 217 13% 34%;
--ring: 217 100% 66%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
Expand Down
4 changes: 2 additions & 2 deletions services/platform/app/routes/dashboard/$id.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ function DashboardLayout() {
<TeamFilterProvider organizationId={organizationId}>
<AdaptiveHeaderProvider>
<div className="flex size-full flex-col overflow-hidden md:flex-row">
<div className="bg-background flex h-[--nav-size] items-center gap-2 p-2 md:hidden">
<div className="bg-sidebar flex h-[--nav-size] items-center gap-2 p-2 md:hidden">
<MobileNavigation organizationId={organizationId} />
<AdaptiveHeaderSlot />
</div>

<div className="hidden h-full px-2 md:flex md:flex-[0_0_var(--nav-size)]">
<div className="bg-sidebar hidden h-full px-2 md:flex md:flex-[0_0_var(--nav-size)]">
<Navigation organizationId={organizationId} />
</div>

Expand Down
1 change: 0 additions & 1 deletion services/platform/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2690,7 +2690,6 @@ export declare const components: {
maximumRowsRead?: number;
numItems: number;
};
select?: Array<string>;
sortBy?: { direction: "asc" | "desc"; field: string };
where?: Array<{
connector?: "AND" | "OR";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -980,7 +980,6 @@ export type ComponentApi<Name extends string | undefined = string | undefined> =
maximumRowsRead?: number;
numItems: number;
};
select?: Array<string>;
sortBy?: { direction: "asc" | "desc"; field: string };
where?: Array<{
connector?: "AND" | "OR";
Expand Down
11 changes: 11 additions & 0 deletions services/platform/scripts/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,17 @@ async function main() {

await waitForConvex();

// Re-read CONVEX_DEPLOYMENT from .env.local in case `convex dev` wrote it
// after our initial loadEnvFiles() call (happens on first run with fresh DB)
const platformEnvLocalPath = join(platformRoot, '.env.local');
const freshEnv = parseDotEnv(platformEnvLocalPath);
if (freshEnv.CONVEX_DEPLOYMENT && !process.env.CONVEX_DEPLOYMENT) {
process.env.CONVEX_DEPLOYMENT = freshEnv.CONVEX_DEPLOYMENT;
console.log(
`[dev] ℹ️ Picked up new deployment: ${freshEnv.CONVEX_DEPLOYMENT}`,
);
}

console.log('[dev] 🔄 Syncing environment variables...');
try {
await runCommand('bun', ['scripts/sync-convex-env-from-dotenv.ts']);
Expand Down
Loading