@agent-native/core 0.123.0 → 0.123.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +16 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/templates-meta.ts +12 -0
- package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
- package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
- package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
- package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
- package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
- package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
- package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
- package/corpus/templates/crm/.env.example +5 -0
- package/corpus/templates/crm/AGENTS.md +82 -0
- package/corpus/templates/crm/CHANGELOG.md +25 -0
- package/corpus/templates/crm/DEVELOPING.md +66 -0
- package/corpus/templates/crm/README.md +122 -0
- package/corpus/templates/crm/_gitignore +12 -0
- package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
- package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
- package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
- package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
- package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
- package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
- package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
- package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
- package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
- package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
- package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
- package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
- package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
- package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
- package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
- package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
- package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
- package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
- package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
- package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
- package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
- package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
- package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
- package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
- package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
- package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
- package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
- package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
- package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
- package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
- package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
- package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
- package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
- package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
- package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
- package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
- package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
- package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
- package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
- package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
- package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
- package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
- package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
- package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
- package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
- package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
- package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
- package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
- package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
- package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
- package/corpus/templates/crm/actions/navigate.ts +54 -0
- package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
- package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
- package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
- package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
- package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
- package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
- package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
- package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
- package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
- package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
- package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
- package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
- package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
- package/corpus/templates/crm/actions/run.ts +7 -0
- package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
- package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
- package/corpus/templates/crm/actions/sync-crm.ts +146 -0
- package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
- package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
- package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
- package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
- package/corpus/templates/crm/actions/view-screen.ts +324 -0
- package/corpus/templates/crm/agent-native.app-skill.json +69 -0
- package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
- package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
- package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
- package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
- package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
- package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
- package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
- package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
- package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
- package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
- package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
- package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
- package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
- package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
- package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
- package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
- package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
- package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
- package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
- package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
- package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
- package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
- package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
- package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
- package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
- package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
- package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
- package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
- package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
- package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
- package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
- package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
- package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
- package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
- package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
- package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
- package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
- package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
- package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
- package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
- package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
- package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
- package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
- package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
- package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
- package/corpus/templates/crm/app/design-system.ts +3 -0
- package/corpus/templates/crm/app/entry.client.tsx +19 -0
- package/corpus/templates/crm/app/entry.server.tsx +10 -0
- package/corpus/templates/crm/app/global.css +167 -0
- package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
- package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
- package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
- package/corpus/templates/crm/app/i18n/index.ts +34 -0
- package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
- package/corpus/templates/crm/app/lib/navigation.ts +17 -0
- package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
- package/corpus/templates/crm/app/lib/types.ts +172 -0
- package/corpus/templates/crm/app/lib/utils.ts +1 -0
- package/corpus/templates/crm/app/root.tsx +93 -0
- package/corpus/templates/crm/app/routes/_index.tsx +26 -0
- package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
- package/corpus/templates/crm/app/routes/agent.tsx +5 -0
- package/corpus/templates/crm/app/routes/ask.tsx +53 -0
- package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
- package/corpus/templates/crm/app/routes/lists.tsx +228 -0
- package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
- package/corpus/templates/crm/app/routes/people.tsx +41 -0
- package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
- package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
- package/corpus/templates/crm/app/routes/records.tsx +64 -0
- package/corpus/templates/crm/app/routes/settings.tsx +118 -0
- package/corpus/templates/crm/app/routes/setup.tsx +308 -0
- package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
- package/corpus/templates/crm/app/routes/views.tsx +683 -0
- package/corpus/templates/crm/app/routes.ts +19 -0
- package/corpus/templates/crm/app/vite-env.d.ts +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
- package/corpus/templates/crm/components.json +20 -0
- package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
- package/corpus/templates/crm/docs/features/README.md +55 -0
- package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
- package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
- package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
- package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
- package/corpus/templates/crm/learnings.defaults.md +5 -0
- package/corpus/templates/crm/netlify.toml +11 -0
- package/corpus/templates/crm/package.json +61 -0
- package/corpus/templates/crm/public/favicon.svg +1 -0
- package/corpus/templates/crm/public/icon-180.svg +1 -0
- package/corpus/templates/crm/public/manifest.json +11 -0
- package/corpus/templates/crm/react-router.config.ts +7 -0
- package/corpus/templates/crm/server/crm/adapter.ts +32 -0
- package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
- package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
- package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
- package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
- package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
- package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
- package/corpus/templates/crm/server/crm/read-through.ts +207 -0
- package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
- package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
- package/corpus/templates/crm/server/db/index.ts +191 -0
- package/corpus/templates/crm/server/db/schema.ts +647 -0
- package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
- package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
- package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
- package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
- package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
- package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
- package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
- package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
- package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
- package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
- package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
- package/corpus/templates/crm/server/middleware/auth.ts +4 -0
- package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
- package/corpus/templates/crm/server/plugins/auth.ts +14 -0
- package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
- package/corpus/templates/crm/server/plugins/db.ts +738 -0
- package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
- package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
- package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
- package/corpus/templates/crm/shared/crm-contract.ts +417 -0
- package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
- package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
- package/corpus/templates/crm/ssr-entry.ts +11 -0
- package/corpus/templates/crm/tsconfig.json +24 -0
- package/corpus/templates/crm/vite.config.ts +15 -0
- package/corpus/templates/crm/vitest.config.ts +5 -0
- package/corpus/templates/design/app/i18n-data.ts +12 -12
- package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +12 -0
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/src/cli/templates-meta.ts +12 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the grid adds on top of the shared attribute-value registry: a default
|
|
3
|
+
* column width per type, the read-only demotion for a cell the caller cannot
|
|
4
|
+
* write, and cell provenance.
|
|
5
|
+
*
|
|
6
|
+
* The value logic itself — display text, clipboard text, parsing, options,
|
|
7
|
+
* currency, status SLA — lives in `../shared/attribute-value` and is shared
|
|
8
|
+
* with the record page. The grid body still never branches on attribute type:
|
|
9
|
+
* it asks `cellSpecFor(attribute)`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
ATTRIBUTE_TYPE_SPECS,
|
|
14
|
+
CRM_ATTRIBUTE_TYPES,
|
|
15
|
+
type CrmAttributeType,
|
|
16
|
+
} from "../../../../shared/crm-attributes";
|
|
17
|
+
import type {
|
|
18
|
+
CrmActorType,
|
|
19
|
+
CrmAttributeAuthority,
|
|
20
|
+
CrmAttributeOption,
|
|
21
|
+
CrmFieldStoragePolicy,
|
|
22
|
+
} from "../../../../shared/crm-contract";
|
|
23
|
+
import {
|
|
24
|
+
assertValueRegistryComplete,
|
|
25
|
+
ATTRIBUTE_VALUE_SPECS,
|
|
26
|
+
copyAttributeValue,
|
|
27
|
+
formatAttributeValue,
|
|
28
|
+
parseAttributeValue,
|
|
29
|
+
type CrmAttributeValue,
|
|
30
|
+
type CrmValueParse,
|
|
31
|
+
type CrmValueParseFailure,
|
|
32
|
+
type CrmValueSpec,
|
|
33
|
+
} from "../shared/attribute-value";
|
|
34
|
+
|
|
35
|
+
export { statusOverrunDays } from "../shared/attribute-value";
|
|
36
|
+
|
|
37
|
+
export type CrmCellValue = CrmAttributeValue;
|
|
38
|
+
|
|
39
|
+
/** The attribute shape the grid needs; a subset of `CrmAttributeDefinition`. */
|
|
40
|
+
export interface CrmGridAttribute {
|
|
41
|
+
id: string;
|
|
42
|
+
apiSlug: string;
|
|
43
|
+
label: string;
|
|
44
|
+
attributeType: CrmAttributeType;
|
|
45
|
+
multi: boolean;
|
|
46
|
+
authority: CrmAttributeAuthority;
|
|
47
|
+
storagePolicy: CrmFieldStoragePolicy;
|
|
48
|
+
updateable: boolean;
|
|
49
|
+
options?: CrmAttributeOption[];
|
|
50
|
+
config?: Record<string, unknown>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Where one cell's current value came from.
|
|
55
|
+
*
|
|
56
|
+
* `readable: false` means the stored provenance blob could not be parsed — a
|
|
57
|
+
* different state from "no provenance recorded", which is `readable: true` with
|
|
58
|
+
* nothing but an actor. The grid renders them differently on purpose.
|
|
59
|
+
*/
|
|
60
|
+
export interface CrmCellProvenance {
|
|
61
|
+
actorType: CrmActorType;
|
|
62
|
+
actorId?: string | null;
|
|
63
|
+
readable: boolean;
|
|
64
|
+
source?: string;
|
|
65
|
+
sourceUrl?: string;
|
|
66
|
+
confidence?: number;
|
|
67
|
+
reasoning?: string;
|
|
68
|
+
observedAt?: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface CrmGridRow {
|
|
72
|
+
id: string;
|
|
73
|
+
displayName: string;
|
|
74
|
+
/** Required by `update-crm-record` for native and provider writes. */
|
|
75
|
+
remoteRevision?: string;
|
|
76
|
+
values: Record<string, CrmCellValue>;
|
|
77
|
+
/** `activeFrom` of the current value — what a stage SLA is measured from. */
|
|
78
|
+
valuesSince?: Record<string, string>;
|
|
79
|
+
provenance?: Record<string, CrmCellProvenance>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* How a cell is edited: the shared control, plus `readonly` for the types no
|
|
84
|
+
* surface can edit and for any attribute the caller has no write permission on.
|
|
85
|
+
*/
|
|
86
|
+
export type CrmCellEditor =
|
|
87
|
+
| "text"
|
|
88
|
+
| "number"
|
|
89
|
+
| "checkbox"
|
|
90
|
+
| "date"
|
|
91
|
+
| "datetime"
|
|
92
|
+
| "options"
|
|
93
|
+
| "rating"
|
|
94
|
+
| "reference"
|
|
95
|
+
| "readonly";
|
|
96
|
+
|
|
97
|
+
export type CrmCellParseFailure = CrmValueParseFailure;
|
|
98
|
+
export type CrmCellParse = CrmValueParse;
|
|
99
|
+
|
|
100
|
+
export interface CrmCellSpec extends CrmValueSpec {
|
|
101
|
+
editor: CrmCellEditor;
|
|
102
|
+
defaultWidth: number;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// The grid registry — the shared value spec plus grid column presentation
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
const COLUMN_WIDTHS: Record<CrmAttributeType, number> = {
|
|
110
|
+
text: 220,
|
|
111
|
+
number: 130,
|
|
112
|
+
checkbox: 90,
|
|
113
|
+
currency: 140,
|
|
114
|
+
date: 140,
|
|
115
|
+
timestamp: 180,
|
|
116
|
+
rating: 120,
|
|
117
|
+
status: 170,
|
|
118
|
+
select: 170,
|
|
119
|
+
"record-reference": 200,
|
|
120
|
+
"actor-reference": 200,
|
|
121
|
+
location: 200,
|
|
122
|
+
domain: 190,
|
|
123
|
+
"email-address": 230,
|
|
124
|
+
"phone-number": 170,
|
|
125
|
+
interaction: 200,
|
|
126
|
+
"personal-name": 200,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
export const CELL_SPECS: Record<CrmAttributeType, CrmCellSpec> =
|
|
130
|
+
Object.fromEntries(
|
|
131
|
+
CRM_ATTRIBUTE_TYPES.map((type) => {
|
|
132
|
+
const spec = ATTRIBUTE_VALUE_SPECS[type];
|
|
133
|
+
return [
|
|
134
|
+
type,
|
|
135
|
+
{
|
|
136
|
+
...spec,
|
|
137
|
+
editor: spec.control === "none" ? "readonly" : spec.control,
|
|
138
|
+
defaultWidth: COLUMN_WIDTHS[type],
|
|
139
|
+
} satisfies CrmCellSpec,
|
|
140
|
+
];
|
|
141
|
+
}),
|
|
142
|
+
) as Record<CrmAttributeType, CrmCellSpec>;
|
|
143
|
+
|
|
144
|
+
export function cellSpecFor(attribute: CrmGridAttribute): CrmCellSpec {
|
|
145
|
+
const spec = CELL_SPECS[attribute.attributeType];
|
|
146
|
+
// A readable-but-not-updateable attribute is a display cell, not a silently
|
|
147
|
+
// failing editor. A provider-owned but updateable one stays editable: the
|
|
148
|
+
// grid routes that write to `update-crm-record` as a proposal.
|
|
149
|
+
if (!attribute.updateable && spec.editor !== "readonly") {
|
|
150
|
+
return { ...spec, editor: "readonly" };
|
|
151
|
+
}
|
|
152
|
+
return spec;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function isCellEditable(attribute: CrmGridAttribute): boolean {
|
|
156
|
+
return cellSpecFor(attribute).editor !== "readonly";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Every attribute type has a spec and a width — the grid can never fall through. */
|
|
160
|
+
export function assertCellRegistryComplete(): void {
|
|
161
|
+
assertValueRegistryComplete();
|
|
162
|
+
for (const type of CRM_ATTRIBUTE_TYPES) {
|
|
163
|
+
if (typeof COLUMN_WIDTHS[type] !== "number") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`CRM grid has no column width for attribute type "${type}".`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (
|
|
169
|
+
ATTRIBUTE_TYPE_SPECS[type].systemOnly &&
|
|
170
|
+
CELL_SPECS[type].editor !== "readonly"
|
|
171
|
+
) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`CRM grid must render system-only attribute type "${type}" read-only.`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function formatCell(
|
|
180
|
+
attribute: CrmGridAttribute,
|
|
181
|
+
value: CrmCellValue,
|
|
182
|
+
locale?: string,
|
|
183
|
+
): string {
|
|
184
|
+
return formatAttributeValue(attribute, value, locale);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function copyCell(
|
|
188
|
+
attribute: CrmGridAttribute,
|
|
189
|
+
value: CrmCellValue,
|
|
190
|
+
locale?: string,
|
|
191
|
+
): string {
|
|
192
|
+
return copyAttributeValue(attribute, value, locale);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function parseCell(
|
|
196
|
+
attribute: CrmGridAttribute,
|
|
197
|
+
text: string,
|
|
198
|
+
locale?: string,
|
|
199
|
+
): CrmCellParse {
|
|
200
|
+
if (!isCellEditable(attribute)) return { ok: false, reason: "read-only" };
|
|
201
|
+
return parseAttributeValue(attribute, text, locale);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---------------------------------------------------------------------------
|
|
205
|
+
// Duplicate attributes
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
function sameCellValue(a: CrmCellValue, b: CrmCellValue | undefined): boolean {
|
|
209
|
+
if (a === b) return true;
|
|
210
|
+
if (a === null || b === null || b === undefined) return false;
|
|
211
|
+
if (typeof a === "object" || typeof b === "object") {
|
|
212
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* `displayName` duplicates `name` when the native adapter minted both from
|
|
219
|
+
* the same write — see `record-data.ts`'s `isSuppressedDuplicateAttribute`
|
|
220
|
+
* for the record-page equivalent of this same rule. Suppress only when
|
|
221
|
+
* `name` has a real value equal to `displayName`, so a row missing `name`
|
|
222
|
+
* still shows its `displayName` cell.
|
|
223
|
+
*/
|
|
224
|
+
export function isSuppressedDisplayNameCell(
|
|
225
|
+
apiSlug: string,
|
|
226
|
+
rowValues: Record<string, CrmCellValue>,
|
|
227
|
+
): boolean {
|
|
228
|
+
if (apiSlug !== "displayName") return false;
|
|
229
|
+
const name = rowValues.name;
|
|
230
|
+
if (!name) return false;
|
|
231
|
+
return sameCellValue(name, rowValues.displayName);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
// Provenance
|
|
236
|
+
// ---------------------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
const PROVENANCE_ACTORS: readonly CrmActorType[] = [
|
|
239
|
+
"user",
|
|
240
|
+
"agent",
|
|
241
|
+
"automation",
|
|
242
|
+
"provider",
|
|
243
|
+
"system",
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Read one cell's provenance out of a stored `provenance_json` blob.
|
|
248
|
+
*
|
|
249
|
+
* An unreadable blob returns `readable: false` rather than an empty record:
|
|
250
|
+
* "we do not know where this came from" and "nobody recorded a source" are
|
|
251
|
+
* different claims and the grid shows them differently.
|
|
252
|
+
*/
|
|
253
|
+
export function parseCellProvenance(input: {
|
|
254
|
+
actorType: string;
|
|
255
|
+
actorId?: string | null;
|
|
256
|
+
provenanceJson: string | null | undefined;
|
|
257
|
+
fieldName?: string;
|
|
258
|
+
}): CrmCellProvenance {
|
|
259
|
+
const actorType = (PROVENANCE_ACTORS as readonly string[]).includes(
|
|
260
|
+
input.actorType,
|
|
261
|
+
)
|
|
262
|
+
? (input.actorType as CrmActorType)
|
|
263
|
+
: "system";
|
|
264
|
+
const base: CrmCellProvenance = {
|
|
265
|
+
actorType,
|
|
266
|
+
actorId: input.actorId ?? null,
|
|
267
|
+
readable: true,
|
|
268
|
+
};
|
|
269
|
+
if (input.provenanceJson == null || input.provenanceJson === "") return base;
|
|
270
|
+
let parsed: unknown;
|
|
271
|
+
try {
|
|
272
|
+
parsed = JSON.parse(input.provenanceJson);
|
|
273
|
+
} catch {
|
|
274
|
+
return { ...base, readable: false };
|
|
275
|
+
}
|
|
276
|
+
const entries = Array.isArray(parsed) ? parsed : [parsed];
|
|
277
|
+
const match = entries.find(
|
|
278
|
+
(entry): entry is Record<string, unknown> =>
|
|
279
|
+
Boolean(entry) &&
|
|
280
|
+
typeof entry === "object" &&
|
|
281
|
+
(!input.fieldName ||
|
|
282
|
+
(entry as Record<string, unknown>).fieldName === undefined ||
|
|
283
|
+
(entry as Record<string, unknown>).fieldName === input.fieldName),
|
|
284
|
+
);
|
|
285
|
+
if (!match) return base;
|
|
286
|
+
const text = (key: string) =>
|
|
287
|
+
typeof match[key] === "string" && match[key] !== ""
|
|
288
|
+
? (match[key] as string)
|
|
289
|
+
: undefined;
|
|
290
|
+
const confidence =
|
|
291
|
+
typeof match.confidence === "number" && Number.isFinite(match.confidence)
|
|
292
|
+
? match.confidence
|
|
293
|
+
: undefined;
|
|
294
|
+
return {
|
|
295
|
+
...base,
|
|
296
|
+
...(text("provider") ? { source: text("provider") } : {}),
|
|
297
|
+
...((text("sourceUrl") ?? text("evidenceRef"))
|
|
298
|
+
? { sourceUrl: text("sourceUrl") ?? text("evidenceRef") }
|
|
299
|
+
: {}),
|
|
300
|
+
...(confidence === undefined ? {} : { confidence }),
|
|
301
|
+
...(text("reasoning") ? { reasoning: text("reasoning") } : {}),
|
|
302
|
+
...(text("observedAt") ? { observedAt: text("observedAt") } : {}),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grid's keyboard model, kept out of React so it can be tested without a
|
|
3
|
+
* DOM. `resolveGridKey` maps one keydown to one intent; the component only
|
|
4
|
+
* decides what to do with the intent.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface CellRef {
|
|
8
|
+
row: number;
|
|
9
|
+
col: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface GridSelection {
|
|
13
|
+
/** Where the range started — fixed while Shift extends it. */
|
|
14
|
+
anchor: CellRef;
|
|
15
|
+
/** The active cell; arrows move it. */
|
|
16
|
+
focus: CellRef;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GridBounds {
|
|
20
|
+
rows: number;
|
|
21
|
+
cols: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type GridDirection = "up" | "down" | "left" | "right";
|
|
25
|
+
|
|
26
|
+
export type GridKeyIntent =
|
|
27
|
+
| { type: "move"; direction: GridDirection; extend: boolean }
|
|
28
|
+
| { type: "jump"; edge: "top" | "bottom" | "start" | "end"; extend: boolean }
|
|
29
|
+
| { type: "edit" }
|
|
30
|
+
/** Typing over a cell: start editing seeded with this character. */
|
|
31
|
+
| { type: "type"; text: string }
|
|
32
|
+
| { type: "commit"; direction: GridDirection | null }
|
|
33
|
+
| { type: "cancel" }
|
|
34
|
+
| { type: "copy" }
|
|
35
|
+
| { type: "paste" }
|
|
36
|
+
| { type: "clear" }
|
|
37
|
+
| { type: "selectAll" };
|
|
38
|
+
|
|
39
|
+
export interface GridKeyEvent {
|
|
40
|
+
key: string;
|
|
41
|
+
shiftKey: boolean;
|
|
42
|
+
metaKey: boolean;
|
|
43
|
+
ctrlKey: boolean;
|
|
44
|
+
altKey: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ARROWS: Record<string, GridDirection> = {
|
|
48
|
+
ArrowUp: "up",
|
|
49
|
+
ArrowDown: "down",
|
|
50
|
+
ArrowLeft: "left",
|
|
51
|
+
ArrowRight: "right",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* One printable character starts an edit. `event.key.length === 1` is the
|
|
56
|
+
* reliable test — checking key codes or ranges misses every non-Latin keyboard.
|
|
57
|
+
*/
|
|
58
|
+
function isPrintable(event: GridKeyEvent): boolean {
|
|
59
|
+
return (
|
|
60
|
+
event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resolveGridKey(
|
|
65
|
+
event: GridKeyEvent,
|
|
66
|
+
state: { editing: boolean },
|
|
67
|
+
): GridKeyIntent | null {
|
|
68
|
+
const mod = event.metaKey || event.ctrlKey;
|
|
69
|
+
|
|
70
|
+
if (state.editing) {
|
|
71
|
+
if (event.key === "Escape") return { type: "cancel" };
|
|
72
|
+
if (event.key === "Enter") {
|
|
73
|
+
return { type: "commit", direction: event.shiftKey ? "up" : "down" };
|
|
74
|
+
}
|
|
75
|
+
if (event.key === "Tab") {
|
|
76
|
+
return { type: "commit", direction: event.shiftKey ? "left" : "right" };
|
|
77
|
+
}
|
|
78
|
+
// Everything else belongs to the editor input, not the grid.
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (mod && (event.key === "c" || event.key === "C")) return { type: "copy" };
|
|
83
|
+
if (mod && (event.key === "v" || event.key === "V")) return { type: "paste" };
|
|
84
|
+
if (mod && (event.key === "a" || event.key === "A")) {
|
|
85
|
+
return { type: "selectAll" };
|
|
86
|
+
}
|
|
87
|
+
if (mod && ARROWS[event.key]) {
|
|
88
|
+
const direction = ARROWS[event.key]!;
|
|
89
|
+
const edge =
|
|
90
|
+
direction === "up"
|
|
91
|
+
? "top"
|
|
92
|
+
: direction === "down"
|
|
93
|
+
? "bottom"
|
|
94
|
+
: direction === "left"
|
|
95
|
+
? "start"
|
|
96
|
+
: "end";
|
|
97
|
+
return { type: "jump", edge, extend: event.shiftKey };
|
|
98
|
+
}
|
|
99
|
+
if (ARROWS[event.key]) {
|
|
100
|
+
return {
|
|
101
|
+
type: "move",
|
|
102
|
+
direction: ARROWS[event.key]!,
|
|
103
|
+
extend: event.shiftKey,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (event.key === "Tab") {
|
|
107
|
+
return {
|
|
108
|
+
type: "move",
|
|
109
|
+
direction: event.shiftKey ? "left" : "right",
|
|
110
|
+
extend: false,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (event.key === "Enter") return { type: "edit" };
|
|
114
|
+
if (event.key === "Escape") return { type: "cancel" };
|
|
115
|
+
if (event.key === "Backspace" || event.key === "Delete") {
|
|
116
|
+
return { type: "clear" };
|
|
117
|
+
}
|
|
118
|
+
if (isPrintable(event)) return { type: "type", text: event.key };
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function clamp(value: number, max: number): number {
|
|
123
|
+
return Math.max(0, Math.min(max, value));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function moveCell(
|
|
127
|
+
ref: CellRef,
|
|
128
|
+
direction: GridDirection,
|
|
129
|
+
bounds: GridBounds,
|
|
130
|
+
): CellRef {
|
|
131
|
+
const lastRow = Math.max(0, bounds.rows - 1);
|
|
132
|
+
const lastCol = Math.max(0, bounds.cols - 1);
|
|
133
|
+
if (direction === "up")
|
|
134
|
+
return { row: clamp(ref.row - 1, lastRow), col: ref.col };
|
|
135
|
+
if (direction === "down") {
|
|
136
|
+
return { row: clamp(ref.row + 1, lastRow), col: ref.col };
|
|
137
|
+
}
|
|
138
|
+
if (direction === "left") {
|
|
139
|
+
return { row: ref.row, col: clamp(ref.col - 1, lastCol) };
|
|
140
|
+
}
|
|
141
|
+
return { row: ref.row, col: clamp(ref.col + 1, lastCol) };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function jumpCell(
|
|
145
|
+
ref: CellRef,
|
|
146
|
+
edge: "top" | "bottom" | "start" | "end",
|
|
147
|
+
bounds: GridBounds,
|
|
148
|
+
): CellRef {
|
|
149
|
+
if (edge === "top") return { row: 0, col: ref.col };
|
|
150
|
+
if (edge === "bottom")
|
|
151
|
+
return { row: Math.max(0, bounds.rows - 1), col: ref.col };
|
|
152
|
+
if (edge === "start") return { row: ref.row, col: 0 };
|
|
153
|
+
return { row: ref.row, col: Math.max(0, bounds.cols - 1) };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export interface GridRange {
|
|
157
|
+
top: number;
|
|
158
|
+
left: number;
|
|
159
|
+
bottom: number;
|
|
160
|
+
right: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function selectionRange(selection: GridSelection): GridRange {
|
|
164
|
+
return {
|
|
165
|
+
top: Math.min(selection.anchor.row, selection.focus.row),
|
|
166
|
+
bottom: Math.max(selection.anchor.row, selection.focus.row),
|
|
167
|
+
left: Math.min(selection.anchor.col, selection.focus.col),
|
|
168
|
+
right: Math.max(selection.anchor.col, selection.focus.col),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function isInRange(range: GridRange, ref: CellRef): boolean {
|
|
173
|
+
return (
|
|
174
|
+
ref.row >= range.top &&
|
|
175
|
+
ref.row <= range.bottom &&
|
|
176
|
+
ref.col >= range.left &&
|
|
177
|
+
ref.col <= range.right
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Apply a movement intent to a selection. `extend` keeps the anchor so
|
|
183
|
+
* Shift+arrow grows the range; a plain move collapses it to one cell.
|
|
184
|
+
*/
|
|
185
|
+
export function applyMove(
|
|
186
|
+
selection: GridSelection,
|
|
187
|
+
next: CellRef,
|
|
188
|
+
extend: boolean,
|
|
189
|
+
): GridSelection {
|
|
190
|
+
return extend
|
|
191
|
+
? { anchor: selection.anchor, focus: next }
|
|
192
|
+
: { anchor: next, focus: next };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Where a pasted rectangle lands. A single-cell selection anchors the paste at
|
|
197
|
+
* that cell; a range clamps the paste to the range so a stray large clipboard
|
|
198
|
+
* cannot overwrite rows the user never selected.
|
|
199
|
+
*/
|
|
200
|
+
export function pasteTargets(input: {
|
|
201
|
+
selection: GridSelection;
|
|
202
|
+
bounds: GridBounds;
|
|
203
|
+
rows: number;
|
|
204
|
+
cols: number;
|
|
205
|
+
}): GridRange {
|
|
206
|
+
const range = selectionRange(input.selection);
|
|
207
|
+
const singleCell = range.top === range.bottom && range.left === range.right;
|
|
208
|
+
const bottom = singleCell
|
|
209
|
+
? range.top + input.rows - 1
|
|
210
|
+
: Math.min(range.bottom, range.top + input.rows - 1);
|
|
211
|
+
const right = singleCell
|
|
212
|
+
? range.left + input.cols - 1
|
|
213
|
+
: Math.min(range.right, range.left + input.cols - 1);
|
|
214
|
+
return {
|
|
215
|
+
top: range.top,
|
|
216
|
+
left: range.left,
|
|
217
|
+
bottom: Math.min(bottom, input.bounds.rows - 1),
|
|
218
|
+
right: Math.min(right, input.bounds.cols - 1),
|
|
219
|
+
};
|
|
220
|
+
}
|