@rebasepro/app 0.17.3 → 0.18.0

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.
Files changed (185) hide show
  1. package/README.md +4 -1
  2. package/dist/components/index.d.ts +0 -3
  3. package/dist/contexts/AuthControllerContext.d.ts +2 -1
  4. package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
  5. package/dist/contexts/DialogsProvider.d.ts +2 -1
  6. package/dist/contexts/RebaseDataContext.d.ts +10 -1
  7. package/dist/contexts/StorageSourceContext.d.ts +11 -1
  8. package/dist/core/RebaseProps.d.ts +2 -12
  9. package/dist/debug/index.d.ts +19 -0
  10. package/dist/debug.js +3631 -0
  11. package/dist/hooks/data/delete.d.ts +16 -7
  12. package/dist/hooks/data/save.d.ts +18 -4
  13. package/dist/hooks/index.d.ts +1 -0
  14. package/dist/hooks/useAdminModeController.d.ts +5 -4
  15. package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
  16. package/dist/hooks/useStudioBridge.d.ts +33 -0
  17. package/dist/index.es.js +2599 -6980
  18. package/dist/index.es.js.map +1 -1
  19. package/dist/vitePlugin.d.ts +22 -0
  20. package/dist/vitePlugin.js +40 -1
  21. package/package.json +42 -31
  22. package/src/auth/api.ts +0 -134
  23. package/src/auth/index.ts +0 -26
  24. package/src/auth/types.ts +0 -115
  25. package/src/auth/useRebaseAuthController.ts +0 -345
  26. package/src/collections/collection_view_config.ts +0 -76
  27. package/src/collections/entity-display-cache.ts +0 -195
  28. package/src/collections/entity-display.ts +0 -77
  29. package/src/collections/entity_image_preview.ts +0 -61
  30. package/src/collections/filter-operator-resolution.ts +0 -119
  31. package/src/collections/form-layout.ts +0 -394
  32. package/src/collections/index.ts +0 -13
  33. package/src/collections/navigation_from_path.ts +0 -123
  34. package/src/collections/navigation_utils.ts +0 -171
  35. package/src/collections/parent_references_from_path.ts +0 -59
  36. package/src/collections/property-path.ts +0 -30
  37. package/src/collections/property_presentation.ts +0 -264
  38. package/src/collections/summary-property.ts +0 -114
  39. package/src/collections/title-property.ts +0 -312
  40. package/src/components/AIIcon.tsx +0 -47
  41. package/src/components/AuthSimulationMenu.tsx +0 -0
  42. package/src/components/ConfirmationDialog.tsx +0 -48
  43. package/src/components/Debug/UIReferenceView.tsx +0 -1053
  44. package/src/components/Debug/UIStyleGuide.tsx +0 -159
  45. package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
  46. package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
  47. package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
  48. package/src/components/Debug/collection-views/index.ts +0 -3
  49. package/src/components/Debug/collection-views/sample_data.ts +0 -44
  50. package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
  51. package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
  52. package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
  53. package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
  54. package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
  55. package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
  56. package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
  57. package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
  58. package/src/components/ErrorTooltip.tsx +0 -12
  59. package/src/components/ErrorView.tsx +0 -94
  60. package/src/components/LanguageToggle.tsx +0 -59
  61. package/src/components/LoginView/LoginView.tsx +0 -1235
  62. package/src/components/LoginView/index.ts +0 -2
  63. package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
  64. package/src/components/NotFoundPage.tsx +0 -27
  65. package/src/components/RebaseAuth.tsx +0 -27
  66. package/src/components/RebaseLogo.tsx +0 -88
  67. package/src/components/SchemaDriftBanner.tsx +0 -102
  68. package/src/components/UnsavedChangesDialog.tsx +0 -46
  69. package/src/components/UserDisplay.tsx +0 -54
  70. package/src/components/UserSelectPopover.tsx +0 -399
  71. package/src/components/UserSettingsView.tsx +0 -318
  72. package/src/components/common/index.ts +0 -6
  73. package/src/components/common/table_height.tsx +0 -21
  74. package/src/components/common/types.tsx +0 -66
  75. package/src/components/common/useColumnsIds.tsx +0 -261
  76. package/src/components/common/useDataTableController.tsx +0 -571
  77. package/src/components/common/useDebouncedData.ts +0 -49
  78. package/src/components/common/useScrollRestoration.tsx +0 -68
  79. package/src/components/index.tsx +0 -26
  80. package/src/contexts/AdminModeController.tsx +0 -11
  81. package/src/contexts/AnalyticsContext.tsx +0 -4
  82. package/src/contexts/AuthControllerContext.tsx +0 -4
  83. package/src/contexts/CollectionResolverContext.tsx +0 -24
  84. package/src/contexts/CollectionScopeContext.tsx +0 -68
  85. package/src/contexts/ComponentOverrideContext.tsx +0 -81
  86. package/src/contexts/CustomizationControllerContext.tsx +0 -4
  87. package/src/contexts/DataDriverContext.tsx +0 -4
  88. package/src/contexts/DataSourcesContext.tsx +0 -36
  89. package/src/contexts/DatabaseAdminContext.tsx +0 -4
  90. package/src/contexts/DialogsProvider.tsx +0 -56
  91. package/src/contexts/EffectiveRoleController.tsx +0 -12
  92. package/src/contexts/ModeController.tsx +0 -11
  93. package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
  94. package/src/contexts/RebaseDataContext.tsx +0 -4
  95. package/src/contexts/SnackbarProvider.tsx +0 -15
  96. package/src/contexts/StorageSourceContext.tsx +0 -4
  97. package/src/contexts/StorageSourcesContext.tsx +0 -30
  98. package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
  99. package/src/contexts/index.ts +0 -18
  100. package/src/core/PluginLifecycleManager.tsx +0 -96
  101. package/src/core/PluginProviderStack.tsx +0 -40
  102. package/src/core/Rebase.tsx +0 -488
  103. package/src/core/RebaseProps.tsx +0 -333
  104. package/src/core/RebaseRouter.tsx +0 -18
  105. package/src/core/RebaseRoutes.tsx +0 -24
  106. package/src/core/index.tsx +0 -5
  107. package/src/hooks/ApiConfigContext.tsx +0 -83
  108. package/src/hooks/data/collectionQuery.ts +0 -55
  109. package/src/hooks/data/delete.ts +0 -56
  110. package/src/hooks/data/save.ts +0 -78
  111. package/src/hooks/data/useCollection.tsx +0 -183
  112. package/src/hooks/data/useData.tsx +0 -18
  113. package/src/hooks/data/useDataOrder.ts +0 -26
  114. package/src/hooks/data/useFetch.tsx +0 -156
  115. package/src/hooks/data/useRelationSelector.tsx +0 -280
  116. package/src/hooks/index.tsx +0 -45
  117. package/src/hooks/useAdminModeController.tsx +0 -23
  118. package/src/hooks/useAnalyticsController.tsx +0 -8
  119. package/src/hooks/useAuthController.tsx +0 -15
  120. package/src/hooks/useAuthSubscription.ts +0 -130
  121. package/src/hooks/useBackendStorageSource.ts +0 -295
  122. package/src/hooks/useBridgeRegistration.tsx +0 -32
  123. package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
  124. package/src/hooks/useBuildAdminModeController.tsx +0 -29
  125. package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
  126. package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
  127. package/src/hooks/useBuildModeController.tsx +0 -73
  128. package/src/hooks/useClipboard.tsx +0 -158
  129. package/src/hooks/useCollapsedGroups.ts +0 -116
  130. package/src/hooks/useComponentOverride.tsx +0 -59
  131. package/src/hooks/useCustomizationController.tsx +0 -14
  132. package/src/hooks/useDialogsController.tsx +0 -15
  133. package/src/hooks/useEffectiveRoleController.tsx +0 -10
  134. package/src/hooks/useLargeLayout.tsx +0 -65
  135. package/src/hooks/useModeController.tsx +0 -23
  136. package/src/hooks/useNavigationBlocker.tsx +0 -108
  137. package/src/hooks/usePermissions.ts +0 -45
  138. package/src/hooks/useRebaseClient.tsx +0 -10
  139. package/src/hooks/useRebaseContext.tsx +0 -84
  140. package/src/hooks/useRebaseRegistry.tsx +0 -91
  141. package/src/hooks/useResolvedComponent.tsx +0 -195
  142. package/src/hooks/useSlot.tsx +0 -64
  143. package/src/hooks/useSnackbarController.tsx +0 -80
  144. package/src/hooks/useStorageSource.tsx +0 -11
  145. package/src/hooks/useStudioBridge.tsx +0 -258
  146. package/src/hooks/useTranslation.ts +0 -38
  147. package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
  148. package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
  149. package/src/i18n/RebaseI18nProvider.tsx +0 -159
  150. package/src/index.ts +0 -32
  151. package/src/internal/common.tsx +0 -28
  152. package/src/internal/useRestoreScroll.tsx +0 -69
  153. package/src/locales/de.ts +0 -941
  154. package/src/locales/en.ts +0 -1021
  155. package/src/locales/es.ts +0 -962
  156. package/src/locales/fr.ts +0 -941
  157. package/src/locales/hi.ts +0 -941
  158. package/src/locales/it.ts +0 -941
  159. package/src/locales/pt.ts +0 -949
  160. package/src/util/constants.ts +0 -7
  161. package/src/util/createFormexStub.tsx +0 -66
  162. package/src/util/entity_cache.ts +0 -242
  163. package/src/util/enums.ts +0 -34
  164. package/src/util/icon_list.ts +0 -22
  165. package/src/util/icons.tsx +0 -129
  166. package/src/util/index.ts +0 -13
  167. package/src/util/local_storage.ts +0 -66
  168. package/src/util/previews.ts +0 -127
  169. package/src/util/useStorageUploadController.tsx +0 -354
  170. package/src/vitePlugin.ts +0 -215
  171. /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
  172. /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
  173. /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
  174. /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
  175. /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
  176. /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
  177. /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
  178. /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
  179. /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
  180. /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
  181. /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
  182. /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
  183. /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
  184. /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
  185. /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
@@ -1,114 +0,0 @@
1
- import type { Property } from "@rebasepro/types";
2
-
3
- /**
4
- * Whether a property can stand in for a record in *one line*.
5
- *
6
- * Preview surfaces — the reference card, a list row, a board card — have a
7
- * single line per property and no way to grow. That is a constraint on the
8
- * *value*, not on the property's importance: a Markdown biography is the most
9
- * interesting column on an author, and it is still the wrong thing to paste
10
- * into a 44px card. Ranking it here means the picker can skip it without
11
- * anyone having to configure their way out of a broken card.
12
- *
13
- * Kept beside {@link getTitlePropertyCandidates} because the two answer
14
- * neighbouring questions — "what is this record called" and "what else can be
15
- * said about it in a line" — from the same property schema.
16
- *
17
- * @group Collections
18
- */
19
- export const SUMMARY_RANK = {
20
- /**
21
- * No single-line form exists. A map renders as a key/value table and an
22
- * array of maps as a stack of them; there is no first line to take.
23
- */
24
- UNUSABLE: 0,
25
- /**
26
- * Readable in one line, but only as an excerpt — the property holds a
27
- * document, and the line is its opening. Worth showing when nothing better
28
- * is available, never in preference to a value that fits whole.
29
- */
30
- EXCERPT: 1,
31
- /** Short and self-contained: the line *is* the value. */
32
- DIRECT: 2
33
- } as const;
34
-
35
- /** One of the three summary ranks. */
36
- export type SummaryRank = typeof SUMMARY_RANK[keyof typeof SUMMARY_RANK];
37
-
38
- /**
39
- * File-storage backed content (single image, array of images, generic upload…).
40
- * Preview surfaces give these their own image slot, so they are never also a
41
- * text line.
42
- *
43
- * @group Collections
44
- */
45
- export function isStorageProperty(property: Property | undefined): boolean {
46
- if (!property) return false;
47
- if (property.type === "string" && (property.storage || property.admin?.urlPreview === "image")) return true;
48
- if (property.type === "array" && property.of && !Array.isArray(property.of)) {
49
- const inner = property.of;
50
- if (inner.type === "string" && (inner.storage || inner.admin?.urlPreview === "image")) return true;
51
- }
52
- return false;
53
- }
54
-
55
- /**
56
- * True when the property holds free text long enough to be a document rather
57
- * than a value — Markdown, or an explicitly multi-line string.
58
- *
59
- * @group Collections
60
- */
61
- export function isLongTextProperty(property: Property | undefined): boolean {
62
- if (!property || property.type !== "string") return false;
63
- return Boolean(property.admin?.markdown || property.admin?.multiline);
64
- }
65
-
66
- /**
67
- * How well a property reads as one line of a preview. See {@link SUMMARY_RANK}.
68
- *
69
- * Decided from the property *schema* only: no values are consulted, so the
70
- * answer is stable for a collection and can be memoised per collection rather
71
- * than per row.
72
- *
73
- * @group Collections
74
- */
75
- export function rankSummaryProperty(property: Property | undefined): SummaryRank {
76
- if (!property) return SUMMARY_RANK.UNUSABLE;
77
-
78
- // Rendered by the image slot, not as text.
79
- if (isStorageProperty(property)) return SUMMARY_RANK.UNUSABLE;
80
-
81
- // A map is a table of its own, whether it declares its properties (a row
82
- // per sub-property) or not (a key/value dump). Neither has a first line.
83
- if (property.type === "map") return SUMMARY_RANK.UNUSABLE;
84
-
85
- if (property.type === "array") {
86
- // A tuple: several properties side by side, so there is nothing single
87
- // about the line it would produce.
88
- if (Array.isArray(property.of)) return SUMMARY_RANK.UNUSABLE;
89
- if (!property.of) return property.oneOf ? SUMMARY_RANK.UNUSABLE : SUMMARY_RANK.DIRECT;
90
- // Arrays of block-shaped things inherit the block shape.
91
- const innerRank = rankSummaryProperty(property.of);
92
- if (innerRank === SUMMARY_RANK.UNUSABLE) return SUMMARY_RANK.UNUSABLE;
93
- // An array of documents is a stack of excerpts — worse than one
94
- // excerpt, and never what a card should lead with.
95
- if (innerRank === SUMMARY_RANK.EXCERPT) return SUMMARY_RANK.UNUSABLE;
96
- // Short values render as a run of chips, which fits a line.
97
- return SUMMARY_RANK.DIRECT;
98
- }
99
-
100
- if (isLongTextProperty(property)) return SUMMARY_RANK.EXCERPT;
101
-
102
- // Numbers, dates, booleans, enums, plain strings, and single references or
103
- // relations (which collapse to one linked line) all fit as they are.
104
- return SUMMARY_RANK.DIRECT;
105
- }
106
-
107
- /**
108
- * True when the property has any single-line form at all.
109
- *
110
- * @group Collections
111
- */
112
- export function canSummariseProperty(property: Property | undefined): boolean {
113
- return rankSummaryProperty(property) !== SUMMARY_RANK.UNUSABLE;
114
- }
@@ -1,312 +0,0 @@
1
- import { isRelationalCollectionConfig, type Property, type Relation } from "@rebasepro/types";
2
- import { generateForeignKeyName } from "@rebasepro/utils";
3
- import type { AdminCollection } from "@rebasepro/cms-types";
4
- import { getPrimaryKeys, isPropertyBuilder } from "@rebasepro/common";
5
- import { getPropertyInPath } from "./property-path";
6
- import { getDisplayPropertyKey, hasDeclaredDisplay } from "./entity-display";
7
- import { isStorageProperty } from "./summary-property";
8
-
9
- /**
10
- * How good a property is as the human-readable title of an entity.
11
- * Higher wins; ties are broken by declaration order.
12
- * `DISQUALIFIED` means the property can never be a title.
13
- */
14
- const SCORE = {
15
- DISQUALIFIED: -1,
16
- /**
17
- * Points at another entity. Only readable once the target resolves, and it
18
- * renders as the raw key until then — last resort, for collections (like
19
- * junction tables) that hold no text of their own.
20
- */
21
- REFERENCE: 5,
22
- RELATION: 10,
23
- /** A user picker: resolves to a person's name, like a relation does */
24
- USER: 10,
25
- /** Long form text — readable, but a description, not a name */
26
- LONG_TEXT: 20,
27
- /** A closed set of values: shared by many rows, so it identifies nothing */
28
- ENUM: 30,
29
- /** Identifying, but PII and usually secondary to a name */
30
- EMAIL: 45,
31
- /** Any plain, short, free text field */
32
- PLAIN_TEXT: 60,
33
- /** Free text whose name says it holds the entity's label */
34
- NAMED: 100
35
- } as const;
36
-
37
- /**
38
- * Keys whose name states outright that the property is the entity label.
39
- * This is a *bonus* on top of the structural rules — never a requirement, and
40
- * never the mechanism that keeps identifiers out of the title slot.
41
- */
42
- const TITLE_LIKE_KEYS = new Set([
43
- "name",
44
- "fullname",
45
- "displayname",
46
- "title",
47
- "label",
48
- "heading",
49
- "subject",
50
- "username",
51
- "nickname"
52
- ]);
53
-
54
- function normalizeKey(key: string): string {
55
- return key.toLowerCase().replace(/[^a-z0-9]/g, "");
56
- }
57
-
58
- function isHidden(property: Property): boolean {
59
- return Boolean(property.admin?.hideFromCollection);
60
- }
61
-
62
- /**
63
- * Every column on this collection that stores a foreign key: the `localKey` of
64
- * each owning relation (declared inline on a property or in `relations[]`), the
65
- * source column of a junction, and the key a many-to-many joins on.
66
- *
67
- * These hold another entity's id, so they must never fill the title slot even
68
- * though they are declared as plain strings.
69
- */
70
- function getForeignKeyColumns<M extends Record<string, unknown>>(collection: AdminCollection<M>): Set<string> {
71
- const keys = new Set<string>();
72
-
73
- const addRelationKeys = (relation: Relation) => {
74
- switch (relation.kind) {
75
- case "belongsTo":
76
- // Left to be inferred, the column still takes the conventional
77
- // `<relation>_id` name.
78
- keys.add(relation.localKey ?? generateForeignKeyName(relation.relationName ?? ""));
79
- break;
80
- case "manyToMany":
81
- if (relation.through?.sourceColumn) keys.add(relation.through.sourceColumn);
82
- break;
83
- case "via": {
84
- // Only the first hop starts on this collection's table.
85
- const from = relation.joinPath?.[0]?.on?.from;
86
- for (const column of Array.isArray(from) ? from : [from]) {
87
- if (column) keys.add(column);
88
- }
89
- break;
90
- }
91
- default:
92
- // hasOne / hasMany put their column on the *target*.
93
- break;
94
- }
95
- };
96
-
97
- for (const relation of (isRelationalCollectionConfig(collection) ? collection.relations : undefined) ?? []) {
98
- addRelationKeys(relation);
99
- }
100
-
101
- for (const [key, propertyRaw] of Object.entries(collection.properties ?? {})) {
102
- const property = propertyRaw as Property | undefined;
103
- if (!property || isPropertyBuilder(property) || property.type !== "relation") continue;
104
- if (property.relation) {
105
- addRelationKeys({ ...property.relation,
106
- relationName: property.relation.relationName ?? key });
107
- }
108
- }
109
-
110
- return keys;
111
- }
112
-
113
- /**
114
- * True when the property is declared to hold an opaque identifier rather than
115
- * something a person reads: a primary key, a foreign key, a UUID column, or a
116
- * picker bound to an auth user id. Decided from the property *schema*, never
117
- * from the key name.
118
- */
119
- function isIdentifierProperty(property: Property, key: string, idKeys: Set<string>, foreignKeys: Set<string>): boolean {
120
- if (idKeys.has(key)) return true;
121
- if (foreignKeys.has(key)) return true;
122
- if ("isId" in property && property.isId) return true;
123
- if (property.type === "string" && property.columnType === "uuid") return true;
124
- return false;
125
- }
126
-
127
- function scoreTitleCandidate(property: Property, key: string, idKeys: Set<string>, foreignKeys: Set<string>): number {
128
- if (isHidden(property)) return SCORE.DISQUALIFIED;
129
- if (isIdentifierProperty(property, key, idKeys, foreignKeys)) return SCORE.DISQUALIFIED;
130
- if (isStorageProperty(property)) return SCORE.DISQUALIFIED;
131
-
132
- if (property.type === "relation") {
133
- const isMany = property.resolvedRelation
134
- ? property.resolvedRelation.cardinality === "many"
135
- : property.relation?.kind === "hasMany" || property.relation?.kind === "manyToMany";
136
- return isMany ? SCORE.DISQUALIFIED : SCORE.RELATION;
137
- }
138
- if (property.type === "reference") {
139
- return SCORE.REFERENCE;
140
- }
141
- if (property.type !== "string") {
142
- // Numbers, dates, booleans, maps and arrays never read as a title.
143
- return SCORE.DISQUALIFIED;
144
- }
145
-
146
- // A user picker stores an auth user id but renders the person behind it, so
147
- // it ranks with relations: usable as a title, but only as a last resort.
148
- if (property.userSelect) return SCORE.USER;
149
- if (property.enum) return SCORE.ENUM;
150
- if (property.admin?.multiline || property.admin?.markdown) return SCORE.LONG_TEXT;
151
- if (property.email) return SCORE.EMAIL;
152
- if (TITLE_LIKE_KEYS.has(normalizeKey(key))) return SCORE.NAMED;
153
- return SCORE.PLAIN_TEXT;
154
- }
155
-
156
- /**
157
- * All properties that could serve as the entity title, best first.
158
- *
159
- * Candidates are ranked from the property *schema* — identifiers (primary
160
- * keys, foreign keys, UUID columns, user pickers), images and hidden fields are
161
- * excluded structurally, so a collection whose id column is called something
162
- * other than `id` is handled the same as one where it isn't.
163
- *
164
- * When the collection declares `propertiesOrder` the developer has already
165
- * stated what comes first, so qualifying candidates keep that order; otherwise
166
- * they are ranked (a name beats a description beats a relation).
167
- *
168
- * @group Collections
169
- */
170
- export function getTitlePropertyCandidates<M extends Record<string, unknown>>(
171
- collection: AdminCollection<M>
172
- ): string[] {
173
- if (!collection.properties) return [];
174
-
175
- // `getPropertyInPath`, not `properties[key]`: a declared title on a nested
176
- // field — `profile.display_name` — was looked up flat, found nothing, and
177
- // silently fell through to the derived ranking. The dotted form has been
178
- // documented as valid the whole time.
179
- const declared = getDisplayPropertyKey(collection, "title");
180
- if (declared && getPropertyInPath(collection.properties, declared)) {
181
- return [declared];
182
- }
183
-
184
- const idKeys = new Set<string>(getPrimaryKeys(collection) as string[]);
185
- const foreignKeys = getForeignKeyColumns(collection);
186
- const explicitOrder = collection.propertiesOrder as string[] | undefined;
187
- const order = explicitOrder ?? Object.keys(collection.properties);
188
-
189
- const scored: { key: string; score: number; index: number }[] = [];
190
- order.forEach((key, index) => {
191
- const property = collection.properties[key];
192
- if (!property || isPropertyBuilder(property)) return;
193
- const score = scoreTitleCandidate(property as Property, key, idKeys, foreignKeys);
194
- if (score === SCORE.DISQUALIFIED) return;
195
- scored.push({ key,
196
- score,
197
- index });
198
- });
199
-
200
- // An explicit `propertiesOrder` is a statement of what matters first, so it
201
- // wins over the ranking — the ranking only decides between properties the
202
- // developer never ordered.
203
- if (!explicitOrder) {
204
- scored.sort((a, b) => (b.score - a.score) || (a.index - b.index));
205
- }
206
-
207
- return scored.map(candidate => candidate.key);
208
- }
209
-
210
- /**
211
- * The relation a collection *leads* with, if it leads with one.
212
- *
213
- * A junction-shaped collection — a pipeline entry, a membership, a booking —
214
- * holds no label of its own: it is "this candidate, for that vacancy". The
215
- * general ranking puts relations last (they only read once the target
216
- * resolves), so the title slot would otherwise fall to whatever free text
217
- * happens to follow: a note, a comment. Preview surfaces that render one row
218
- * per entity (list, cards, board) prefer the leading relation instead.
219
- *
220
- * The decision is structural — the first property that could be a title, in the
221
- * order the developer declared (or ordered) them. Declaration order is a
222
- * statement about what the collection is about; property *names* are not
223
- * consulted here.
224
- *
225
- * Returns `undefined` when that first property is anything but a
226
- * single-cardinality relation, or when the collection states its own
227
- * `display.title`.
228
- *
229
- * @group Collections
230
- */
231
- export function getLeadingRelationTitleKey<M extends Record<string, unknown>>(
232
- collection: AdminCollection<M>
233
- ): string | undefined {
234
- if (!collection.properties) return undefined;
235
- // The collection states what it is called, in one form or the other; either
236
- // way it is not asking for the leading-relation heuristic.
237
- if (hasDeclaredDisplay(collection, "title")) return undefined;
238
-
239
- const idKeys = new Set<string>(getPrimaryKeys(collection) as string[]);
240
- const foreignKeys = getForeignKeyColumns(collection);
241
- const order = (collection.propertiesOrder as string[] | undefined) ?? Object.keys(collection.properties);
242
-
243
- for (const key of order) {
244
- const property = collection.properties[key];
245
- if (!property || isPropertyBuilder(property)) continue;
246
- if (scoreTitleCandidate(property as Property, key, idKeys, foreignKeys) === SCORE.DISQUALIFIED) continue;
247
- return (property as Property).type === "relation" ? key : undefined;
248
- }
249
-
250
- return undefined;
251
- }
252
-
253
- /**
254
- * The property that should fill the title slot for a collection, ignoring any
255
- * concrete values. Prefer {@link getTitlePropertyKeyForValues} when an entity
256
- * is at hand — it can skip candidates that happen to be empty or to hold an id.
257
- *
258
- * @group Collections
259
- */
260
- export function getTitlePropertyKey<M extends Record<string, unknown>>(
261
- collection: AdminCollection<M>
262
- ): string | undefined {
263
- return getTitlePropertyCandidates(collection)[0];
264
- }
265
-
266
- const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
267
- const LONG_HEX_REGEX = /^[0-9a-f]{24,}$/i;
268
- const CUID_REGEX = /^c[a-z0-9]{20,}$/i;
269
-
270
- /**
271
- * True when a value reads as a machine identifier (UUID, ObjectId/long hex,
272
- * cuid) rather than as something worth showing to a person.
273
- *
274
- * @group Collections
275
- */
276
- export function looksLikeIdentifierValue(value: unknown): boolean {
277
- if (typeof value !== "string") return false;
278
- const trimmed = value.trim();
279
- return UUID_REGEX.test(trimmed) || LONG_HEX_REGEX.test(trimmed) || CUID_REGEX.test(trimmed);
280
- }
281
-
282
- /**
283
- * The title property for a concrete entity: the best-ranked candidate that
284
- * actually carries a readable value. Candidates that are empty, that repeat the
285
- * entity id, or that hold an opaque identifier are skipped, so a free-text
286
- * column that happens to store UUIDs never ends up as the title.
287
- *
288
- * Returns the top-ranked candidate when none of them has a usable value, so
289
- * callers can still render a placeholder for that property.
290
- *
291
- * @group Collections
292
- */
293
- export function getTitlePropertyKeyForValues<M extends Record<string, unknown>>(
294
- collection: AdminCollection<M>,
295
- values: Record<string, unknown> | undefined,
296
- entityId?: string | number
297
- ): string | undefined {
298
- const candidates = getTitlePropertyCandidates(collection);
299
- if (!values || candidates.length === 0) return candidates[0];
300
-
301
- for (const key of candidates) {
302
- const value = values[key];
303
- if (value === undefined || value === null || value === "") continue;
304
- if (typeof value === "string") {
305
- if (looksLikeIdentifierValue(value)) continue;
306
- if (entityId !== undefined && value === String(entityId)) continue;
307
- }
308
- return key;
309
- }
310
-
311
- return candidates[0];
312
- }
@@ -1,47 +0,0 @@
1
- import { Wand2Icon } from "@rebasepro/ui";
2
- import React from "react";
3
-
4
- export interface AIIconProps {
5
- size?: "smallest" | "small" | "medium" | "large";
6
- className?: string;
7
- }
8
-
9
- /**
10
- * AI-styled AutoAwesome icon with gradient coloring.
11
- * Used consistently across AI features for visual identification.
12
- */
13
- export function AIIcon({ size = "small", className }: AIIconProps) {
14
- const sizeMap: Record<string, number> = {
15
- smallest: 16,
16
- small: 20,
17
- medium: 24,
18
- large: 28
19
- };
20
- const numericSize = typeof size === "string" ? sizeMap[size] || 20 : size;
21
-
22
- return (
23
- <Wand2Icon
24
- size={numericSize}
25
- className={className}
26
- style={{
27
- background: "linear-gradient(to right, var(--color-primary), var(--color-secondary))",
28
- WebkitBackgroundClip: "text",
29
- WebkitTextFillColor: "transparent",
30
- backgroundClip: "text"
31
- }}
32
- />
33
- );
34
- }
35
-
36
- /**
37
- * Small animated dot indicator for AI-modified elements.
38
- * Shows a pulsing gradient dot.
39
- */
40
- export function AIModifiedIndicator({ className }: { className?: string }) {
41
- return (
42
- <div
43
- className={`w-2 h-2 rounded-full bg-gradient-to-r from-primary to-secondary animate-pulse ${className ?? ""}`}
44
- title="AI modified"
45
- />
46
- );
47
- }
File without changes
@@ -1,48 +0,0 @@
1
- import React from "react";
2
-
3
- import { Button, Dialog, DialogActions, DialogContent, DialogTitle, LoadingButton } from "@rebasepro/ui";
4
- import { useTranslation } from "../hooks";
5
-
6
- export function ConfirmationDialog({
7
- open,
8
- onAccept,
9
- onCancel,
10
- title,
11
- loading,
12
- body
13
- }: {
14
- open: boolean,
15
- onAccept: () => void,
16
- onCancel: () => void,
17
- loading?: boolean,
18
- title: React.ReactNode,
19
- body?: React.ReactNode,
20
- }) {
21
- const { t } = useTranslation();
22
- return (
23
- <Dialog
24
- open={open}
25
- onOpenChange={(open) => !open ? onCancel() : undefined}
26
- >
27
- <DialogTitle variant={"h6"} className={"mb-2"}>{title}</DialogTitle>
28
- <DialogContent>
29
- {body}
30
- </DialogContent>
31
-
32
- <DialogActions>
33
- <Button
34
- variant={"text"}
35
- onClick={onCancel}
36
- autoFocus>{t("cancel")}</Button>
37
-
38
- <LoadingButton
39
- type="submit"
40
- loading={loading}
41
- onClick={onAccept}
42
- autoFocus>
43
- {t("ok")}
44
- </LoadingButton>
45
- </DialogActions>
46
- </Dialog>
47
- );
48
- }