Skip to content

Lewis/contact and currencies - #56

Merged
carhartlewis merged 3 commits into
mainfrom
lewis/contact-and-currencies
Aug 6, 2026
Merged

Lewis/contact and currencies#56
carhartlewis merged 3 commits into
mainfrom
lewis/contact-and-currencies

Conversation

@carhartlewis

@carhartlewis carhartlewis commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary by cubic

Adds dynamic custom fields for companies, contacts, and deals with a full API, UI editor/sheet, and inline editing. Also adds bulk actions, deal–contact management, and support for ZAR.

  • New Features

    • Dynamic fields: new DB tables and fields API for list/create/update/archive/reorder; records now accept fields in update inputs and return field values.
    • Unified Fields sheet: a cog on each record opens a manager to add/reorder/hide fields and options; select fields can show as table columns.
    • Inline fields: edit custom values on company, contact, and deal sheets; agent backfill trigger added for large changes.
    • Agent tools: list_fields, manage_fields (create/update brief), set_field_value, archive_field.
    • Bulk actions: assign owner, enrich, and delete for companies/contacts/deals; contacts can bulk set company; deals can bulk set stage/owner; new table selection UI.
    • Deal people: attach/detach contacts and set roles per deal with safeguards; picker and APIs added.
    • Currency: ZAR added to supported currencies.
  • Migration

    • Run the new database migration (adds FieldDefinition/FieldOption/FieldValue).
    • Install UI deps for drag-and-drop: @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/modifiers, @dnd-kit/utilities, then rebuild.
    • Update clients to send optional fields in company.update, contact.update, and deal.update when writing custom values.

Written for commit a1cbf56. Summary will update on new commits.

Review in cubic

- Added new fields module with CRUD operations for custom fields.
- Introduced tools for creating, updating, archiving, and listing fields.
- Integrated field management into existing entities (companies, contacts, deals) for enhanced data handling.
- Updated contracts and services to support bulk operations for fields.
- Enhanced error handling and validation for field operations.
- Updated README to reflect the new name and purpose of the CRM.
- Introduced new input types and mutations for managing deal contacts, including attaching, detaching, and setting roles for contacts on deals.
- Implemented service methods to handle contact options and role assignments.
- Added tests for the new deal contact functionalities to ensure proper behavior.
- Updated API documentation to include new endpoints for deal contact management.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
crm-agent Ready Ready Preview Aug 6, 2026 7:40pm
crm-api Ready Ready Preview Aug 6, 2026 7:40pm
crm-app Ready Ready Preview Aug 6, 2026 7:40pm

Request Review

@cubic-dev-ai cubic-dev-ai 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.

38 issues found across 82 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/app/lib/trpc/cache.ts">

<violation number="1" location="apps/app/lib/trpc/cache.ts:96">
P2: After a contact or company bulk delete, open attach-contact and contact-assignment controls can still offer the deleted record, leading to a stale selection and a `NotFoundException`/invalid assignment. Adding the corresponding `deals.contactOptions` or `companies.options` query key to the removal invalidation set would refresh these controls.</violation>
</file>

<file name="README.md">

<violation number="1" location="README.md:17">
P3: The new tagline "an open source, CRM designed" has a misplaced comma that breaks the noun phrase — it should be "an open source CRM" (or "an open-source CRM"). The line also repeats "CRM" (Comp AI CRM ... CRM designed) immediately under the "CRM" h1 and is restated by the following "Agentic-first CRM." line, so consider tightening wording.</violation>
</file>

<file name="apps/app/components/crm/bulk-actions.tsx">

<violation number="1" location="apps/app/components/crm/bulk-actions.tsx:48">
P3: Partial bulk operations render ungrammatical feedback when exactly one record fails (`1 were left alone`); use singular wording for `failed === 1` so the toast remains clear.</violation>
</file>

<file name="packages/ui/src/components/checkbox.tsx">

<violation number="1" location="packages/ui/src/components/checkbox.tsx:25">
P3: Picking the icon from the controlled `checked` prop breaks the uncontrolled case: when a consumer uses `defaultChecked="indeterminate"` (supported by Radix), `props.checked` is `undefined` so the indicator renders a checkmark even though the box shows the indeterminate styling and data-state. Consider driving the icon from the rendered data-state (e.g. a data-[state=indeterminate] selector on a nested icon) so it matches whatever state Radix actually applies.</violation>
</file>

<file name="apps/app/components/crm/record-sheet/record-stack.ts">

<violation number="1" location="apps/app/components/crm/record-sheet/record-stack.ts:36">
P2: A stale or hand-edited `field` URL opens the create form and can create a new field when saved, because any non-empty string is treated as an editor key but a missing lookup is treated as new. Validating the key against the loaded field list (and allowing create mode only for `field=new`) would prevent accidental mutations from invalid links.</violation>
</file>

<file name="apps/agent/agent/tools/archive_field.ts">

<violation number="1" location="apps/agent/agent/tools/archive_field.ts:6">
P2: The new tool is not registered in the repository's telemetry and transcript tool maps, so its calls are bucketed as `other` and the authored-tool coverage tests fail. Adding `archive_field` to `AGENT_TOOLS` and `TOOL_VERBS` alongside this tool would preserve per-tool metrics and human-readable transcript entries.</violation>
</file>

<file name="apps/api/src/agent/agent-trigger.service.ts">

<violation number="1" location="apps/api/src/agent/agent-trigger.service.ts:73">
P1: Backfills for same-named fields on different record types are collapsed into one task, and the worker cannot tell which entity to scan. Include the entity in the method signature and task identity/reason so each `(entity, key)` field gets its own task.</violation>

<violation number="2" location="apps/api/src/agent/agent-trigger.service.ts:86">
P2: Concurrent field creation, updates, or manual backfill requests can enqueue duplicate work for the same field, causing repeated research and breaking the one-task-per-field guarantee. Make the deduplication atomic with a unique task identity/upsert or an appropriate transaction/lock.</violation>
</file>

<file name="apps/api/src/companies/companies.service.ts">

<violation number="1" location="apps/api/src/companies/companies.service.ts:324">
P1: A failed company update can still persist custom-field changes, and a multi-field request can persist only its earlier fields, leaving the record in a partially updated state. Applying the field writes and company update in one transaction would make the update atomic.</violation>
</file>

<file name="packages/ui/src/components/sortable-list.tsx">

<violation number="1" location="packages/ui/src/components/sortable-list.tsx:102">
P2: If a `SortableItem` is rendered inside a form, clicking its drag handle submits the form because this activator has no explicit button type. Setting `type="button"` keeps reordering from triggering form submission.</violation>

<violation number="2" location="packages/ui/src/components/sortable-list.tsx:106">
P2: On touch devices, dragging from this handle can be interpreted as vertical scrolling instead of starting the sort. Adding `touch-none` to the activator prevents native touch panning from cancelling the PointerSensor gesture.</violation>
</file>

<file name="packages/ui/src/components/data-table.tsx">

<violation number="1" location="packages/ui/src/components/data-table.tsx:544">
P2: During a page or filter update, selection can temporarily operate on different rows from those displayed because the table renders `deferredRows` while the selection state tracks immediate `rows`. Align the selection row IDs with the deferred rows or avoid deferring rendered rows when selection is enabled to prevent selecting unseen records during the transition.</violation>

<violation number="2" location="packages/ui/src/components/data-table.tsx:576">
P2: On viewports below `sm`, the new selection cells are hidden, so Contacts, Companies, and Deals users cannot select a row or start their bulk actions on mobile. Keeping the selection column visible, or providing an alternate mobile selection control, would preserve this feature at the DataTable’s mobile breakpoint.</violation>
</file>

<file name="apps/app/components/crm/fields/field-editor.tsx">

<violation number="1" location="apps/app/components/crm/fields/field-editor.tsx:124">
P2: Editing a manual-only field still sends a coverage request even though the component immediately renders nothing; conditionally enable this query with `field.agentFilled` so manual fields do not perform the two unnecessary coverage counts.</violation>
</file>

<file name="apps/agent/agent/lib/fields.ts">

<violation number="1" location="apps/agent/agent/lib/fields.ts:37">
P3: `readFields` is exported but has no repository caller, leaving this new record-reading path dead; either wire it into an agent read flow or remove it to avoid unused behavior that can drift from the shared service.</violation>

<violation number="2" location="apps/agent/agent/lib/fields.ts:79">
P2: Clearing a field on a nonexistent or wrong-type record reports success even though nothing changed; validate that the target record exists for the selected entity before calling `writeValues`.</violation>

<violation number="3" location="apps/agent/agent/lib/fields.ts:118">
P2: A SELECT with a whitespace-only option passes the length check and creates an option that can never be selected; trim and reject blank option labels before creating the definition.</violation>

<violation number="4" location="apps/agent/agent/lib/fields.ts:134">
P1: Creating a manual-only field through `manage_fields` still stores `agentFilled: true`, so the agent can later write to a field the rep explicitly handed back; pass the tool flag through `createField` and persist it, defaulting only when absent.</violation>

<violation number="5" location="apps/agent/agent/lib/fields.ts:148">
P1: Creating a field or changing an agent-filled field's brief through these agent tools never queues the required `field-backfill` task, so existing records are not evaluated with the new field or instructions; mirror the backfill trigger used by the API path after these writes.</violation>
</file>

<file name="apps/agent/agent/tools/set_field_value.ts">

<violation number="1" location="apps/agent/agent/tools/set_field_value.ts:6">
P2: The new tool is not registered in the repository’s tool registries, so telemetry buckets its calls as `other` and the authored-tool transcript/allowlist checks fail. Add `set_field_value` to `AGENT_TOOLS` and give it an explicit transcript verb alongside the tool file.</violation>
</file>

<file name="apps/app/components/crm/fields/fields-list.tsx">

<violation number="1" location="apps/app/components/crm/fields/fields-list.tsx:159">
P2: The Standard fields section promises “reorder and hide only,” but each entry is rendered as static text with no reorder or visibility control, so users cannot perform the advertised customization; wire those actions to persisted standard-field settings or remove the promise from the row copy.</violation>

<violation number="2" location="apps/app/components/crm/fields/fields-list.tsx:173">
P2: A failed fields.list request is presented as “No custom fields yet” with a New field button because only the pending state is handled; add an explicit `query.isError` state so transient or authorization failures are not mistaken for empty data.</violation>

<violation number="3" location="apps/app/components/crm/fields/fields-list.tsx:173">
P2: When every custom field is archived, the sheet shows the empty state and hides the Archived disclosure, leaving no Restore action and preventing users from recovering those fields; render the archived section outside this live-fields-only branch.</violation>

<violation number="4" location="apps/app/components/crm/fields/fields-list.tsx:200">
P2: Reordering live fields after an archive can assign the same `position` to an archived field that retained its old position, so restoring it can produce an unstable order; reorder all definitions together or assign archived/restored fields unique positions.</violation>
</file>

<file name="apps/api/src/fields/fields.contracts.ts">

<violation number="1" location="apps/api/src/fields/fields.contracts.ts:64">
P2: Record updates accept objects and arrays even though field values are serialized as primitives, allowing malformed values to be persisted as strings for TEXT and USER fields. Constrain each value to `string | number | boolean | null` before it reaches the coercion layer.</violation>
</file>

<file name="apps/api/src/deals/deals.service.ts">

<violation number="1" location="apps/api/src/deals/deals.service.ts:275">
P1: Custom-field changes can be committed even when the same deal update fails, leaving the API reporting an unsuccessful update while the field value has changed. Applying fields through the same transaction as the deal update (and inside the existing error translation path) would keep the update atomic and preserve consistent missing-deal errors.</violation>
</file>

<file name="docs/plan/dynamic-fields-build.md">

<violation number="1" location="docs/plan/dynamic-fields-build.md:80">
P2: This plan is a stale build order: virtually every step it tells an agent to create (fields.ts + exports entry, the fields router/service/files.spec.ts, the agent tools, the Prisma models, the fields/field URL params, the md sheet size, Badge, Collapsible, SortableList, the CrmCache.fields entry) is already present in the repo at this PR's base. An agent following it cannot perform the steps and risks re-creating or overwriting the existing, working implementation. Recommend rewriting this as a record of what shipped (or dropping the build steps) so it reflects the actual repo state before it is used as an instruction set.</violation>
</file>

<file name="packages/db/prisma/migrations/20260806140000_dynamic_fields/migration.sql">

<violation number="1" location="packages/db/prisma/migrations/20260806140000_dynamic_fields/migration.sql:12">
P2: Agents can write the wrong option when a SELECT field contains duplicate labels. Enforce case-insensitive uniqueness for active option labels per field, or reject duplicates in the create/update validation before storing them.</violation>

<violation number="2" location="packages/db/prisma/migrations/20260806140000_dynamic_fields/migration.sql:42">
P2: A field value can exist without exactly one company, contact, or deal owner, and the nullable unique indexes do not prevent duplicates for NULL owners. Add a `num_nonnulls(companyId, contactId, dealId) = 1` constraint (and enforce the selected owner matches the definition entity) so malformed rows cannot enter the table.</violation>

<violation number="3" location="packages/db/prisma/migrations/20260806140000_dynamic_fields/migration.sql:57">
P2: Concurrent field creation can make the displayed field order unstable because duplicate positions are allowed and ties have no secondary ordering. Serialize position allocation in a transaction or use a database-backed ordering strategy with a deterministic tie-breaker.</violation>

<violation number="4" location="packages/db/prisma/migrations/20260806140000_dynamic_fields/migration.sql:102">
P2: A SELECT value can reference an option from a different field because the two foreign keys are independent. Add a composite relationship/constraint tying `fieldValue.optionId` to an option whose `fieldId` equals the value's `fieldId`.</violation>
</file>

<file name="packages/db/prisma/schema.prisma">

<violation number="1" location="packages/db/prisma/schema.prisma:536">
P2: USER custom fields can store arbitrary or deleted user IDs and cannot be resolved or cleaned up with the `User` record because `userId` is only a scalar. Model this as an optional `User` relation with `onDelete: SetNull` (including the reverse relation), or use a text field if it is intentionally free-form.</violation>

<violation number="2" location="packages/db/prisma/schema.prisma:540">
P2: Opening a record or loading custom-field values will scan the entire `fieldValue` table as data grows because the only target indexes are prefixed by `fieldId`, while `valuesFor` and the agent reader filter by `companyId`, `contactId`, or `dealId` alone. Add indexes keyed by each record column.</violation>
</file>

<file name="apps/api/test/bulk.spec.ts">

<violation number="1" location="apps/api/test/bulk.spec.ts:198">
P2: The company created as `Doomed Co` with domain `doomed-${domain}` is never removed by `clean()`: cleanup matches companies by `where: { domain }` (the exact `bulk-spec.test` domain), so this orphan survives any run where the test fails before `companies.bulkDelete`. Because `companies.create` throws `ConflictException` on a duplicate domain, the next run fails in `beforeAll`/test setup with a confusing "already uses the domain" error instead of the real failure. Consider cleaning `domain: { endsWith: domain }` or tracking the created company id for removal.</violation>
</file>

<file name="apps/api/test/fields.spec.ts">

<violation number="1" location="apps/api/test/fields.spec.ts:147">
P3: `reorder` returns the full entity list, so this exact-array assertion only holds when no other COMPANY field definitions exist in the shared DB. If any seeded or leftover COMPANY field is present the test fails for reasons unrelated to this case. Prefer asserting the relative order of the two spec fields (indexes) rather than the full list.</violation>

<violation number="2" location="apps/api/test/fields.spec.ts:158">
P3: The spec couples unrelated test groups through shared server-side state and declaration order: the "field values" tests depend on fields created by earlier "field definitions" tests, and queued/companyId are mutated across tests. Any focus/filter run (bun -t, .only) or reorder silently fails. Suggest seeding required fields in a beforeAll and asserting only what each test creates.</violation>
</file>

<file name="packages/db/src/agent-tasks.ts">

<violation number="1" location="packages/db/src/agent-tasks.ts:36">
P3: The documented queue ordering is now incomplete: `field-backfill` runs at priority 20, between `companyProfile` and `recheck`, but `docs/agent.md` omits it. Updating that priority list would keep operational guidance consistent with the scheduler.</violation>
</file>

<file name="apps/agent/agent/tools/manage_fields.ts">

<violation number="1" location="apps/agent/agent/tools/manage_fields.ts:77">
P2: Updating only `agentFilled` clears the existing brief because the tool maps an omitted brief to `null`, losing the instructions that should guide future filling. Preserve an omitted brief and reserve an explicit clear value for intentionally removing it.</violation>
</file>

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

});
}

async fieldBackfill(key: string, reason: string): Promise<void> {

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P1: Backfills for same-named fields on different record types are collapsed into one task, and the worker cannot tell which entity to scan. Include the entity in the method signature and task identity/reason so each (entity, key) field gets its own task.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/agent/agent-trigger.service.ts, line 73:

<comment>Backfills for same-named fields on different record types are collapsed into one task, and the worker cannot tell which entity to scan. Include the entity in the method signature and task identity/reason so each `(entity, key)` field gets its own task.</comment>

<file context>
@@ -70,6 +70,44 @@ export class AgentTriggerService {
 		});
 	}
 
+	async fieldBackfill(key: string, reason: string): Promise<void> {
+		try {
+			const pending = await this.db.agentTask.findFirst({
</file context>
Fix with cubic


async update(id: string, input: CompanyUpdateInput) {
if (input.fields) {
await this.fields.applyValues(this.db, "COMPANY", id, input.fields);

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P1: A failed company update can still persist custom-field changes, and a multi-field request can persist only its earlier fields, leaving the record in a partially updated state. Applying the field writes and company update in one transaction would make the update atomic.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/companies/companies.service.ts, line 324:

<comment>A failed company update can still persist custom-field changes, and a multi-field request can persist only its earlier fields, leaving the record in a partially updated state. Applying the field writes and company update in one transaction would make the update atomic.</comment>

<file context>
@@ -309,6 +320,10 @@ export class CompaniesService {
 
 	async update(id: string, input: CompanyUpdateInput) {
+		if (input.fields) {
+			await this.fields.applyValues(this.db, "COMPANY", id, input.fields);
+		}
+
</file context>
Fix with cubic

include: WITH_OPTIONS,
});

return serializeField(definition);

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P1: Creating a field or changing an agent-filled field's brief through these agent tools never queues the required field-backfill task, so existing records are not evaluated with the new field or instructions; mirror the backfill trigger used by the API path after these writes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/fields.ts, line 148:

<comment>Creating a field or changing an agent-filled field's brief through these agent tools never queues the required `field-backfill` task, so existing records are not evaluated with the new field or instructions; mirror the backfill trigger used by the API path after these writes.</comment>

<file context>
@@ -0,0 +1,200 @@
+		include: WITH_OPTIONS,
+	});
+
+	return serializeField(definition);
+}
+
</file context>
Fix with cubic

key,
label: input.label,
type: input.type,
agentBrief: input.agentBrief ?? null,

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P1: Creating a manual-only field through manage_fields still stores agentFilled: true, so the agent can later write to a field the rep explicitly handed back; pass the tool flag through createField and persist it, defaulting only when absent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/fields.ts, line 134:

<comment>Creating a manual-only field through `manage_fields` still stores `agentFilled: true`, so the agent can later write to a field the rep explicitly handed back; pass the tool flag through `createField` and persist it, defaulting only when absent.</comment>

<file context>
@@ -0,0 +1,200 @@
+			key,
+			label: input.label,
+			type: input.type,
+			agentBrief: input.agentBrief ?? null,
+			position: (last?.position ?? -1) + 1,
+			options: usesOptions(input.type)
</file context>
Fix with cubic


async update(id: string, input: DealUpdateInput) {
if (input.fields) {
await this.fields.applyValues(this.db, "DEAL", id, input.fields);

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P1: Custom-field changes can be committed even when the same deal update fails, leaving the API reporting an unsuccessful update while the field value has changed. Applying fields through the same transaction as the deal update (and inside the existing error translation path) would keep the update atomic and preserve consistent missing-deal errors.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/src/deals/deals.service.ts, line 275:

<comment>Custom-field changes can be committed even when the same deal update fails, leaving the API reporting an unsuccessful update while the field value has changed. Applying fields through the same transaction as the deal update (and inside the existing error translation path) would keep the update atomic and preserve consistent missing-deal errors.</comment>

<file context>
@@ -258,6 +271,10 @@ export class DealsService {
 
 	async update(id: string, input: DealUpdateInput) {
+		if (input.fields) {
+			await this.fields.applyValues(this.db, "DEAL", id, input.fields);
+		}
+
</file context>
Fix with cubic

className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
>
<CheckIcon />
{props.checked === "indeterminate" ? <MinusIcon /> : <CheckIcon />}

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P3: Picking the icon from the controlled checked prop breaks the uncontrolled case: when a consumer uses defaultChecked="indeterminate" (supported by Radix), props.checked is undefined so the indicator renders a checkmark even though the box shows the indeterminate styling and data-state. Consider driving the icon from the rendered data-state (e.g. a data-[state=indeterminate] selector on a nested icon) so it matches whatever state Radix actually applies.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/ui/src/components/checkbox.tsx, line 25:

<comment>Picking the icon from the controlled `checked` prop breaks the uncontrolled case: when a consumer uses `defaultChecked="indeterminate"` (supported by Radix), `props.checked` is `undefined` so the indicator renders a checkmark even though the box shows the indeterminate styling and data-state. Consider driving the icon from the rendered data-state (e.g. a data-[state=indeterminate] selector on a nested icon) so it matches whatever state Radix actually applies.</comment>

<file context>
@@ -22,7 +22,7 @@ function Checkbox({
 				className="grid place-content-center text-current transition-none [&>svg]:size-3.5"
 			>
-				<CheckIcon />
+				{props.checked === "indeterminate" ? <MinusIcon /> : <CheckIcon />}
 			</CheckboxPrimitive.Indicator>
 		</CheckboxPrimitive.Root>
</file context>
Fix with cubic

return definitions.map(serializeField);
}

export async function readFields(

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P3: readFields is exported but has no repository caller, leaving this new record-reading path dead; either wire it into an agent read flow or remove it to avoid unused behavior that can drift from the shared service.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/agent/agent/lib/fields.ts, line 37:

<comment>`readFields` is exported but has no repository caller, leaving this new record-reading path dead; either wire it into an agent read flow or remove it to avoid unused behavior that can drift from the shared service.</comment>

<file context>
@@ -0,0 +1,200 @@
+	return definitions.map(serializeField);
+}
+
+export async function readFields(
+	entity: FieldEntity,
+	recordId: string,
</file context>
Fix with cubic

ids: [second.id, first.id],
});

expect(reordered.map((field) => field.key)).toEqual([

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P3: reorder returns the full entity list, so this exact-array assertion only holds when no other COMPANY field definitions exist in the shared DB. If any seeded or leftover COMPANY field is present the test fails for reasons unrelated to this case. Prefer asserting the relative order of the two spec fields (indexes) rather than the full list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/test/fields.spec.ts, line 147:

<comment>`reorder` returns the full entity list, so this exact-array assertion only holds when no other COMPANY field definitions exist in the shared DB. If any seeded or leftover COMPANY field is present the test fails for reasons unrelated to this case. Prefer asserting the relative order of the two spec fields (indexes) rather than the full list.</comment>

<file context>
@@ -0,0 +1,221 @@
+			ids: [second.id, first.id],
+		});
+
+		expect(reordered.map((field) => field.key)).toEqual([
+			"spec_seats",
+			"spec_runs_on",
</file context>
Fix with cubic

});
});

describe("field values", () => {

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P3: The spec couples unrelated test groups through shared server-side state and declaration order: the "field values" tests depend on fields created by earlier "field definitions" tests, and queued/companyId are mutated across tests. Any focus/filter run (bun -t, .only) or reorder silently fails. Suggest seeding required fields in a beforeAll and asserting only what each test creates.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/api/test/fields.spec.ts, line 158:

<comment>The spec couples unrelated test groups through shared server-side state and declaration order: the "field values" tests depend on fields created by earlier "field definitions" tests, and queued/companyId are mutated across tests. Any focus/filter run (bun -t, .only) or reorder silently fails. Suggest seeding required fields in a beforeAll and asserting only what each test creates.</comment>

<file context>
@@ -0,0 +1,221 @@
+	});
+});
+
+describe("field values", () => {
+	it("round-trips each storage class", async () => {
+		await fields.create({
</file context>
Fix with cubic

identify: 100,
sweep: 50,
companyProfile: 40,
fieldBackfill: 20,

@cubic-dev-ai cubic-dev-ai Bot Aug 6, 2026

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.

P3: The documented queue ordering is now incomplete: field-backfill runs at priority 20, between companyProfile and recheck, but docs/agent.md omits it. Updating that priority list would keep operational guidance consistent with the scheduler.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/agent-tasks.ts, line 36:

<comment>The documented queue ordering is now incomplete: `field-backfill` runs at priority 20, between `companyProfile` and `recheck`, but `docs/agent.md` omits it. Updating that priority list would keep operational guidance consistent with the scheduler.</comment>

<file context>
@@ -32,5 +33,6 @@ export const PRIORITY = {
 	identify: 100,
 	sweep: 50,
 	companyProfile: 40,
+	fieldBackfill: 20,
 	recheck: 0,
 } as const;
</file context>
Fix with cubic

@carhartlewis
carhartlewis merged commit c04e4b2 into main Aug 6, 2026
5 of 8 checks passed
@carhartlewis
carhartlewis deleted the lewis/contact-and-currencies branch August 6, 2026 20:04
@github-actions github-actions Bot mentioned this pull request Aug 7, 2026
strats360 pushed a commit to strats360/crm that referenced this pull request Aug 24, 2026
* Implement currency conversion features and enhance deal handling

- Introduced a new CurrencyModule to manage currency conversion and rates.
- Added ConversionService for handling currency conversions and fetching rates.
- Updated DealsService to support base amounts and currency conversion logic.
- Enhanced Deal and Dashboard functionalities to include reporting currency and unconverted deals.
- Implemented new currency-related contracts and routes for setting reporting currency and manual rates.
- Added integration tests to ensure correct handling of currency conversions and deal totals.

* Refactor currency rates service to use open.er-api.com

- Updated the currency rates service to fetch exchange rates from open.er-api.com, replacing the previous provider frankfurter.dev.
- Enhanced error handling to check for unsupported base currencies in the response.
- Implemented retry logic for fetching rates with a maximum of two attempts and a reduced timeout.
- Cleaned up stale exchange rates for unsupported currencies during the refresh process.
- Updated documentation to reflect the new exchange rate provider and its implications.

* Enhance currency handling and conversion logic

- Introduced baseCurrency to the Deal model to track the currency of baseAmount.
- Updated ConversionService to streamline currency conversion processes and improve deal field handling.
- Enhanced CurrencyService to enforce permissions for managing currency settings based on user roles.
- Refactored DealsService to incorporate base currency logic in deal aggregations and reporting.
- Improved DashboardService to accurately reflect open deal values based on the current reporting currency.
- Updated integration tests to validate new currency handling features and ensure correct behavior across services.

* Enhance currency conversion logic and improve deal handling

- Updated `pendingWhere` method in `ConversionService` to explicitly match null `baseCurrency`, ensuring no deals are excluded from totals.
- Added integration test to verify that deals with missing currency are correctly handled and updated.
- Modified seeding logic to ensure `baseCurrency` is set alongside `baseAmount` for newly created deals, preventing issues with unconverted figures.
- Updated documentation to clarify changes in currency handling and the implications for deal visibility.

* Refine currency conversion logic and enhance deal handling

- Updated `ConversionService` to conditionally clear rates only when `onlyMissing` is false, improving efficiency in handling missing currencies.
- Enhanced integration tests to verify correct behavior when dealing with unconverted figures and missing currency rates.
- Introduced a new utility function in the deal sheet component to manage currency options, ensuring proper display of unsupported currencies.

* Revise agent and API documentation for clarity and structure

- Updated AGENTS.md to emphasize the importance of reviewing relevant documentation before starting work, including a new index table for quick reference.
- Refined API rules in api.md to clarify logging practices and the separation of intelligence from the API.
- Consolidated environment setup instructions into a new setup.md file for better organization and ease of access.
- Enhanced currency handling in DashboardService and related tests to ensure accurate reporting and conversion logic.
- Improved integration tests to validate new currency handling features and ensure correct behavior across services.

* Enhance documentation and introduce new currency handling guidelines

- Updated AGENTS.md to include new references for the Agent panel and local setup instructions.
- Added a new docs/agent-panel.md file detailing the Agent panel's functionality and usage.
- Introduced docs/currency.md to clarify currency handling rules and reporting practices.
- Revised environment setup instructions in docs/environment.md for better clarity and organization.

* Add anonymous usage telemetry documentation and enhance currency handling in DealSheet

* Implement anonymous usage telemetry and enhance related documentation

- Added telemetry functionality to track anonymous usage data, including installation metrics and tool usage.
- Introduced new environment variables for telemetry configuration in `.env.example`.
- Updated `AGENTS.md` to reference the new telemetry documentation.
- Created a `TelemetryModule` with services and controllers for managing telemetry data.
- Added a settings page for telemetry configuration in the application.
- Enhanced error handling and logging for telemetry events across various services.
- Removed outdated ADR on telemetry usage from the repository.

* Remove telemetry-related components and references from the application

- Deleted the TelemetryRouter and its associated service, removing the telemetry status query.
- Updated the settings sidebar to eliminate the Telemetry option.
- Removed the TelemetrySettingsPage and its related components, including the TelemetryStatus display.
- Cleaned up unused imports and references to telemetry throughout the codebase.

* Enhance telemetry functionality and improve budget management

- Added an 'exhausted' state to the focus management to track when the research budget is depleted.
- Updated the spend function to prevent multiple budget exhaustion events from being recorded.
- Refactored the rollup service to handle telemetry rollup claims and restore counters more effectively.
- Improved error handling in telemetry events to ensure proper reporting and recovery from failures.
- Enhanced documentation to clarify the behavior of telemetry when disabled and the implications for data integrity.

* Add telemetry support and enhance landing page analytics

- Introduced `@crm/telemetry` package to manage telemetry configurations and constants.
- Integrated `posthog-js` for analytics on the landing page, ensuring it only runs on allowed domains.
- Updated the `LandingAnalytics` component to initialize analytics tracking based on hostname.
- Enhanced the `audit` hook to exclude specific event types from archiving.
- Improved agent session handling by implementing offline thread management.
- Added utility functions for analytics host validation and created tests for the new functionality.
- Updated documentation to reflect changes in telemetry usage and landing page analytics.

* Update agent panel to use SETTLED_TTL_MS for archive stale time and enhance documentation

- Changed the `staleTime` for the archive query in the agent panel from `Infinity` to `SETTLED_TTL_MS` to ensure proper session management.
- Updated documentation to clarify the behavior of the archive in relation to session state and stale time handling.

* Enhance landing page analytics with CTA event tracking

- Introduced `captureLanding` function to track user interactions with the setup prompt and GitHub star buttons.
- Updated `SetupPromptButton` and `GitHubStarButton` components to accept a `location` prop for distinguishing between 'hero' and 'closing' CTAs.
- Modified `LandingAnalytics` to include new event types for clipboard actions and button clicks.
- Enhanced documentation to reflect the new telemetry events and their usage.

* Update README with new images and remove outdated ones

- Replaced outdated images with new visuals for the landing page, showcasing agents and capabilities.
- Removed references to deleted images related to deals, contacts, and companies to streamline documentation.

* Refactor README to improve layout of screenshots

- Converted individual screenshot sections into a table format for better visual organization.
- Updated captions for clarity and conciseness, enhancing the overall presentation of the landing page visuals.

* Update README and images for landing page

- Removed outdated captions from the README for agents and capabilities images to streamline content.
- Updated binary images for agents, capabilities, and hero sections to enhance visual quality on the landing page.

* Update README and replace landing hero image

- Updated the README to reflect the new image caption for the companies list.
- Replaced the outdated landing hero image with a new product shot to enhance visual appeal.
- Removed the old landing hero image from the repository.

* Update landing page images for agents and capabilities

- Replaced existing binary images for agents and capabilities on the landing page to improve visual quality and consistency.
- Ensured that the new images align with the recent updates to the README and overall landing page design.

* Update landing page images for agents and capabilities to enhance visual quality

* Refactor AddButton component in multiple sheets to use ComponentProps for better type safety

- Updated the AddButton function in create-company-sheet, create-contact-sheet, create-deal-sheet, and add-sso-provider-sheet to accept props of type ComponentProps from the Button component.
- This change enhances type safety and allows for more flexible button properties across different sheets.

* Refactor TelemetryService to integrate RollupService for telemetry rollups

- Replaced FunnelService with RollupService in TelemetryService to handle telemetry rollups.
- Implemented a timer to run rollups hourly, enhancing telemetry data collection.
- Updated documentation to reflect changes in telemetry rollup processes and clarify the in-process execution without cron dependencies.

* Report installs without a cron, and stop double counting them

The install count was reading 1 while 20 databases had migrated. Every
"Active installs" tile is built on install_daily, which only ever fired
from POST /internal/telemetry/rollup — a route that refuses to run
without CRON_SECRET. An install that never configures a cron reported
nothing at all, however much it was used.

TelemetryService now rolls up in-process, on boot and hourly. The
existing row lock on install makes all but the first of those a no-op,
and it short-circuits before the aggregation runs, so it is still one
set of grouped queries per install per day. The route stays, still
behind CRON_SECRET, for a platform cron that would rather drive it;
nothing depends on it now.

Two ways the same event could arrive twice, both of which the hourly
timer would have made more frequent:

A rollup wrongly read as failed hands the day back and is re-sent.
posthog-node does not reject on a failed send, so the client inferred
failure from a module-global error counter that any other capture could
move. It now enqueues and awaits flush(), which does throw, and treats
either signal as a failure — erring toward a re-send, which is free,
over consuming a day whose event never left.

A milestone sent before it was recorded, so both the boot sweep and the
rollup sweep could send the same step. One install sent
first_fact_applied six times. The insert is now the claim: of two
sweeps exactly one is told it landed the row, and only that one sends.
A failed send deletes the row so the step is retried.

Both events also carry a deterministic uuid derived from the install
and the day (or the step), so a duplicate that does get out is ingested
once. Installs and active installs were always safe — PostHog's unique
math is per install per day — but the summed agent-usage properties
were not.

* Derive the dedupe id with SHA-256 in a v8 uuid

CodeQL flags a weak algorithm reached by the install identity, and it
is right that the two do not belong in one expression. SHA-1 was there
only because RFC 4122 defines v5 that way; nothing depends on being a
v5, so this is a SHA-256 digest in a v8 uuid, the slot RFC 9562 leaves
for a derivation of one's own.

* CMP-1 chore: enrich agentic experience

* ci: open pull requests, gate titles and promote releases automatically (trycompai#53)

* Lewis/contact and currencies (trycompai#56)

* Implement fields management features (trycompai#55)

* Lewis/dynamic field fix (trycompai#70)

* Refactor query prefetching in Companies, Contacts, and Deals pages to… (trycompai#71)

* chore: release main (trycompai#72)

* feat(api): add microsoft sign-in and outlook mailbox sync (trycompai#73)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore: release release

* ci: run release-please on main and document merge order (trycompai#76)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: release main (trycompai#78)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(db): CMP-1 persist durable custom agents (trycompai#67)

* feat(agent): CMP-1 add sandboxed builder and runner runtimes (trycompai#60)

* refactor(app): CMP-59 harden CRM UI foundations (trycompai#61)

* feat(app): CMP-46 add the private agent builder workspace (trycompai#62)

* feat(app): CMP-12 review agent drafts before deployment (trycompai#63)

* fix(app): CMP-47 consolidate agent builder presentation (trycompai#64)

* feat(app): CMP-47 add inline composer context

* fix(app): move chat beneath overview in icon rail (trycompai#83)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* fix(ci): tag releases automatically and keep previews off the production schema (trycompai#82)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.4.0 (trycompai#86)

* feat(agent): bound agent builder retries and improve chat scrolling (trycompai#89)

* fix(app): render agent transcript chronologically with anchored tool results (trycompai#92)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix(agent): declare granted write actions in draft access summary (trycompai#93)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* chore(main): release 1.5.0 (trycompai#91)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(api): warn when the deployed schema does not match schema.prisma (trycompai#88)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* CMP-62 chore: add gh-stack skill (trycompai#96)

* chore(main): release 1.5.1 (trycompai#97)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): make the release guard reject only genuinely untagged pull requests (trycompai#105)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* fix(ci): stop the auto-titler downgrading a release

The titler regenerated the title on every push once it had written one, so a
pull request's subject was whatever its *last* commits looked like. trycompai#105 carried
the whole website-tracking feature and was retitled `fix(ci)` by its final push,
squashed onto main under that subject, and released as a patch whose notes
mention none of it.

A generated title is now left alone unless it stops being a conventional commit
or stops covering the branch, and no title — generated or typed — may release
less than the commits behind it: `floor_of` takes the strongest bump on the
branch and `generate` raises its proposal to meet it. A branch holding a `feat`
cannot ship as a `fix`, and one holding a breaking change cannot ship without
the `!`. Over-releasing is the safe direction; losing a feature out of the
changelog is not.

* feat(tracking): add website tracking with form capture and attribution

A first-party script on the marketing site, a collector in the API, and one
rule: a form submission becomes a contact. Page views, click labels and
first/last-touch attribution hang off that, with a 90-day retention sweep, an
hourly contact cap and a per-minute event budget.

The work landed in 815a832. The auto-titler had retitled its pull request
`fix(ci)` on the last push, so it squashed onto main under that subject and
released as a patch whose notes describe only the guard fix. This commit carries
no code — it exists so the changelog and the version say what actually shipped.
See docs/tracking.md.

* chore(main): release 1.6.0 (trycompai#106)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): make a release one pull request instead of two

Shipping was a release pull request into `main` and a promotion pull request
into `release`, open at the same time, with a warning on the promotion telling
you to merge the other one first. Merge them the wrong way round and you shipped
untagged code and left the version behind for the next promotion. Nobody should
have to hold that rule in their head to deploy.

The tag and the code have to travel together, so the release workflow now does
it in one step: when release-please cuts the tag it merges that exact commit
into `release` through the merges API. One pull request, no order to remember,
and the tag is by construction an ancestor of what shipped. `promote.yml` is
gone.

A conflict is the one case a human still has to see, and it can only mean
somebody committed to `release` directly, so it fails the run and says so rather
than quietly leaving production behind.

Non-releasable commits now wait for the next release rather than riding a
promotion, which is the trade: `release` moves when a tag is cut and at no other
time.

* fix(ci): fall back to the pushed commit when release-please reports no sha

* chore(main): release 1.6.1 (trycompai#108)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(db): add peek script for inspecting database contents (trycompai#110)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.7.0 (trycompai#111)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): apply sourced facts to empty fields automatically (trycompai#112)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.0 (trycompai#113)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(ci): ship releases by opening a pull request into release (trycompai#114)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.1 (trycompai#115)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(agent): fill blank fields on the dispatch tick instead of sign-in (trycompai#117)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.8.2 (trycompai#118)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): stop suggesting a URL that already matches the field (trycompai#120)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.9.0 (trycompai#121)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(tracking): support installing the tracking tag via Google Tag Manager (trycompai#124)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.10.0 (trycompai#126)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(app): copy the tracking snippet for the selected install method (trycompai#128)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.11.0 (trycompai#129)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat: edit a deployed agent, and show what Slack actually granted (CMP-77) (trycompai#109)

* chore(main): release 1.12.0 (trycompai#132)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(app): search company dropdowns instead of scrolling them (trycompai#125)

* fix(app): show select field values in record tables (trycompai#133)

* fix(agent): let the assistant chat read the deal list it is told to use (CMP-77) (trycompai#139)

* chore(main): release 1.13.0 (trycompai#136)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* chore: add anti-slop lint rules, dead-code checks and stricter Biome constraints (CMP-80) (trycompai#145)

* refactor: clear anti-slop type assertions and conditional object spreads (CMP-81) (trycompai#146)

* docs: propose an i18n layer (trycompai#143)

* refactor: parse every remaining I/O boundary into a domain type (CMP-82) (trycompai#151)

* fix: unblock the test suite and actually install the git hooks (CMP-83) (trycompai#152)

* ci: run anti-slop lint in CI and pre-push (CMP-84) (trycompai#153)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* feat: enrichment queue widget (CMP-92) (trycompai#159)

* feat(agent): read people from Context.dev instead of RapidAPI (CMP-86) (trycompai#158)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* feat: page the enrichment queue (CMP-92) (trycompai#160)

Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>

* chore(main): release 1.14.0 (trycompai#147)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* feat(agent): scope field backfill tasks to records missing values (trycompai#163)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.0 (trycompai#164)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(api): serve openapi.json and bundle swagger deps in function build (trycompai#166)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.1 (trycompai#167)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Lewis/openapi json (trycompai#169)

* docs(api): explain runtime openapi document and vendoring rules (trycompai#170)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* chore(main): release 1.15.2 (trycompai#171)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* fix(app): prevent url param collision between fields sheet and table filter (trycompai#175)

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>

* fix: stop a finished enrichment reading as failed (trycompai#173)

* chore(main): release 1.15.3 (trycompai#176)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* ci: add Vercel deploy workflow

Deploys to Vercel on push to main/release branches.
Also supports manual trigger via workflow_dispatch with
environment selection (preview/production).

Uses secrets: VERCEL_API_KEY, VERCEL_ORG_ID, VERCEL_PROJECT_ID

---------

Co-authored-by: Lewis Carhart <lewis@trycomp.ai>
Co-authored-by: grim <75869731+ripgrim@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
Co-authored-by: Guzman Pintos <37162183+GuzmanPintos@users.noreply.github.com>
Co-authored-by: twinprime19 <38123958+twinprime19@users.noreply.github.com>
Co-authored-by: Kiro Agent <244629292+kiro-agent@users.noreply.github.com>
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