@rebasepro/admin 0.13.0 → 0.13.1-canary.g06dbe5b

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 (188) hide show
  1. package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-C3DbFNqp.js} +3 -3
  2. package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-C3DbFNqp.js.map} +1 -1
  3. package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-DFH8rXRN.js} +5 -5
  4. package/dist/PropertyEditView-DFH8rXRN.js.map +1 -0
  5. package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-DiUDOu4b.js} +9 -13
  6. package/dist/RouterCollectionsStudioView-DiUDOu4b.js.map +1 -0
  7. package/dist/collection_editor/serializable_types.d.ts +1 -0
  8. package/dist/collection_editor_ui.js +4 -4
  9. package/dist/components/AdditionalFieldValue.d.ts +25 -0
  10. package/dist/components/ClearFilterSortButton.d.ts +7 -1
  11. package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
  12. package/dist/components/CollectionViewBinding/EntityCardBinding.d.ts +3 -1
  13. package/dist/components/CollectionViewBinding/SearchExplanation.d.ts +48 -0
  14. package/dist/components/CollectionViewBinding/SearchHighlight.d.ts +101 -0
  15. package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
  16. package/dist/components/CollectionViewBinding/SortButton.d.ts +7 -1
  17. package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
  18. package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
  19. package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
  20. package/dist/components/DetailViewBinding.d.ts +15 -0
  21. package/dist/components/DrawerNavigationGroup.d.ts +9 -6
  22. package/dist/components/DrawerNavigationItem.d.ts +16 -6
  23. package/dist/components/EditViewBinding.d.ts +30 -1
  24. package/dist/components/EntityDisplayHeader.d.ts +53 -0
  25. package/dist/components/EntityIdentityBar.d.ts +43 -1
  26. package/dist/components/EntityViewBinding.d.ts +60 -6
  27. package/dist/components/RebaseLayout.d.ts +1 -1
  28. package/dist/components/RebaseShell.d.ts +1 -1
  29. package/dist/components/RelationSelector.d.ts +8 -1
  30. package/dist/components/UserSelector.d.ts +8 -1
  31. package/dist/components/app/Scaffold.d.ts +9 -3
  32. package/dist/components/index.d.ts +2 -0
  33. package/dist/data_export/export/export.d.ts +7 -0
  34. package/dist/data_export/export/fetch_export_data.d.ts +48 -0
  35. package/dist/data_export/export/index.d.ts +1 -0
  36. package/dist/data_import/utils/csv.d.ts +30 -0
  37. package/dist/data_import/utils/index.d.ts +2 -0
  38. package/dist/data_import/utils/save_entities.d.ts +38 -0
  39. package/dist/data_import/utils/transforms.d.ts +9 -0
  40. package/dist/{export-J79rMoNj.js → export-Bd5i6Uvm.js} +175 -39
  41. package/dist/export-Bd5i6Uvm.js.map +1 -0
  42. package/dist/form/components/FieldBlock.d.ts +58 -4
  43. package/dist/form/components/FormRail.d.ts +13 -0
  44. package/dist/form/components/FormSections.d.ts +15 -1
  45. package/dist/form/components/useRailVisible.d.ts +5 -4
  46. package/dist/form/form_utils.d.ts +38 -1
  47. package/dist/{history-D7L_JlLO.js → history-D9R6LUrH.js} +4 -4
  48. package/dist/{history-D7L_JlLO.js.map → history-D9R6LUrH.js.map} +1 -1
  49. package/dist/hooks/useEntityDisplay.d.ts +102 -0
  50. package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
  51. package/dist/{import-BvYl--U4.js → import-pt_j-0G3.js} +6 -5
  52. package/dist/import-pt_j-0G3.js.map +1 -0
  53. package/dist/index.d.ts +2 -2
  54. package/dist/index.js +158 -60
  55. package/dist/index.js.map +1 -1
  56. package/dist/preview/compact_text.d.ts +23 -0
  57. package/dist/preview/components/BooleanPreview.d.ts +3 -1
  58. package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
  59. package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
  60. package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
  61. package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
  62. package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
  63. package/dist/routes/RebaseRoute.d.ts +1 -1
  64. package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
  65. package/dist/util/number_format.d.ts +14 -0
  66. package/dist/util/previews.d.ts +9 -2
  67. package/dist/util/view_constants.d.ts +24 -0
  68. package/dist/util/view_mode.d.ts +18 -0
  69. package/dist/{util-GiwPgxnL.js → util-nX_-JRuF.js} +3984 -1774
  70. package/dist/util-nX_-JRuF.js.map +1 -0
  71. package/package.json +10 -9
  72. package/src/collection_editor/serializable_types.ts +1 -0
  73. package/src/collection_editor/ui/collection_editor/CollectionsStudioView.tsx +9 -11
  74. package/src/collection_editor/ui/collection_editor/properties/MapPropertyField.tsx +1 -1
  75. package/src/collection_editor/ui/collection_editor/properties/advanced/AdvancedPropertyValidation.tsx +5 -2
  76. package/src/collection_editor/useLocalCollectionsConfigController.tsx +10 -3
  77. package/src/components/AdditionalFieldValue.tsx +68 -0
  78. package/src/components/ClearFilterSortButton.tsx +16 -7
  79. package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
  80. package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
  81. package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
  82. package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
  83. package/src/components/CollectionTableBinding/table_bindings.tsx +7 -1
  84. package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
  85. package/src/components/CollectionViewBinding/CollectionBoardViewBinding.tsx +13 -7
  86. package/src/components/CollectionViewBinding/CollectionCardViewBinding.tsx +2 -1
  87. package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +822 -354
  88. package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
  89. package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +51 -12
  90. package/src/components/CollectionViewBinding/CollectionViewStartActions.tsx +9 -0
  91. package/src/components/CollectionViewBinding/EntityCardBinding.tsx +51 -39
  92. package/src/components/CollectionViewBinding/SearchExplanation.tsx +106 -0
  93. package/src/components/CollectionViewBinding/SearchHighlight.tsx +311 -0
  94. package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
  95. package/src/components/CollectionViewBinding/SortButton.tsx +16 -4
  96. package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
  97. package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
  98. package/src/components/CollectionViewBinding/SplitListView.tsx +47 -7
  99. package/src/components/CollectionViewBinding/ViewModeToggle.tsx +10 -5
  100. package/src/components/CollectionViewBinding/useBoardDataController.tsx +23 -6
  101. package/src/components/CollectionViewBinding/usePreviewSlots.ts +183 -64
  102. package/src/components/CollectionViewBinding/utils.ts +7 -4
  103. package/src/components/DefaultDrawer.tsx +19 -23
  104. package/src/components/DetailViewBinding.tsx +109 -62
  105. package/src/components/DrawerNavigationGroup.tsx +16 -39
  106. package/src/components/DrawerNavigationItem.tsx +44 -16
  107. package/src/components/EditViewBinding.tsx +233 -50
  108. package/src/components/EntityDisplayHeader.tsx +170 -0
  109. package/src/components/EntityIdentityBar.tsx +163 -23
  110. package/src/components/EntityInspector.tsx +24 -6
  111. package/src/components/EntityPreviewBinding.tsx +58 -18
  112. package/src/components/EntityViewBinding.tsx +344 -41
  113. package/src/components/InlineEntityPreview.tsx +10 -10
  114. package/src/components/RebaseLayout.tsx +2 -2
  115. package/src/components/RebaseShell.tsx +2 -2
  116. package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
  117. package/src/components/RelationSelector.tsx +35 -10
  118. package/src/components/SideDialogs.tsx +15 -3
  119. package/src/components/SidePanelBinding.tsx +122 -66
  120. package/src/components/UserSelector.tsx +67 -13
  121. package/src/components/app/Scaffold.tsx +112 -10
  122. package/src/components/history/LastEditedByIndicator.tsx +7 -12
  123. package/src/components/index.ts +3 -0
  124. package/src/data_export/export/ExportCollectionAction.tsx +101 -48
  125. package/src/data_export/export/export.ts +63 -16
  126. package/src/data_export/export/fetch_export_data.ts +90 -0
  127. package/src/data_export/export/index.ts +1 -0
  128. package/src/data_import/components/ImportSaveInProgress.tsx +35 -36
  129. package/src/data_import/import/ImportCollectionAction.tsx +13 -2
  130. package/src/data_import/utils/csv.ts +153 -0
  131. package/src/data_import/utils/data.ts +16 -2
  132. package/src/data_import/utils/file_to_json.ts +51 -5
  133. package/src/data_import/utils/index.ts +2 -0
  134. package/src/data_import/utils/save_entities.ts +138 -0
  135. package/src/data_import/utils/transforms.ts +23 -0
  136. package/src/form/EntityForm.tsx +74 -53
  137. package/src/form/EntityFormBinding.tsx +24 -11
  138. package/src/form/PropertyFieldBinding.tsx +2 -2
  139. package/src/form/components/FieldBlock.tsx +80 -10
  140. package/src/form/components/FormRail.tsx +19 -2
  141. package/src/form/components/FormSections.tsx +32 -5
  142. package/src/form/components/StorageUploadProgress.tsx +9 -3
  143. package/src/form/components/useRailVisible.tsx +5 -4
  144. package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
  145. package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
  146. package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
  147. package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
  148. package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
  149. package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
  150. package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
  151. package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +21 -2
  152. package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
  153. package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
  154. package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
  155. package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
  156. package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
  157. package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
  158. package/src/form/form_utils.ts +78 -0
  159. package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
  160. package/src/hooks/useEntityDisplay.tsx +262 -0
  161. package/src/hooks/useEntityDisplayTitle.tsx +27 -38
  162. package/src/index.ts +9 -1
  163. package/src/preview/PropertyPreview.tsx +25 -5
  164. package/src/preview/compact_text.ts +68 -0
  165. package/src/preview/components/BooleanPreview.tsx +5 -2
  166. package/src/preview/components/ReferencePreview.tsx +5 -8
  167. package/src/preview/components/RelationPreview.tsx +8 -7
  168. package/src/preview/components/StorageThumbnail.tsx +5 -2
  169. package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
  170. package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
  171. package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
  172. package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
  173. package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
  174. package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
  175. package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
  176. package/src/routes/RebaseRoute.tsx +62 -4
  177. package/src/types/components/PropertyPreviewProps.tsx +21 -0
  178. package/src/util/navigation_utils.ts +4 -2
  179. package/src/util/number_format.ts +46 -0
  180. package/src/util/previews.ts +8 -55
  181. package/src/util/property_utils.tsx +6 -1
  182. package/src/util/view_constants.ts +27 -0
  183. package/src/util/view_mode.ts +36 -0
  184. package/dist/PropertyEditView-W8I3YV89.js.map +0 -1
  185. package/dist/RouterCollectionsStudioView-BsLTNoqX.js.map +0 -1
  186. package/dist/export-J79rMoNj.js.map +0 -1
  187. package/dist/import-BvYl--U4.js.map +0 -1
  188. package/dist/util-GiwPgxnL.js.map +0 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/admin",
3
3
  "type": "module",
4
- "version": "0.13.0",
4
+ "version": "0.13.1-canary.g06dbe5b",
5
5
  "description": "Rebase CMS — content management views, forms, and routing",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -83,14 +83,14 @@
83
83
  "react-dropzone": "^19.1.1",
84
84
  "react-use-measure": "^2.1.7",
85
85
  "zod": "^4.4.3",
86
- "@rebasepro/admin-types": "0.13.0",
87
- "@rebasepro/app": "0.13.0",
88
- "@rebasepro/common": "0.13.0",
89
- "@rebasepro/inference": "0.13.0",
90
- "@rebasepro/forms": "0.13.0",
91
- "@rebasepro/ui": "0.13.0",
92
- "@rebasepro/types": "0.13.0",
93
- "@rebasepro/utils": "0.13.0"
86
+ "@rebasepro/admin-types": "0.13.1-canary.g06dbe5b",
87
+ "@rebasepro/app": "0.13.1-canary.g06dbe5b",
88
+ "@rebasepro/common": "0.13.1-canary.g06dbe5b",
89
+ "@rebasepro/inference": "0.13.1-canary.g06dbe5b",
90
+ "@rebasepro/ui": "0.13.1-canary.g06dbe5b",
91
+ "@rebasepro/utils": "0.13.1-canary.g06dbe5b",
92
+ "@rebasepro/forms": "0.13.1-canary.g06dbe5b",
93
+ "@rebasepro/types": "0.13.1-canary.g06dbe5b"
94
94
  },
95
95
  "peerDependencies": {
96
96
  "react": ">=19.2.7",
@@ -107,6 +107,7 @@
107
107
  "@types/react-dom": "^19.2.3",
108
108
  "@types/react-measure": "^2.0.12",
109
109
  "@vitejs/plugin-react": "^6.0.4",
110
+ "fast-check": "^4.9.0",
110
111
  "jest": "^30.4.2",
111
112
  "react-router": "^8.3.0",
112
113
  "ts-jest": "^29.4.12",
@@ -155,6 +155,7 @@ export interface SerializableAdminRelationOptions extends SerializableAdminBaseO
155
155
  fixedFilter?: FilterValues<string>;
156
156
  includeId?: boolean;
157
157
  includeEntityLink?: boolean;
158
+ renderInForm?: boolean;
158
159
  }
159
160
 
160
161
  /** JSON-serializable version of `AdminArrayOptions`. */
@@ -17,6 +17,7 @@ import { CollectionsConfigController } from "../../types/config_controller";
17
17
  import { CollectionStudioView } from "./CollectionStudioView";
18
18
  import type { CollectionEditorExtensionProps } from "../../extensibility_types";
19
19
  import type { AdminCollection } from "@rebasepro/admin-types";
20
+ import { readStoredString, writeStoredString } from "@rebasepro/utils";
20
21
 
21
22
  export interface CollectionsStudioViewProps extends CollectionEditorExtensionProps {
22
23
  configController: CollectionsConfigController;
@@ -77,21 +78,18 @@ export function CollectionsStudioView({
77
78
  const collections = collectionsProp ?? configController.collections ?? [];
78
79
 
79
80
  // ── Sidebar sizing ──────────────────────────────────────────────────
81
+ // Checked, not just parsed: `parseFloat` answers NaN for anything it cannot
82
+ // read, and a NaN pane size lays the sidebar out to nothing — with the bad
83
+ // value still in storage on reload. `SplitListView.getSavedPanelSize` is the
84
+ // same read, done this way.
80
85
  const [sidebarSize, setSidebarSize] = useState(() => {
81
- try {
82
- const saved = localStorage.getItem("rebase_collections_editor_sidebar_size");
83
- return saved !== null ? parseFloat(saved) : 25;
84
- } catch (e) {
85
- return 25;
86
- }
86
+ const saved = readStoredString("rebase_collections_editor_sidebar_size");
87
+ const parsed = saved === null ? NaN : parseFloat(saved);
88
+ return Number.isFinite(parsed) && parsed > 0 && parsed < 100 ? parsed : 25;
87
89
  });
88
90
 
89
91
  useEffect(() => {
90
- try {
91
- localStorage.setItem("rebase_collections_editor_sidebar_size", sidebarSize.toString());
92
- } catch (e) {
93
- // ignore local storage error
94
- }
92
+ writeStoredString("rebase_collections_editor_sidebar_size", sidebarSize.toString());
95
93
  }, [sidebarSize]);
96
94
 
97
95
  return (
@@ -111,7 +111,7 @@ export function MapPropertyField({ disabled, getData, allowDataInference, proper
111
111
  position={"start"}
112
112
  size={"medium"}
113
113
  label={t("spread_children_as_columns")}
114
- onValueChange={(v) => setFieldValue("ui.spreadChildren", v)}
114
+ onValueChange={(v) => setFieldValue("admin.spreadChildren", v)}
115
115
  value={values.admin?.spreadChildren ?? false}
116
116
  />
117
117
  <FieldCaption>
@@ -7,8 +7,11 @@ export function AdvancedPropertyValidation({ disabled }: {
7
7
  disabled: boolean
8
8
  }) {
9
9
 
10
- const hideFromCollection = "ui.hideFromCollection";
11
- const readOnly = "ui.readOnly";
10
+ // `ui` was renamed to `admin` in 0.11. Nothing reads `ui.*` — the toggles did
11
+ // nothing — and the boot validator treats the old name as a removed key, so a
12
+ // save through here left a project that would not start.
13
+ const hideFromCollection = "admin.hideFromCollection";
14
+ const readOnly = "admin.readOnly";
12
15
 
13
16
  return (
14
17
 
@@ -202,13 +202,18 @@ collectionData });
202
202
  await request("/collection/delete", { collectionId: id });
203
203
  },
204
204
 
205
+ // The follow-up write carries exactly one key, and `partial` is what says
206
+ // so. Read as a whole-collection save it deletes everything it does not
207
+ // mention — `securityRules` first, which then falls back to the
208
+ // directory default and widens who can read the collection.
205
209
  saveProperty: async ({ path, propertyKey, property, newPropertiesOrder }: SavePropertyParams) => {
206
210
  await request("/property/save", { collectionId: path,
207
211
  propertyKey,
208
212
  propertyConfig: property });
209
213
  if (newPropertiesOrder) {
210
214
  await request("/collection/save", { collectionId: path,
211
- collectionData: { propertiesOrder: newPropertiesOrder } });
215
+ collectionData: { propertiesOrder: newPropertiesOrder },
216
+ partial: true });
212
217
  }
213
218
  },
214
219
  deleteProperty: async ({ path, propertyKey, newPropertiesOrder }: DeletePropertyParams) => {
@@ -216,14 +221,16 @@ collectionData: { propertiesOrder: newPropertiesOrder } });
216
221
  propertyKey });
217
222
  if (newPropertiesOrder) {
218
223
  await request("/collection/save", { collectionId: path,
219
- collectionData: { propertiesOrder: newPropertiesOrder } });
224
+ collectionData: { propertiesOrder: newPropertiesOrder },
225
+ partial: true });
220
226
  }
221
227
  },
222
228
 
223
229
  updatePropertiesOrder: async ({ collection, fullPath, newPropertiesOrder }: UpdatePropertiesOrderParams) => {
224
230
  const collectionId = (collection as AdminCollection & { id?: string }).id || fullPath.split("/").pop();
225
231
  await request("/collection/save", { collectionId,
226
- collectionData: { propertiesOrder: newPropertiesOrder } });
232
+ collectionData: { propertiesOrder: newPropertiesOrder },
233
+ partial: true });
227
234
  },
228
235
  updateKanbanColumnsOrder: async () => {
229
236
  // Kanban order mapping logic can be added later if needed natively.
@@ -0,0 +1,68 @@
1
+ import type { AdditionalFieldDelegate, RebaseContext } from "@rebasepro/admin-types";
2
+ import type { Entity } from "@rebasepro/types";
3
+ import React, { useEffect, useState } from "react";
4
+
5
+ export interface AdditionalFieldValueProps<M extends Record<string, unknown>> {
6
+ field: AdditionalFieldDelegate<M>;
7
+ entity: Entity<M>;
8
+ context: RebaseContext;
9
+ }
10
+
11
+ /**
12
+ * Renders an `additionalFields` entry that supplies a `value` rather than a
13
+ * `Builder`.
14
+ *
15
+ * `value` is typed `string | number | Promise<string | number>`, and the promise
16
+ * arm has been part of that type from the start — the CSV export awaits it
17
+ * correctly. Every *renderer* called `.toString()` on the result instead, so an
18
+ * async value drew the literal text `[object Promise]` in the cell: a shape the
19
+ * type promised, that worked in one of its two consumers, and failed silently in
20
+ * the other. That is what made the obvious port of a FireCMS column — one that
21
+ * fetches a title from a subcollection — look supported and not be.
22
+ *
23
+ * Three call sites had the same three lines copied between them (the table cell,
24
+ * the form and the read-only record), which is why the bug reached all three.
25
+ * Now there is one.
26
+ */
27
+ export function AdditionalFieldValue<M extends Record<string, unknown>>({
28
+ field,
29
+ entity,
30
+ context
31
+ }: AdditionalFieldValueProps<M>) {
32
+
33
+ const [resolved, setResolved] = useState<string | number | undefined>(() => {
34
+ const value = field.value?.({ entity, context });
35
+ // A synchronous value is available on the first render, so it never
36
+ // flashes empty on its way in.
37
+ return isPromise(value) ? undefined : value;
38
+ });
39
+
40
+ useEffect(() => {
41
+ const value = field.value?.({ entity, context });
42
+ if (!isPromise(value)) {
43
+ setResolved(value);
44
+ return;
45
+ }
46
+
47
+ // The row may scroll out, or the record change, before this lands.
48
+ let live = true;
49
+ value.then(
50
+ result => {
51
+ if (live) setResolved(result);
52
+ },
53
+ error => {
54
+ if (live) setResolved(undefined);
55
+ console.warn(`[rebase] Additional field "${field.key}" failed to resolve:`, error);
56
+ }
57
+ );
58
+ return () => {
59
+ live = false;
60
+ };
61
+ }, [field, entity, context]);
62
+
63
+ return <>{resolved === undefined || resolved === null ? null : String(resolved)}</>;
64
+ }
65
+
66
+ function isPromise(value: unknown): value is Promise<string | number | undefined> {
67
+ return typeof (value as Promise<unknown> | undefined)?.then === "function";
68
+ }
@@ -1,13 +1,20 @@
1
- import { Button, FilterXIcon, iconSize, Tooltip } from "@rebasepro/ui";
1
+ import { FilterXIcon, iconSize, IconButton, Tooltip } from "@rebasepro/ui";
2
2
  import { EntityTableController } from "@rebasepro/admin-types";
3
3
  import { useTranslation } from "@rebasepro/app";
4
4
 
5
5
  export function ClearFilterSortButton({
6
6
  tableController,
7
- enabled
7
+ enabled,
8
+ compact
8
9
  }: {
9
10
  enabled: boolean;
10
- tableController: EntityTableController
11
+ tableController: EntityTableController;
12
+ /**
13
+ * Toolbar showing icon-only controls (split view, or a layout too narrow
14
+ * for the filters button's label). Only decides the icon size, which
15
+ * follows that button — see `CollectionViewStartActions`.
16
+ */
17
+ compact?: boolean;
11
18
  }) {
12
19
  const { t } = useTranslation();
13
20
 
@@ -29,17 +36,19 @@ export function ClearFilterSortButton({
29
36
  }
30
37
  return (
31
38
  <Tooltip title={label}>
32
- <Button
39
+ {/* An `IconButton`, like every other icon-only control in this
40
+ toolbar. As a `Button` it was a rounded-md box with text
41
+ padding sitting between two circular icon buttons. */}
42
+ <IconButton
33
43
  size={"small"}
34
- variant={"text"}
35
44
  aria-label={label}
36
45
  onClick={() => {
37
46
  tableController.clearFilter?.();
38
47
  tableController.setSortBy?.(undefined);
39
48
  }}
40
49
  >
41
- <FilterXIcon size={iconSize.small}/>
42
- </Button>
50
+ <FilterXIcon size={compact ? iconSize.smallest : iconSize.small}/>
51
+ </IconButton>
43
52
  </Tooltip>
44
53
  );
45
54
  }
@@ -1,4 +1,5 @@
1
1
  import type { AdditionalFieldDelegate, AdminCollection } from "@rebasepro/admin-types";
2
+ import { AdditionalFieldValue } from "../AdditionalFieldValue";
2
3
  import React, { useCallback, useMemo, useRef } from "react";
3
4
  import { Entity, User, Property } from "@rebasepro/types";
4
5
  import { CollectionSize, RebaseContext } from "@rebasepro/admin-types";
@@ -16,7 +17,7 @@ import { propertiesToColumns } from "./column_utils";
16
17
  import { ErrorView } from "@rebasepro/app";
17
18
  import { SelectableTable } from "../SelectableTable/SelectableTable";
18
19
  import { cls } from "@rebasepro/ui";
19
- import { getRowHeight } from "@rebasepro/app";
20
+ import { getRowHeight, isDisabled } from "@rebasepro/app";
20
21
  import { getValueInPath } from "@rebasepro/utils";
21
22
 
22
23
  /**
@@ -146,7 +147,7 @@ export const CollectionTableBinding = function CollectionTableBinding<M extends
146
147
  propertyKey,
147
148
  entity
148
149
  }) ?? columnCustom?.resolvedProperty;
149
- if (!property?.admin?.disabled) {
150
+ if (!property || !isDisabled(property)) {
150
151
  disabled = false;
151
152
  }
152
153
 
@@ -214,12 +215,10 @@ export const CollectionTableBinding = function CollectionTableBinding<M extends
214
215
 
215
216
  const child: React.ReactNode = Builder
216
217
  ? <Builder entity={entity} context={context}/>
217
- : <>
218
- {additionalField.value?.({
219
- entity,
220
- context: context as RebaseContext
221
- })?.toString()}
222
- </>;
218
+ : <AdditionalFieldValue
219
+ field={additionalField}
220
+ entity={entity}
221
+ context={context as RebaseContext}/>;
223
222
 
224
223
  return (
225
224
  <EntityTableCell
@@ -18,7 +18,7 @@ import { EntityTableCellActions } from "./internal/EntityTableCellActions";
18
18
  import { useSelectableTableController } from "../SelectableTable/SelectableTableContext";
19
19
  import { useClearRestoreValue } from "../../form/useClearRestoreValue";
20
20
  import { getRowHeight } from "@rebasepro/app";
21
- import { isReadOnly } from "@rebasepro/app";
21
+ import { isDisabled, isReadOnly } from "@rebasepro/app";
22
22
  import { TableRelationField } from "./fields/TableRelationField";
23
23
  import { TableRelationSelectorField } from "./fields/TableRelationSelectorField";
24
24
 
@@ -95,7 +95,7 @@ export const PropertyTableCell = React.memo<PropertyTableCellProps<any>>(
95
95
  const customPreview = Boolean(property.admin?.Preview);
96
96
  const readOnlyProperty = isReadOnly(property);
97
97
  const disabledTooltip: string | undefined = typeof property.admin?.disabled === "object" ? property.admin?.disabled.disabledMessage : undefined;
98
- const disabled = readonly || disabledProp || Boolean(property.admin?.disabled);
98
+ const disabled = readonly || disabledProp || isDisabled(property);
99
99
 
100
100
  const validation = useMemo(() => mapPropertyToZod({
101
101
  property,
@@ -39,15 +39,24 @@ export function propertiesToColumns<M extends Record<string, unknown>>({ propert
39
39
  const disabledFilter = Boolean(fixedFilter);
40
40
  return Object.entries<Property>(properties)
41
41
  .flatMap(([key, property]) => getColumnKeysForProperty(property, key))
42
- .map(({
42
+ .flatMap(({
43
43
  key,
44
44
  disabled
45
45
  }) => {
46
- const property = getResolvedPropertyInPath(properties, key) as Property;
47
- if (!property)
48
- throw Error("Internal error: no property found in path " + key);
46
+ const property = getResolvedPropertyInPath(properties, key) as Property | undefined;
47
+ // One unresolvable key used to throw, and this runs inside the
48
+ // memo that builds the table's columns — so a single bad column
49
+ // took down the header, the rows and the empty state together,
50
+ // leaving a blank pane with nothing to attribute it to. A column
51
+ // that cannot be resolved is one column the table cannot offer;
52
+ // say so and carry the rest. Same choice, and now the same
53
+ // behaviour, as `getSortablePropertyOptions` below.
54
+ if (!property) {
55
+ console.warn(`No property found in path "${key}" — skipping that column.`);
56
+ return [];
57
+ }
49
58
  const filterable = filterableProperty(property, false, engine);
50
- return {
59
+ return [{
51
60
  key: key as string,
52
61
  align: getTableCellAlignment(property),
53
62
  icon: getIconForProperty(property, "small"),
@@ -63,7 +72,7 @@ export function propertiesToColumns<M extends Record<string, unknown>>({ propert
63
72
  AdditionalHeaderWidget: AdditionalHeaderWidget
64
73
  ? ({ onHover }: { onHover: boolean }) => <AdditionalHeaderWidget property={property} propertyKey={key} onHover={onHover}/>
65
74
  : undefined
66
- } satisfies VirtualTableColumn;
75
+ } satisfies VirtualTableColumn];
67
76
  });
68
77
  }
69
78
 
@@ -16,7 +16,7 @@ import { getPropertyInPath } from "../../../../util/property_utils";
16
16
  import { PropertyFieldBinding, zodToFormErrors } from "../../../../form";
17
17
  import { useAuthController, useCustomizationController, useData, useRebaseContext } from "@rebasepro/app";
18
18
  import type { OnCellValueChangeParams } from "@rebasepro/app";
19
- import { isReadOnly } from "@rebasepro/app";
19
+ import { isDisabled, isReadOnly } from "@rebasepro/app";
20
20
  import type { AdminCollection } from "@rebasepro/admin-types";
21
21
 
22
22
  interface PopupFormFieldProps<M extends Record<string, unknown>> {
@@ -295,7 +295,7 @@ export function PopupFormFieldInternal<M extends Record<string, unknown>>({
295
295
  const fieldProps: PropertyFieldBindingProps<M> | undefined = propertyKey && property
296
296
  ? {
297
297
  propertyKey: propertyKey as string,
298
- disabled: isSubmitting || isReadOnly(property) || !!property.admin?.disabled,
298
+ disabled: isSubmitting || isReadOnly(property) || isDisabled(property),
299
299
  property,
300
300
  includeDescription: false,
301
301
  underlyingValueHasChanged: false,
@@ -240,7 +240,13 @@ export function getTableBindingForProperty(
240
240
  Component: RelationDialogBindingComponent,
241
241
  allowScroll: false
242
242
  };
243
- } else {
243
+ } else if (selected) {
244
+ // Gated on `selected`, like the number and string editors above.
245
+ // This one mounts a live picker against the *target* collection,
246
+ // and the cell is rendered once per visible row: ungated, opening
247
+ // a table with one relation column fired a request — or, on the
248
+ // realtime path, a subscription — per row, for data nobody had
249
+ // asked to see. Unselected cells fall through to PropertyPreview.
244
250
  return {
245
251
  Component: RelationSelectorBindingComponent,
246
252
  allowScroll: false
@@ -6,7 +6,8 @@ import { PropertyPreview } from "../../preview";
6
6
  import { useAuthController, useCustomizationController } from "@rebasepro/app";
7
7
  import { IconForView } from "@rebasepro/app";
8
8
  import { BoardItemViewProps } from "@rebasepro/ui";
9
- import { useCollectionSlotKeys, resolveEntitySlots } from "./usePreviewSlots";
9
+ import { useCollectionSlotKeys, useEntitySlots } from "./usePreviewSlots";
10
+ import { SlotValue, TagChips } from "./SlotValue";
10
11
  import type { AdminCollection } from "@rebasepro/admin-types";
11
12
 
12
13
  export type BoardCardBindingProps<M extends Record<string, unknown> = Record<string, unknown>> = BoardItemViewProps<Entity<M>> & {
@@ -46,13 +47,10 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
46
47
  customizationController.propertyConfigs
47
48
  );
48
49
 
49
- const slots = useMemo(
50
- () => resolveEntitySlots(
51
- entity as Entity<Record<string, unknown>>,
52
- collection as AdminCollection<Record<string, unknown>>,
53
- slotKeys
54
- ),
55
- [entity, collection, slotKeys]
50
+ const slots = useEntitySlots(
51
+ entity as Entity<Record<string, unknown>>,
52
+ collection as AdminCollection<Record<string, unknown>>,
53
+ slotKeys
56
54
  );
57
55
 
58
56
  const handleClick = useCallback((e: React.MouseEvent) => {
@@ -126,13 +124,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
126
124
  selectionEnabled && "group-hover/card:opacity-30",
127
125
  selected && "opacity-0"
128
126
  )}>
129
- <PropertyPreview
130
- property={slots.image.property}
131
- propertyKey={slots.image.propertyKey}
132
- size="small"
133
- value={slots.image.value}
134
- fill={true}
135
- />
127
+ <SlotValue slot={slots.image} size="small" fill={true}/>
136
128
  </div>
137
129
  ) : (
138
130
  <div className={cls(
@@ -177,12 +169,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
177
169
  {/* Title slot */}
178
170
  <div className="line-clamp-2 text-sm font-medium">
179
171
  {slots.title ? (
180
- <PropertyPreview
181
- propertyKey={slots.title.propertyKey}
182
- value={slots.title.value}
183
- property={slots.title.property}
184
- size="small"
185
- />
172
+ <SlotValue slot={slots.title} size="small"/>
186
173
  ) : (
187
174
  <span className="text-surface-500">{entity.id}</span>
188
175
  )}
@@ -193,12 +180,7 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
193
180
  {typeof slots.subtitle.value === "string" ? (
194
181
  <Markdown source={slots.subtitle.value} size="small" />
195
182
  ) : (
196
- <PropertyPreview
197
- propertyKey={slots.subtitle.propertyKey}
198
- value={slots.subtitle.value}
199
- property={slots.subtitle.property}
200
- size="small"
201
- />
183
+ <SlotValue slot={slots.subtitle} size="small"/>
202
184
  )}
203
185
  </div>
204
186
  ) : (
@@ -228,6 +210,23 @@ function BoardCardBindingInner<M extends Record<string, unknown> = Record<string
228
210
  )}
229
211
  </div>
230
212
  )}
213
+
214
+ {/* Tags slot */}
215
+ {slots.tags && (
216
+ <div className="flex items-center gap-1 mt-1 flex-wrap">
217
+ <TagChips slot={slots.tags} max={3}/>
218
+ </div>
219
+ )}
220
+
221
+ {/* Date slot — a board card is a list row without the image, and
222
+ the row shows it. The status is deliberately not repeated
223
+ here: on a board grouped by it, the column header is already
224
+ saying it. */}
225
+ {slots.date && (
226
+ <div className="text-[10px] text-surface-500 dark:text-surface-400 mt-1">
227
+ {slots.date.formatted}
228
+ </div>
229
+ )}
231
230
  </div>
232
231
  </div>
233
232
  </div>
@@ -1,6 +1,6 @@
1
1
 
2
2
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { Entity, EnumValueConfig } from "@rebasepro/types";
3
+ import { Entity, EnumValueConfig, MAX_LIST_LIMIT } from "@rebasepro/types";
4
4
  import { EntityTableController, SelectionController, AdminCollection } from "@rebasepro/admin-types";
5
5
  import { BoardCardBinding } from "./BoardCardBinding";
6
6
  import {
@@ -376,13 +376,19 @@ parentEntityIds,
376
376
  setBackfillLoading(true);
377
377
 
378
378
  try {
379
- // Fetch ALL documents from collection (not relying on loaded
380
- // entities). No `orderBy`: with no usable order key there is
381
- // nothing to order by, and this is the same query the columns
382
- // themselves run, so the keys are handed out in the order the
383
- // board is showing — which is what the dialog promises.
379
+ // Fetch the documents to key (not relying on loaded entities). No
380
+ // `orderBy`: with no usable order key there is nothing to order by,
381
+ // and this is the same query the columns themselves run, so the
382
+ // keys are handed out in the order the board is showing — which is
383
+ // what the dialog promises.
384
+ //
385
+ // `MAX_LIST_LIMIT`, not a made-up 10 000: that is the most a single
386
+ // read serves, and asking for more is now refused outright rather
387
+ // than quietly answered with the first thousand. A board past the
388
+ // ceiling still gets only its first page backfilled — the same rows
389
+ // as before this became explicit — and wants a paged walk.
384
390
  const allDocsRes = await dataClient.collection(fullPath).find({
385
- limit: 10000 // Fetch all
391
+ limit: MAX_LIST_LIMIT
386
392
  });
387
393
  const allDocs = allDocsRes.data as Entity<M>[];
388
394
 
@@ -145,6 +145,7 @@ export function CollectionCardViewBinding<M extends Record<string, unknown> = Re
145
145
  key={`${entity.path}_${entity.id}`}
146
146
  entity={entity}
147
147
  collection={collection}
148
+ searchString={tableController.searchString}
148
149
  onClick={onClick as any}
149
150
  selected={selected}
150
151
  highlighted={highlighted}
@@ -152,7 +153,7 @@ export function CollectionCardViewBinding<M extends Record<string, unknown> = Re
152
153
  selectionEnabled={selectionEnabled}
153
154
  size={size}
154
155
  />
155
- ), [collection, selectionEnabled, size, handleRowSelectionChange, ResolvedEntityCard])}
156
+ ), [collection, selectionEnabled, size, handleRowSelectionChange, ResolvedEntityCard, tableController.searchString])}
156
157
  />
157
158
  );
158
159
  }