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
2 changes: 1 addition & 1 deletion .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
".": "1.12.0"
".": "1.13.0"
}
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## [1.13.0](https://github.com/trycompai/crm/compare/v1.12.0...v1.13.0) (2026-08-12)


### Features

* **app:** search company dropdowns instead of scrolling them ([#125](https://github.com/trycompai/crm/issues/125)) ([3b558a8](https://github.com/trycompai/crm/commit/3b558a82155d556aa4ff2860121ddf314e2ae88c))


### Fixes

* **agent:** let the assistant chat read the deal list it is told to use (CMP-77) ([#139](https://github.com/trycompai/crm/issues/139)) ([e86a0fb](https://github.com/trycompai/crm/commit/e86a0fbe4076ec3ead43f9ab24c3ac805070819a))
* **app:** show select field values in record tables ([#133](https://github.com/trycompai/crm/issues/133)) ([1d89b43](https://github.com/trycompai/crm/commit/1d89b43e2d0ad376970be09f9446f1903203ec09))

## [1.12.0](https://github.com/trycompai/crm/compare/v1.11.0...v1.12.0) (2026-08-11)


Expand Down
4 changes: 1 addition & 3 deletions apps/agent/agent/tools/list_deals.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { defineTool } from "eve/tools";
import { z } from "zod";
import { listDeals } from "../lib/lookup";
import { assertResearchPurpose } from "../lib/session-purpose";

export default defineTool({
description:
Expand All @@ -22,8 +21,7 @@ export default defineTool({
limit: z.number().int().min(1).max(100).default(50),
cursor: z.string().optional(),
}),
async execute(input, ctx) {
assertResearchPurpose(ctx);
async execute(input) {
return listDeals(input);
},
toModelOutput(output) {
Expand Down
9 changes: 9 additions & 0 deletions apps/agent/test/custom-agent-runtime.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ describe("session purpose boundaries", () => {
expect(() => assertResearchPurpose(context("team-agent"))).toThrow();
});

it("leaves the read-only deal list open to the assistant chat that is told to use it", async () => {
const source = await Bun.file(
new URL("../agent/tools/list_deals.ts", import.meta.url),
).text();

expect(source).not.toContain("assertResearchPurpose");
expect(builderTaskMarkdown(null)).toContain("list_deals");
});

it("binds specialist tools to their explicit session purpose", () => {
expect(
requireBuilderAttribute(
Expand Down
51 changes: 25 additions & 26 deletions apps/app/app/(app)/[slug]/contacts/contacts-bulk-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@ import TrashCan from "@carbon/icons-react/es/TrashCan";
import {
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
} from "@crm/ui/components/dropdown-menu";
import { formatCount } from "@crm/ui/lib/format";
import { useMutation, useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useRef, useState } from "react";
import { toast } from "sonner";
import {
BulkActionsMenu,
BulkDeleteDialog,
BulkOwnerMenu,
reportBulk,
} from "@/components/crm/bulk-actions";
import { CompanyMenuSearch } from "@/components/crm/company-picker";
import { useCrmCache } from "@/lib/trpc/cache";
import { useTRPC } from "@/lib/trpc/client";

Expand All @@ -38,8 +38,9 @@ export function ContactsBulkActions({
const trpc = useTRPC();
const cache = useCrmCache();
const users = useQuery(trpc.users.list.queryOptions());
const companies = useQuery(trpc.companies.options.queryOptions({ q: "" }));
const [confirming, setConfirming] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const companySearch = useRef<HTMLInputElement>(null);

const onError = (error: { message: string }) => toast.error(error.message);

Expand Down Expand Up @@ -99,36 +100,34 @@ export function ContactsBulkActions({

return (
<>
<BulkActionsMenu pending={pending}>
<BulkActionsMenu
pending={pending}
open={menuOpen}
onOpenChange={setMenuOpen}
>
<BulkOwnerMenu
users={users.data ?? []}
unassignedLabel="Nobody"
onSelect={(ownerId) => assignOwner.mutate({ ids, ownerId })}
/>
<DropdownMenuSub>
<DropdownMenuSubTrigger>Move to company</DropdownMenuSubTrigger>
<DropdownMenuSubContent className="max-h-72 overflow-y-auto">
<DropdownMenuGroup>
<DropdownMenuItem
onSelect={() => setCompany.mutate({ ids, companyId: null })}
>
No company
</DropdownMenuItem>
{companies.data?.length === 0 ? (
<DropdownMenuLabel>No companies yet.</DropdownMenuLabel>
) : (
companies.data?.map((company) => (
<DropdownMenuItem
key={company.id}
onSelect={() =>
setCompany.mutate({ ids, companyId: company.id })
}
>
{company.name}
</DropdownMenuItem>
))
)}
</DropdownMenuGroup>
<DropdownMenuSubContent
className="w-64 p-0"
onFocus={(event) => {
if (event.target === event.currentTarget) {
companySearch.current?.focus();
}
}}
>
<CompanyMenuSearch
none="No company"
inputRef={companySearch}
onSelect={(companyId) => {
setMenuOpen(false);
setCompany.mutate({ ids, companyId });
}}
/>
</DropdownMenuSubContent>
</DropdownMenuSub>
<DropdownMenuGroup>
Expand Down
23 changes: 20 additions & 3 deletions apps/app/app/(app)/[slug]/contacts/contacts-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import {
} from "@crm/ui/components/data-table";
import { EmptyCellValue } from "@crm/ui/components/empty-cell";
import { PersonAvatar } from "@crm/ui/components/person-avatar";
import { useSearchInput } from "@crm/ui/hooks/use-search-input";
import { useTableSelection } from "@crm/ui/hooks/use-table-selection";
import { useQuery } from "@tanstack/react-query";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { CompanyCell } from "@/components/crm/company-cell";
import { contactName } from "@/components/crm/contact-name";
import { useFieldColumns } from "@/components/crm/fields/field-columns";
Expand Down Expand Up @@ -130,7 +131,16 @@ export function ContactsTable() {
placeholderData: (previous) => previous,
});
const users = useQuery(trpc.users.list.queryOptions());
const companies = useQuery(trpc.companies.options.queryOptions({ q: "" }));

const [companyQuery, setCompanyQuery] = useState("");
const [companyText, setCompanyText] = useSearchInput(
companyQuery,
setCompanyQuery,
);
const companies = useQuery({
...trpc.companies.options.queryOptions({ q: companyQuery }),
placeholderData: (previous) => previous,
});

const rows = contacts.data?.rows ?? [];
const selection = useTableSelection(
Expand All @@ -154,8 +164,15 @@ export function ContactsTable() {
{
id: "company",
label: "Company",
searchable: true,
search: companyText,
onSearchChange: setCompanyText,
stale: companies.isFetching || companyText.trim() !== companyQuery.trim(),
empty: companies.isFetching ? "Searching…" : "No company matches.",
options: [
{ value: "none", label: "No company" },
...(companyQuery.trim()
? []
: [{ value: "none", label: "No company" }]),
...(companies.data ?? []).map((company) => ({
value: company.id,
label: company.name,
Expand Down
21 changes: 7 additions & 14 deletions apps/app/app/(app)/[slug]/contacts/create-contact-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { parseAsBoolean, useQueryState } from "nuqs";
import { type ComponentProps, Suspense, useId, useState } from "react";
import { toast } from "sonner";
import { CompanyPicker } from "@/components/crm/company-picker";
import { useOpenRecord } from "@/components/crm/record-sheet/record-stack";
import { useCrmCache } from "@/lib/trpc/cache";
import { useTRPC } from "@/lib/trpc/client";
Expand Down Expand Up @@ -72,7 +73,6 @@ function CreateContactForm({ companyId }: { companyId?: string }) {
const titleId = useId();

const users = useQuery(trpc.users.list.queryOptions());
const companies = useQuery(trpc.companies.options.queryOptions({ q: "" }));

const create = useMutation(
trpc.contacts.create.mutationOptions({
Expand Down Expand Up @@ -167,19 +167,12 @@ function CreateContactForm({ companyId }: { companyId?: string }) {

<Field>
<FieldLabel htmlFor="create-contact-company">Company</FieldLabel>
<Select value={company} onValueChange={setCompany}>
<SelectTrigger id="create-contact-company">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value={NONE}>No company</SelectItem>
{(companies.data ?? []).map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.name}
</SelectItem>
))}
</SelectContent>
</Select>
<CompanyPicker
id="create-contact-company"
value={company}
onValueChange={setCompany}
none={{ value: NONE, label: "No company" }}
/>
</Field>

<Field>
Expand Down
19 changes: 6 additions & 13 deletions apps/app/app/(app)/[slug]/deals/create-deal-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { useMutation, useQuery } from "@tanstack/react-query";
import { parseAsBoolean, useQueryState } from "nuqs";
import { type ComponentProps, Suspense, useId, useState } from "react";
import { toast } from "sonner";
import { CompanyPicker } from "@/components/crm/company-picker";
import { useOpenRecord } from "@/components/crm/record-sheet/record-stack";
import { dealStageLabel, OPEN_STAGES } from "@/lib/deal-stage";
import { useCrmCache } from "@/lib/trpc/cache";
Expand Down Expand Up @@ -80,7 +81,6 @@ function CreateDealForm({ companyId }: { companyId?: string }) {
const closeDateId = useId();

const users = useQuery(trpc.users.list.queryOptions());
const companies = useQuery(trpc.companies.options.queryOptions({ q: "" }));
const me = useQuery(trpc.users.me.queryOptions());
const currencies = useQuery(trpc.currency.settings.queryOptions());

Expand Down Expand Up @@ -154,18 +154,11 @@ function CreateDealForm({ companyId }: { companyId?: string }) {

<Field>
<FieldLabel htmlFor="create-deal-company">Company</FieldLabel>
<Select value={company} onValueChange={setCompany}>
<SelectTrigger id="create-deal-company">
<SelectValue placeholder="Choose a company" />
</SelectTrigger>
<SelectContent>
{(companies.data ?? []).map((option) => (
<SelectItem key={option.id} value={option.id}>
{option.name}
</SelectItem>
))}
</SelectContent>
</Select>
<CompanyPicker
id="create-deal-company"
value={company}
onValueChange={setCompany}
/>
</Field>

<Field>
Expand Down
6 changes: 5 additions & 1 deletion apps/app/components/crm/bulk-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,17 @@ export function reportBulk(

export function BulkActionsMenu({
pending,
open,
onOpenChange,
children,
}: {
pending?: boolean;
open?: boolean;
onOpenChange?: (open: boolean) => void;
children: ReactNode;
}) {
return (
<DropdownMenu>
<DropdownMenu open={open} onOpenChange={onOpenChange}>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" disabled={pending}>
{pending ? <Spinner /> : null}
Expand Down
Loading