Conversation
There was a problem hiding this comment.
Sorry @Connorbelez, your pull request is larger than the review limit of 150000 diff characters
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 22 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
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.
There was a problem hiding this comment.
Pull request overview
Adds an ENG-261 “EAV-CRM Integration Sandbox” under /demo/crm to exercise CRM control-plane + data-plane flows end-to-end (custom objects, system adapters, links) with lightweight instrumentation and demo seed/reset helpers. This PR also extends the Convex CRM backend to better support native/system-object parity in view queries and record detail lookups.
Changes:
- Introduces new
/demo/crmroute group (layout + 3 tabs) and a set of reusable demo-local CRM components (object creator, record composer, table surface, detail surface, link explorer, metrics panel). - Adds Convex demo helpers (
demo/crmSandbox) for seeding/resetting demo CRM data. - Extends CRM backend with
recordQueries.getRecordReferenceand enablesviewQueries.queryViewRecordsto load system/native records via the system adapter.
Reviewed changes
Copilot reviewed 43 out of 45 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/routeTree.gen.ts | Updates generated route tree to include /demo/crm routes. |
| src/routes/demo/crm/route.tsx | Adds CRM sandbox layout, navigation, and metrics wrapper. |
| src/routes/demo/crm/index.tsx | Implements “Custom Objects” tab (seed/reset, object inventory, record composer, preview, detail). |
| src/routes/demo/crm/system.tsx | Implements “System Adapters” tab using shared table/detail surfaces. |
| src/routes/demo/crm/links.tsx | Implements “Link Explorer” tab for link types, linking records, and linked-record inspection. |
| src/components/demo/crm/MetricsProvider.tsx | Adds shared metrics context for demo surfaces. |
| src/components/demo/crm/ValidationMetrics.tsx | Adds sticky metrics panel UI (reads, render time, record-shape match). |
| src/components/demo/crm/RecordTableSurface.tsx | Adds shared “UnifiedRecord” table preview surface + metrics hooks. |
| src/components/demo/crm/RecordDetailCard.tsx | Adds shared record-detail card using getRecordReference. |
| src/components/demo/crm/ObjectCreator.tsx | Adds object + field creation UI using a demo action wrapper. |
| src/components/demo/crm/ObjectInventoryCard.tsx | Adds object inventory selection card UI. |
| src/components/demo/crm/FieldDefEditor.tsx | Adds field-definition builder UI. |
| src/components/demo/crm/SelectOptionsEditor.tsx | Adds select/multi-select option editor UI. |
| src/components/demo/crm/FieldInput.tsx | Adds field-type → input control mapping for record composer. |
| src/components/demo/crm/DynamicRecordForm.tsx | Adds record composer form that submits to crm.records.createRecord. |
| src/components/demo/crm/functionRefs.ts | Adds typed function references for demo endpoints + link APIs. |
| src/components/demo/crm/utils.ts | Adds demo utilities: error extraction, slugify, formatting, EAV read estimation, shape checking. |
| src/components/demo/crm/formatters.ts | Adds formatting helpers for metrics display. |
| src/components/demo/crm/schema.ts | Adds demo-side schema/type helpers for field drafting. |
| src/components/demo/crm/types.ts | Adds demo shared types (record kind, metrics state, seed summary). |
| convex/demo/crmSandbox.ts | Adds seed/reset + sandbox object creation demo endpoints. |
| convex/crm/viewQueries.ts | Adds native/system-object record loading path for view queries. |
| convex/crm/systemAdapters/queryAdapter.ts | Refactors native record assembly; adds getNativeRecordById. |
| convex/crm/recordQueries.ts | Adds getRecordReference query supporting both EAV and native records + link loading. |
| convex/crm/tests/helpers.test.ts | Introduces shared CRM test harness + seed helpers. |
| convex/crm/tests/walkthrough.test.ts | Updates CRM tests to use new helpers module name. |
| convex/crm/tests/viewEngine.test.ts | Updates CRM tests to use new helpers module name. |
| convex/crm/tests/systemAdapters.test.ts | Updates CRM tests to use new helpers module name. |
| convex/crm/tests/records.test.ts | Updates CRM tests to use new helpers module name. |
| convex/crm/tests/metadataCompiler.test.ts | Updates CRM tests to use new helpers module name. |
| convex/_generated/api.d.ts | Updates generated API typings for new/added modules. |
| specs/ENG-261/tasks.md | Adds overall ENG-261 task plan + repo reality notes. |
| specs/ENG-261/chunks/manifest.md | Adds chunk breakdown/manifest for ENG-261 work. |
| specs/ENG-261/chunks/chunk-01-foundation/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-01-foundation/context.md | Adds chunk context/constraints. |
| specs/ENG-261/chunks/chunk-02-custom-object-playground/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-02-custom-object-playground/context.md | Adds chunk context/constraints. |
| specs/ENG-261/chunks/chunk-03-record-views/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-03-record-views/context.md | Adds chunk context/constraints. |
| specs/ENG-261/chunks/chunk-04-record-surfaces/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-04-record-surfaces/context.md | Adds chunk context/constraints. |
| specs/ENG-261/chunks/chunk-05-system-detail/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-05-system-detail/context.md | Adds chunk context/constraints. |
| specs/ENG-261/chunks/chunk-06-links-polish/tasks.md | Adds chunk task list. |
| specs/ENG-261/chunks/chunk-06-links-polish/context.md | Adds chunk context/constraints. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (7)
src/components/demo/crm/utils.ts (1)
130-139: Timezone handling inconsistency: local time vs UTC.The
toDateInputValuefunction uses local time methods (getFullYear(),getMonth(),getHours(), etc.), but the relevant code snippet from the same file shows a version using UTC methods (getUTCFullYear(),getUTCMonth(), etc.). This inconsistency could cause timezone-related bugs when editing date/datetime values.For
datetime-localinputs, using local time is actually correct since the input type expects local time. However, fordateinputs (line 139),toISOString().slice(0, 10)returns UTC date which may differ from local date near midnight.♻️ Consistent date formatting
- return date.toISOString().slice(0, 10); + const year = date.getFullYear(); + const month = `${date.getMonth() + 1}`.padStart(2, "0"); + const day = `${date.getDate()}`.padStart(2, "0"); + return `${year}-${month}-${day}`;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/demo/crm/utils.ts` around lines 130 - 139, The toDateInputValue function mixes local-time getters for the "datetime" branch with a UTC-based return (date.toISOString().slice(0, 10)) for the date branch, causing timezone drift; update the date branch to format year/month/day using local getters (getFullYear(), getMonth(), getDate()) and pad month/day like the "datetime" branch so both branches consistently use local time for date inputs and datetime-local formatting.src/components/demo/crm/FieldInput.tsx (1)
110-154: Consider emittingundefinedwhen multi_select becomes empty.When unchecking the last option, the component emits an empty array
[](line 133-135). However,sanitizeRecordValuesinDynamicRecordForm.tsx(line 42) filters out empty arrays. This works correctly end-to-end, but for consistency with other field types that emitundefinedfor empty values, consider:onChange( - currentValues.filter((item) => item !== option.value) + currentValues.length === 1 && currentValues[0] === option.value + ? undefined + : currentValues.filter((item) => item !== option.value) );This is optional since the current behavior is handled downstream.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/demo/crm/FieldInput.tsx` around lines 110 - 154, The multi_select branch in FieldInput.tsx currently calls onChange([]) when the last checkbox is unchecked; change the onCheckedChange logic inside the Checkbox in the "multi_select" case so that if removing the option results in an empty currentValues array it calls onChange(undefined) instead of onChange([]), otherwise call onChange([...]) or onChange(filteredArray) as appropriate; update the handler that currently filters currentValues (the anonymous function passed to onCheckedChange) to compute the newValues and emit undefined when newValues.length === 0 to match other field types and sanitizeRecordValues behavior.src/components/demo/crm/FieldDefEditor.tsx (1)
256-265: Consider improving the rich text preview placeholder.The disabled textarea with
value=""shows only a placeholder. This is fine for a demo, but if intended as a preview, consider showing sample formatted content or removing it if it adds no value to the field builder UX.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/demo/crm/FieldDefEditor.tsx` around lines 256 - 265, The rich text preview in FieldDefEditor.tsx (the branch checking field.fieldType === "rich_text") currently renders a disabled Textarea with value="" which only shows the placeholder; update this to either (a) render meaningful sample formatted content in the Textarea (e.g., sample paragraphs, lists, and inline formatting) so the Preview Label shows what rich text will look like, or (b) remove the Preview block entirely if no preview value is desired; adjust the JSX around the Label and Textarea accordingly and ensure the Textarea value is populated from a sample string or the preview block is removed to improve UX.src/components/demo/crm/DynamicRecordForm.tsx (1)
70-92: Consider adding required field validation before submission.The form allows submission without validating that required fields have values. While the Convex mutation will reject invalid records, providing client-side feedback would improve UX:
♻️ Add client-side required field validation
async function handleSubmit() { if (!objectDefId) { return; } + const missingRequired = activeFields + .filter((f) => f.isRequired && values[f.name] === undefined) + .map((f) => f.label); + if (missingRequired.length > 0) { + toast.error(`Required fields missing: ${missingRequired.join(", ")}`); + return; + } setIsSubmitting(true);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/demo/crm/DynamicRecordForm.tsx` around lines 70 - 92, The handleSubmit function currently allows submitting without client-side required-field checks; before calling setIsSubmitting or sanitizeRecordValues, validate activeFields for any field marked required (e.g., inspect activeFields entries for a required flag or isRequired) and ensure corresponding keys in values are present and non-empty (trim strings, check arrays/objects appropriately); if any required fields are missing, call toast.error with a concise message listing the missing field labels and return early without calling createRecord or changing state; keep the rest of the flow (setIsSubmitting, sanitizeRecordValues, createRecord, setValues, onRecordCreated, setIsSubmitting(false)) unchanged when validation passes.src/routes/demo/crm/route.tsx (1)
12-15: Consider lazy-loading the/demo/crmroute component for improved code-splitting.This route currently uses an inline
componentproperty. Per guidelines, file-based routes should use lazy-loaded components vialazyRouteComponentor a.lazy.tsxcompanion file for code-splitting. MovingCrmSandboxLayoutto a lazy variant would keep this non-critical route out of the initial bundle.Example pattern: Create
src/routes/demo/crm/route.lazy.tsxwith the component, then reference it vialazyRouteComponentin the main route file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/routes/demo/crm/route.tsx` around lines 12 - 15, The Route currently sets component: CrmSandboxLayout inline; convert this to a lazy-loaded component by moving the CrmSandboxLayout export into a companion file (e.g., create src/routes/demo/crm/route.lazy.tsx that exports the component) and update the Route created by createFileRoute to use lazyRouteComponent pointing at that lazy file; specifically, replace the component property with lazyRouteComponent(...) referencing the new route.lazy.tsx export so the /demo/crm route is code-split and not included in the initial bundle.convex/demo/crmSandbox.ts (2)
215-236: Function name doesn't reflect its behavior.
deleteFieldGraphdeletes field capabilities but returns thefieldDefsfor the caller to delete separately (see Line 510-512). Consider renaming todeleteFieldCapabilitiesAndReturnFieldsor having the function delete the fieldDefs itself for consistency with otherdelete*Graphfunctions.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@convex/demo/crmSandbox.ts` around lines 215 - 236, The function deleteFieldGraph currently only removes related fieldCapabilities but returns the fieldDefs for the caller to remove, so rename or change behavior for consistency: either rename deleteFieldGraph to deleteFieldCapabilitiesAndReturnFields (update all call sites, e.g., where deleteFieldGraph is invoked around lines that later delete fieldDefs) to make its contract explicit, or modify deleteFieldGraph to also delete the returned fieldDefs (delete each fieldDef._id after deleting its capabilities) so it behaves like other delete*Graph helpers; pick one approach and update the function name/usages and tests accordingly (refer to function deleteFieldGraph, returned variable fieldDefs, and related table fieldCapabilities).
135-163: Consider parallelizing value table deletions.Each value table is queried and deleted sequentially. For records with many values, this could be slow. Since these deletions are independent per table, they could run in parallel.
♻️ Suggested parallel deletion
async function deleteValueRowsForRecord( ctx: MutationCtx, recordId: Id<"records"> ) { - for (const table of VALUE_TABLES) { - switch (table) { - case "recordValuesText": - case "recordValuesNumber": - case "recordValuesBoolean": - case "recordValuesDate": - case "recordValuesSelect": - case "recordValuesMultiSelect": - case "recordValuesRichText": - case "recordValuesUserRef": { - const rows = await ctx.db - .query(table) - .withIndex("by_record", (q) => q.eq("recordId", recordId)) - .collect(); - for (const row of rows) { - await ctx.db.delete(row._id); - } - break; - } - default: { - throw new ConvexError(`Unsupported value table: ${String(table)}`); - } - } - } + await Promise.all( + VALUE_TABLES.map(async (table) => { + const rows = await ctx.db + .query(table) + .withIndex("by_record", (q) => q.eq("recordId", recordId)) + .collect(); + await Promise.all(rows.map((row) => ctx.db.delete(row._id))); + }) + ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@convex/demo/crmSandbox.ts` around lines 135 - 163, The current deleteValueRowsForRecord function iterates VALUE_TABLES sequentially which is slow; change it to run per-table deletions in parallel by mapping VALUE_TABLES to an array of async tasks and awaiting Promise.all. For each table (inside deleteValueRowsForRecord) keep the same withIndex query (ctx.db.query(table).withIndex("by_record", q => q.eq("recordId", recordId)).collect()), then delete the returned rows concurrently (e.g., map row => ctx.db.delete(row._id) and await Promise.all for that table), and throw the ConvexError for unknown tables as before; ensure the function still returns after all table-level deletions complete.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@convex/crm/__tests__/metadataCompiler.test.ts`:
- Line 20: The test imports helpers from "./helpers.test", which violates the
Biome noExportsInTest rule; change the import back to "./helpers" and ensure the
shared helper file is named helpers.ts (not helpers.test.ts) so exported
utilities are in a non-.test file; update the import in
convex/crm/__tests__/metadataCompiler.test.ts to reference "./helpers" and, if
needed, rename the helper module file and update any other tests importing it
(look for the helpers module symbol used by the tests to verify all references
are updated).
In `@convex/crm/__tests__/systemAdapters.test.ts`:
- Line 21: The test import was changed to "./helpers.test" which violates
Biome's noExportsInTest rule; revert the import in systemAdapters.test.ts back
to import from "./helpers" (replace "./helpers.test" with "./helpers") and
ensure the shared helper module is named/exported from helpers.ts (not
helpers.test.ts) so exported utilities used by tests come from a non-.test.ts
file (check exported symbols in that helpers module match what's imported here).
In `@convex/crm/__tests__/viewEngine.test.ts`:
- Line 11: The import in viewEngine.test.ts currently points to "./helpers.test"
which violates Biome's noExportsInTest rule; update the import to use
"./helpers" and ensure the referenced helper module file is renamed from
helpers.test.ts to helpers.ts (and retains its exported helpers), then update
any other tests importing "./helpers.test" to the new "./helpers" path so shared
test utilities are not exported from a .test.ts file.
In `@convex/crm/__tests__/walkthrough.test.ts`:
- Around line 9-13: Rename the shared test helper file from helpers.test.ts to
helpers.ts and update all imports that reference it (e.g., imports of asAdmin,
CrmTestHarness, createCrmTestHarness) to point to "./helpers" instead of
"./helpers.test"; ensure any other test files referencing helpers.test.ts are
updated to import from the new helpers module so the file uses a .ts extension
and satisfies the noExportsInTest rule.
In `@src/components/demo/crm/ObjectCreator.tsx`:
- Around line 60-67: The current submit validation in ObjectCreator.tsx only
checks options.length for select/multi_select fields so a blank option can slip
through; update the validation for fields (the fields array check using
field.fieldType and field.options) to ensure every option has non-empty label
and value (e.g., no option where !option.label.trim() or !option.value.trim()),
and treat any such option as invalid to block submit; also ensure this aligns
with the payload builder logic that reads options (the code around the payload
builder on Lines ~87-91) so it will no longer send empty label/value strings.
In `@src/components/demo/crm/ObjectInventoryCard.tsx`:
- Around line 45-55: The interactive row in ObjectInventoryCard is visually
marked via isActive but lacks accessibility state; update the button element
(the one using objectDef._id, onSelect, isActive) to expose selection to AT by
adding an aria-pressed attribute set to the boolean isActive (or use appropriate
listbox semantics/role and aria-selected if you prefer listbox patterns),
ensuring the attribute toggles when onClick triggers onSelect so screen readers
announce the selected state.
In `@src/components/demo/crm/RecordTableSurface.tsx`:
- Around line 84-103: The timer reset currently happens whenever preview ===
undefined (startedAtRef.current = performance.now()), causing renderTimeMs to
measure only from the last loading render; instead, initialize/reset
startedAtRef.current when the underlying view/data actually changes (e.g., when
tablePreview becomes available or when tablePreview/fields identity changes).
Remove the preview-undefined assignment and set startedAtRef.current inside the
useEffect (or a separate effect) that depends on tablePreview and fields (or the
specific view/object identifier), so that
setRenderTime(Math.round(performance.now() - startedAtRef.current)) in the
existing useEffect reflects time since the real view/data change; reference
startedAtRef, tablePreview, fields, and setRenderTime in your change.
- Around line 181-193: The TableRow elements rendered with TableRow, cn,
onSelectRecord, selectedRecordId, row._id and row._kind are clickable but not
keyboard-accessible; make them focusable by adding tabIndex={0}, give an
appropriate role (e.g., role="button" or role="row" with aria-selected when
selected), and implement an onKeyDown handler on the same element that calls
onSelectRecord with { recordId: row._id, recordKind: row._kind } when Enter or
Space is pressed (mirror the existing onClick behavior). Ensure you only add
these props when onSelectRecord is provided and keep the visual focus style
consistent with the current selectedRecordId handling.
In `@src/routes/demo/crm/index.tsx`:
- Around line 231-246: Both buttons currently only disable themselves (using
isSeeding and isResetting), allowing concurrent mutating actions; change them so
both use a shared busy flag (e.g., compute const isBusy = isSeeding ||
isResetting or directly use isSeeding || isResetting) and pass that to each
Button's disabled prop so both "Seed lead pipeline" and "Reset sandbox objects"
are disabled while either onSeed or onReset is running.
- Around line 141-147: When the user selects a different object in
ObjectInventoryCard the previous record selection must be cleared so
RecordDetailCard doesn't query with a stale selectedRecord; update the onSelect
handler (currently passing setSelectedObjectId) to call both
setSelectedObjectId(newId) and clear the record selection (e.g.,
setSelectedRecordId(null) and/or setSelectedRecordKind(null) or
setSelectedRecord(null)) so selectedRecord state is reset whenever the inventory
selection changes.
- Around line 53-63: The effect that resets selectedObjectId is deriving a
fallback from possibly stale customObjects, causing selection bounce after
handleSeed() or onCreated(); change the logic in the useEffect so it never picks
customObjects[0]?._id from the current (stale) query snapshot as a fallback:
only set selectedObjectId when there is no selection and use
seedState?.demoObjectId if present, or prefer an explicit pendingSelection id
set by handleSeed()/onCreated (e.g. store the new id in a ref or state like
pendingSelectionRef and read that here), and otherwise do nothing until the live
customObjects list updates — keep references to selectedObjectId, customObjects,
seedState?.demoObjectId, setSelectedObjectId, handleSeed(), and onCreated() when
implementing this change.
---
Nitpick comments:
In `@convex/demo/crmSandbox.ts`:
- Around line 215-236: The function deleteFieldGraph currently only removes
related fieldCapabilities but returns the fieldDefs for the caller to remove, so
rename or change behavior for consistency: either rename deleteFieldGraph to
deleteFieldCapabilitiesAndReturnFields (update all call sites, e.g., where
deleteFieldGraph is invoked around lines that later delete fieldDefs) to make
its contract explicit, or modify deleteFieldGraph to also delete the returned
fieldDefs (delete each fieldDef._id after deleting its capabilities) so it
behaves like other delete*Graph helpers; pick one approach and update the
function name/usages and tests accordingly (refer to function deleteFieldGraph,
returned variable fieldDefs, and related table fieldCapabilities).
- Around line 135-163: The current deleteValueRowsForRecord function iterates
VALUE_TABLES sequentially which is slow; change it to run per-table deletions in
parallel by mapping VALUE_TABLES to an array of async tasks and awaiting
Promise.all. For each table (inside deleteValueRowsForRecord) keep the same
withIndex query (ctx.db.query(table).withIndex("by_record", q =>
q.eq("recordId", recordId)).collect()), then delete the returned rows
concurrently (e.g., map row => ctx.db.delete(row._id) and await Promise.all for
that table), and throw the ConvexError for unknown tables as before; ensure the
function still returns after all table-level deletions complete.
In `@src/components/demo/crm/DynamicRecordForm.tsx`:
- Around line 70-92: The handleSubmit function currently allows submitting
without client-side required-field checks; before calling setIsSubmitting or
sanitizeRecordValues, validate activeFields for any field marked required (e.g.,
inspect activeFields entries for a required flag or isRequired) and ensure
corresponding keys in values are present and non-empty (trim strings, check
arrays/objects appropriately); if any required fields are missing, call
toast.error with a concise message listing the missing field labels and return
early without calling createRecord or changing state; keep the rest of the flow
(setIsSubmitting, sanitizeRecordValues, createRecord, setValues,
onRecordCreated, setIsSubmitting(false)) unchanged when validation passes.
In `@src/components/demo/crm/FieldDefEditor.tsx`:
- Around line 256-265: The rich text preview in FieldDefEditor.tsx (the branch
checking field.fieldType === "rich_text") currently renders a disabled Textarea
with value="" which only shows the placeholder; update this to either (a) render
meaningful sample formatted content in the Textarea (e.g., sample paragraphs,
lists, and inline formatting) so the Preview Label shows what rich text will
look like, or (b) remove the Preview block entirely if no preview value is
desired; adjust the JSX around the Label and Textarea accordingly and ensure the
Textarea value is populated from a sample string or the preview block is removed
to improve UX.
In `@src/components/demo/crm/FieldInput.tsx`:
- Around line 110-154: The multi_select branch in FieldInput.tsx currently calls
onChange([]) when the last checkbox is unchecked; change the onCheckedChange
logic inside the Checkbox in the "multi_select" case so that if removing the
option results in an empty currentValues array it calls onChange(undefined)
instead of onChange([]), otherwise call onChange([...]) or
onChange(filteredArray) as appropriate; update the handler that currently
filters currentValues (the anonymous function passed to onCheckedChange) to
compute the newValues and emit undefined when newValues.length === 0 to match
other field types and sanitizeRecordValues behavior.
In `@src/components/demo/crm/utils.ts`:
- Around line 130-139: The toDateInputValue function mixes local-time getters
for the "datetime" branch with a UTC-based return (date.toISOString().slice(0,
10)) for the date branch, causing timezone drift; update the date branch to
format year/month/day using local getters (getFullYear(), getMonth(), getDate())
and pad month/day like the "datetime" branch so both branches consistently use
local time for date inputs and datetime-local formatting.
In `@src/routes/demo/crm/route.tsx`:
- Around line 12-15: The Route currently sets component: CrmSandboxLayout
inline; convert this to a lazy-loaded component by moving the CrmSandboxLayout
export into a companion file (e.g., create src/routes/demo/crm/route.lazy.tsx
that exports the component) and update the Route created by createFileRoute to
use lazyRouteComponent pointing at that lazy file; specifically, replace the
component property with lazyRouteComponent(...) referencing the new
route.lazy.tsx export so the /demo/crm route is code-split and not included in
the initial bundle.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: c09d12a9-f7ae-4d91-9f99-585aa3e815a3
⛔ Files ignored due to path filters (1)
convex/_generated/api.d.tsis excluded by!**/_generated/**
📒 Files selected for processing (44)
convex/crm/__tests__/helpers.test.tsconvex/crm/__tests__/metadataCompiler.test.tsconvex/crm/__tests__/records.test.tsconvex/crm/__tests__/systemAdapters.test.tsconvex/crm/__tests__/viewEngine.test.tsconvex/crm/__tests__/walkthrough.test.tsconvex/crm/recordQueries.tsconvex/crm/systemAdapters/queryAdapter.tsconvex/crm/viewQueries.tsconvex/demo/crmSandbox.tsspecs/ENG-261/chunks/chunk-01-foundation/context.mdspecs/ENG-261/chunks/chunk-01-foundation/tasks.mdspecs/ENG-261/chunks/chunk-02-custom-object-playground/context.mdspecs/ENG-261/chunks/chunk-02-custom-object-playground/tasks.mdspecs/ENG-261/chunks/chunk-03-record-views/context.mdspecs/ENG-261/chunks/chunk-03-record-views/tasks.mdspecs/ENG-261/chunks/chunk-04-record-surfaces/context.mdspecs/ENG-261/chunks/chunk-04-record-surfaces/tasks.mdspecs/ENG-261/chunks/chunk-05-system-detail/context.mdspecs/ENG-261/chunks/chunk-05-system-detail/tasks.mdspecs/ENG-261/chunks/chunk-06-links-polish/context.mdspecs/ENG-261/chunks/chunk-06-links-polish/tasks.mdspecs/ENG-261/chunks/manifest.mdspecs/ENG-261/tasks.mdsrc/components/demo/crm/DynamicRecordForm.tsxsrc/components/demo/crm/FieldDefEditor.tsxsrc/components/demo/crm/FieldInput.tsxsrc/components/demo/crm/MetricsProvider.tsxsrc/components/demo/crm/ObjectCreator.tsxsrc/components/demo/crm/ObjectInventoryCard.tsxsrc/components/demo/crm/RecordDetailCard.tsxsrc/components/demo/crm/RecordTableSurface.tsxsrc/components/demo/crm/SelectOptionsEditor.tsxsrc/components/demo/crm/ValidationMetrics.tsxsrc/components/demo/crm/formatters.tssrc/components/demo/crm/functionRefs.tssrc/components/demo/crm/schema.tssrc/components/demo/crm/types.tssrc/components/demo/crm/utils.tssrc/routeTree.gen.tssrc/routes/demo/crm/index.tsxsrc/routes/demo/crm/links.tsxsrc/routes/demo/crm/route.tsxsrc/routes/demo/crm/system.tsx
…d keyboard a11y in RecordTableSurface; option validation in ObjectCreator; aria-pressed in ObjectInventoryCard
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added CRM demo playground with custom object creation and management * Enabled record creation, viewing, and linking between records * Introduced table and kanban views for visualizing records * Added system adapter support for viewing native system records alongside custom data * Included demo data seeding and reset capabilities for testing <!-- end of auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit
Release Notes