@rebasepro/core 0.5.0 → 0.6.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.
Files changed (66) hide show
  1. package/dist/components/AIIcon.d.ts +3 -2
  2. package/dist/components/ConfirmationDialog.d.ts +1 -1
  3. package/dist/components/Debug/UIReferenceView.d.ts +13 -1
  4. package/dist/components/Debug/UIStyleGuide.d.ts +2 -1
  5. package/dist/components/ErrorTooltip.d.ts +2 -1
  6. package/dist/components/LanguageToggle.d.ts +2 -1
  7. package/dist/components/LoginView/LoginView.d.ts +2 -2
  8. package/dist/components/NotFoundPage.d.ts +2 -1
  9. package/dist/components/RebaseLogo.d.ts +1 -1
  10. package/dist/components/SchemaDriftBanner.d.ts +27 -0
  11. package/dist/components/UnsavedChangesDialog.d.ts +1 -1
  12. package/dist/components/UserDisplay.d.ts +1 -1
  13. package/dist/components/UserSelectPopover.d.ts +2 -1
  14. package/dist/components/UserSettingsView.d.ts +2 -1
  15. package/dist/components/index.d.ts +1 -1
  16. package/dist/contexts/ComponentOverrideContext.d.ts +42 -0
  17. package/dist/contexts/RebaseClientInstanceContext.d.ts +1 -1
  18. package/dist/contexts/index.d.ts +1 -1
  19. package/dist/core/Rebase.d.ts +2 -1
  20. package/dist/core/RebaseProps.d.ts +23 -22
  21. package/dist/core/RebaseRouter.d.ts +1 -1
  22. package/dist/core/RebaseRoutes.d.ts +1 -1
  23. package/dist/hooks/ApiConfigContext.d.ts +1 -1
  24. package/dist/hooks/index.d.ts +1 -2
  25. package/dist/hooks/useComponentOverride.d.ts +32 -0
  26. package/dist/hooks/useRebaseRegistry.d.ts +1 -1
  27. package/dist/hooks/useStudioBridge.d.ts +4 -16
  28. package/dist/i18n/RebaseI18nProvider.d.ts +2 -2
  29. package/dist/index.es.js +12748 -15004
  30. package/dist/index.es.js.map +1 -1
  31. package/dist/index.umd.js +13062 -15144
  32. package/dist/index.umd.js.map +1 -1
  33. package/dist/util/icons.d.ts +2 -2
  34. package/dist/vitePlugin.js +4 -1
  35. package/package.json +21 -21
  36. package/src/components/Debug/UIReferenceView.tsx +109 -201
  37. package/src/components/ErrorView.tsx +16 -6
  38. package/src/components/LoginView/LoginView.tsx +15 -17
  39. package/src/components/SchemaDriftBanner.tsx +102 -0
  40. package/src/components/common/useDataTableController.tsx +1 -1
  41. package/src/components/index.tsx +1 -1
  42. package/src/contexts/ComponentOverrideContext.tsx +81 -0
  43. package/src/contexts/RebaseClientInstanceContext.tsx +1 -1
  44. package/src/contexts/index.ts +1 -1
  45. package/src/core/Rebase.tsx +16 -18
  46. package/src/core/RebaseProps.tsx +24 -25
  47. package/src/hooks/data/useCollectionFetch.tsx +10 -2
  48. package/src/hooks/data/useRelationSelector.tsx +4 -2
  49. package/src/hooks/index.tsx +1 -3
  50. package/src/hooks/useCollapsedGroups.ts +2 -1
  51. package/src/hooks/useComponentOverride.tsx +59 -0
  52. package/src/hooks/useRebaseContext.tsx +3 -5
  53. package/src/hooks/useResolvedComponent.tsx +1 -1
  54. package/src/hooks/useStudioBridge.tsx +5 -13
  55. package/src/locales/en.ts +3 -0
  56. package/src/util/entity_cache.ts +0 -2
  57. package/src/util/previews.ts +1 -1
  58. package/src/vitePlugin.ts +2 -1
  59. package/dist/components/BootstrapAdminBanner.d.ts +0 -4
  60. package/dist/contexts/InternalUserManagementContext.d.ts +0 -3
  61. package/dist/hooks/data/useUserSelector.d.ts +0 -31
  62. package/dist/hooks/useInternalUserManagementController.d.ts +0 -12
  63. package/src/components/BootstrapAdminBanner.tsx +0 -75
  64. package/src/contexts/InternalUserManagementContext.tsx +0 -4
  65. package/src/hooks/data/useUserSelector.tsx +0 -157
  66. package/src/hooks/useInternalUserManagementController.tsx +0 -17
@@ -12,9 +12,9 @@ export type IconViewProps = {
12
12
  group?: string;
13
13
  icon?: string | React.ReactNode;
14
14
  };
15
- export declare const IconForView: React.NamedExoticComponent<{
15
+ export declare const IconForView: React.MemoExoticComponent<({ collectionOrView, className, color, size }: {
16
16
  collectionOrView?: IconViewProps;
17
17
  color?: IconColor;
18
18
  className?: string;
19
19
  size?: "smallest" | "small" | "medium" | "large" | number;
20
- }>;
20
+ }) => React.ReactElement>;
@@ -62,7 +62,10 @@ function rebaseCollectionsPlugin(options) {
62
62
  return `${prefix}{ __rebaseLazy: true, load: () => import(${quote}${importPath}${quote}) }`;
63
63
  }
64
64
  );
65
- return { code: transformed, map: null };
65
+ return {
66
+ code: transformed,
67
+ map: null
68
+ };
66
69
  }
67
70
  };
68
71
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/core",
3
3
  "type": "module",
4
- "version": "0.5.0",
4
+ "version": "0.6.1",
5
5
  "description": "Rebase core — framework-agnostic runtime for data-driven admin panels",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -48,15 +48,15 @@
48
48
  "dependencies": {
49
49
  "compressorjs": "^1.3.0",
50
50
  "fast-equals": "6.0.0",
51
- "fuse.js": "^7.3.0",
52
- "i18next": "^23.16.8",
51
+ "fuse.js": "^7.4.2",
52
+ "i18next": "^26.3.1",
53
53
  "notistack": "^3.0.2",
54
- "react-i18next": "^14.1.3",
55
- "@rebasepro/common": "0.5.0",
56
- "@rebasepro/formex": "0.5.0",
57
- "@rebasepro/types": "0.5.0",
58
- "@rebasepro/ui": "0.5.0",
59
- "@rebasepro/utils": "0.5.0"
54
+ "react-i18next": "^17.0.8",
55
+ "@rebasepro/common": "0.6.1",
56
+ "@rebasepro/types": "0.6.1",
57
+ "@rebasepro/utils": "0.6.1",
58
+ "@rebasepro/ui": "0.6.1",
59
+ "@rebasepro/formex": "0.6.1"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "react": ">=19.0.0",
@@ -68,22 +68,22 @@
68
68
  "@jest/globals": "^30.4.1",
69
69
  "@testing-library/react": "^16.3.2",
70
70
  "@testing-library/user-event": "^14.6.1",
71
- "@types/jest": "^29.5.14",
72
- "@types/node": "^20.19.41",
73
- "@types/react": "^19.2.15",
71
+ "@types/jest": "^30.0.0",
72
+ "@types/node": "^25.9.3",
73
+ "@types/react": "^19.2.17",
74
74
  "@types/react-dom": "^19.2.3",
75
75
  "@types/react-measure": "^2.0.12",
76
- "@vitejs/plugin-react": "^4.7.0",
76
+ "@vitejs/plugin-react": "^6.0.2",
77
77
  "babel-plugin-react-compiler": "beta",
78
- "cross-env": "^7.0.3",
79
- "esbuild": "^0.25.12",
80
- "jest": "^29.7.0",
78
+ "cross-env": "^10.1.0",
79
+ "esbuild": "^0.28.1",
80
+ "jest": "^30.4.2",
81
81
  "jest-environment-jsdom": "^30.4.1",
82
- "react-router-dom": "^7.15.1",
83
- "ts-jest": "^29.4.10",
84
- "tsd": "^0.31.2",
85
- "typescript": "^5.9.3",
86
- "vite": "^7.3.3"
82
+ "react-router-dom": "^7.17.0",
83
+ "ts-jest": "^29.4.11",
84
+ "tsd": "^0.33.0",
85
+ "typescript": "^6.0.3",
86
+ "vite": "^8.0.16"
87
87
  },
88
88
  "files": [
89
89
  "dist",
@@ -13,7 +13,9 @@
13
13
  import React, { useState } from "react";
14
14
  import {
15
15
  Alert,
16
+ AlertCircleIcon,
16
17
  Avatar,
18
+ AppWindow,
17
19
  BooleanSwitch,
18
20
  Button,
19
21
  Checkbox,
@@ -21,9 +23,14 @@ import {
21
23
  ChevronsLeftIcon,
22
24
  ChevronsRightIcon,
23
25
  Chip,
26
+ CircleUserIcon,
24
27
  CircularProgress,
25
28
  cls,
29
+ ColumnsIcon,
26
30
  defaultBorderMixin,
31
+ FileIcon,
32
+ FileTextIcon,
33
+ FilterChip,
27
34
  FilterIcon,
28
35
  FolderIcon,
29
36
  IconButton,
@@ -38,6 +45,7 @@ import {
38
45
  MoonIcon,
39
46
  MultiSelect,
40
47
  MultiSelectItem,
48
+ PanelLeftIcon,
41
49
  PencilIcon,
42
50
  PlusIcon,
43
51
  SearchBar,
@@ -59,54 +67,45 @@ import {
59
67
  TextField,
60
68
  Tooltip,
61
69
  Trash2Icon,
70
+ TypeIcon,
62
71
  Typography,
63
72
  UserIcon
64
73
  } from "@rebasepro/ui";
65
74
  import { RebaseLogo } from "../RebaseLogo";
66
75
 
67
76
  const SECTIONS = [
68
- { id: "drawer",
69
- label: "Drawer" },
70
- { id: "appbar",
71
- label: "App Bar" },
72
- { id: "tabs",
73
- label: "Tabs" },
74
- { id: "editor-sidebar",
75
- label: "Editor Sidebar" },
76
- { id: "empty-states",
77
- label: "Empty States" },
78
- { id: "typography",
79
- label: "Typography" },
80
- { id: "buttons",
81
- label: "Buttons" },
82
- { id: "inputs",
83
- label: "Form Inputs" },
84
- { id: "chips-alerts",
85
- label: "Chips & Alerts" },
86
- { id: "users",
87
- label: "Users View" },
88
- { id: "user-dialog",
89
- label: "User Dialog" },
90
- { id: "roles",
91
- label: "Roles View" },
92
- { id: "role-dialog",
93
- label: "Role Dialog" }
77
+ { id: "drawer", label: "Drawer", icon: PanelLeftIcon },
78
+ { id: "appbar", label: "App Bar", icon: AppWindow },
79
+ { id: "tabs", label: "Tabs", icon: ListIcon },
80
+ { id: "editor-sidebar", label: "Editor Sidebar", icon: ColumnsIcon },
81
+ { id: "empty-states", label: "Empty States", icon: FileIcon },
82
+ { id: "typography", label: "Typography", icon: TypeIcon },
83
+ { id: "buttons", label: "Buttons", icon: PlusIcon },
84
+ { id: "inputs", label: "Form Inputs", icon: FileTextIcon },
85
+ { id: "chips-alerts", label: "Chips & Alerts", icon: AlertCircleIcon },
86
+ { id: "users", label: "Users View", icon: UserIcon },
87
+ { id: "user-dialog", label: "User Dialog", icon: CircleUserIcon }
94
88
  ];
95
89
 
96
90
  export function UIReferenceView() {
97
91
  const [activeSection, setActiveSection] = useState("drawer");
92
+ const scrollContainerRef = React.useRef<HTMLDivElement>(null);
98
93
 
99
94
  const scrollTo = (id: string) => {
100
- document.getElementById(id)?.scrollIntoView({ behavior: "smooth",
101
- block: "start" });
95
+ const el = document.getElementById(id);
96
+ const container = scrollContainerRef.current;
97
+ if (el && container) {
98
+ const offsetTop = el.offsetTop - container.offsetTop;
99
+ container.scrollTo({ top: offsetTop, behavior: "smooth" });
100
+ }
102
101
  setActiveSection(id);
103
102
  };
104
103
 
105
104
  return (
106
- <div className="flex h-full w-full overflow-hidden">
105
+ <div className="flex w-full">
107
106
 
108
107
  {/* ── Sidebar nav (same structure as DefaultDrawer) ─────────────── */}
109
- <div className={cls("flex flex-col h-full relative grow-0 shrink-0 w-[200px] border-r", defaultBorderMixin)}>
108
+ <div className={cls("flex flex-col sticky top-0 h-screen grow-0 shrink-0 w-[200px] border-r", defaultBorderMixin)}>
110
109
  {/* DrawerLogo */}
111
110
  <div className="flex flex-row items-center shrink-0 pt-4 pb-2 px-2">
112
111
  <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px]">
@@ -127,28 +126,31 @@ block: "start" });
127
126
  </div>
128
127
  {/* Nav items — from DrawerNavigationItem */}
129
128
  <div className="overflow-hidden bg-surface-50 dark:bg-surface-950/30 rounded-b-lg">
130
- {SECTIONS.map(s => (
131
- <div key={s.id}>
132
- <div
133
- onClick={() => scrollTo(s.id)}
134
- className={cls(
135
- "rounded-lg truncate",
136
- "hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75 text-text-primary dark:text-surface-200 hover:text-surface-900 dark:hover:text-white",
137
- "flex flex-row items-center",
138
- "pr-4 h-10",
139
- "font-semibold text-xs cursor-pointer",
140
- activeSection === s.id ? "bg-surface-accent-200/60 dark:bg-surface-950 dark:bg-opacity-50" : ""
141
- )}
142
- >
143
- <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px] text-text-secondary dark:text-text-secondary-dark">
144
- <SettingsIcon size={iconSize.small}/>
145
- </div>
146
- <div className="text-text-primary dark:text-surface-200 opacity-100 font-inherit truncate space-x-2">
147
- {s.label.toUpperCase()}
129
+ {SECTIONS.map(s => {
130
+ const IconComponent = s.icon;
131
+ return (
132
+ <div key={s.id}>
133
+ <div
134
+ onClick={() => scrollTo(s.id)}
135
+ className={cls(
136
+ "rounded-lg truncate group/nav",
137
+ "hover:bg-primary/5 dark:hover:bg-primary/5 text-text-primary dark:text-surface-200 hover:text-surface-900 dark:hover:text-white",
138
+ "flex flex-row items-center",
139
+ "pr-4 h-10",
140
+ "font-medium text-xs cursor-pointer",
141
+ activeSection === s.id ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary" : ""
142
+ )}
143
+ >
144
+ <div className={cls("shrink-0 flex items-center justify-center w-[56px] h-[40px] text-surface-500 dark:text-text-secondary-dark transition-colors duration-150 group-hover/nav:text-primary", activeSection === s.id && "text-primary dark:text-primary")}>
145
+ <IconComponent size={iconSize.small}/>
146
+ </div>
147
+ <div className="text-text-primary dark:text-surface-200 opacity-100 font-inherit truncate space-x-2">
148
+ {s.label.toUpperCase()}
149
+ </div>
148
150
  </div>
149
151
  </div>
150
- </div>
151
- ))}
152
+ );
153
+ })}
152
154
  </div>
153
155
  </div>
154
156
  </div>
@@ -171,8 +173,8 @@ block: "start" });
171
173
  </div>
172
174
  </div>
173
175
 
174
- {/* ── Main scroll area ───────────────────────────────────────────── */}
175
- <div className="flex-1 overflow-y-auto">
176
+ {/* ── Main content area ───────────────────────────────────────────── */}
177
+ <div ref={scrollContainerRef} className="flex-1">
176
178
 
177
179
  {/* ═══════════════════════════════════════════════
178
180
  SECTION: Drawer
@@ -187,7 +189,7 @@ block: "start" });
187
189
  {/* Collapsed — exact markup from DefaultDrawer + DrawerNavigationItem */}
188
190
  <div>
189
191
  <Typography variant="caption" color="secondary" className="block mb-1">Collapsed (72px)</Typography>
190
- <div className={cls("flex flex-col h-72 relative w-[72px] border rounded-lg overflow-hidden", defaultBorderMixin)}>
192
+ <div className={cls("flex flex-col h-72 relative w-[72px] border rounded-lg overflow-hidden bg-white dark:bg-surface-900", defaultBorderMixin)}>
191
193
  <div className="flex flex-row items-center shrink-0 pt-4 pb-2 px-2">
192
194
  <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px]">
193
195
  <RebaseLogo width="28px" height="28px"/>
@@ -197,7 +199,7 @@ block: "start" });
197
199
  <div className="my-2 mx-2 flex flex-col">
198
200
  <div className="overflow-hidden rounded-lg bg-surface-50 dark:bg-surface-950/30">
199
201
  {[<FolderIcon key="folder" size={iconSize.small}/>, <UserIcon key="user" size={iconSize.small}/>, <TagIcon key="tag" size={iconSize.small}/>].map((icon, i) => (
200
- <div key={i} className="rounded-lg truncate hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75 flex flex-row items-center h-10">
202
+ <div key={i} className="rounded-lg truncate hover:bg-primary/5 dark:hover:bg-primary/5 flex flex-row items-center h-10">
201
203
  <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px] text-text-secondary dark:text-text-secondary-dark">
202
204
  {icon}
203
205
  </div>
@@ -219,7 +221,7 @@ block: "start" });
219
221
  {/* Expanded — exact markup from DefaultDrawer + DrawerNavigationGroup + DrawerNavigationItem */}
220
222
  <div>
221
223
  <Typography variant="caption" color="secondary" className="block mb-1">Expanded (280px)</Typography>
222
- <div className={cls("flex flex-col h-72 relative w-[280px] border rounded-lg overflow-hidden", defaultBorderMixin)}>
224
+ <div className={cls("flex flex-col h-72 relative w-[280px] border rounded-lg overflow-hidden bg-white dark:bg-surface-900", defaultBorderMixin)}>
223
225
  {/* DrawerLogo */}
224
226
  <div className="flex flex-row items-center shrink-0 pt-4 pb-2 px-2">
225
227
  <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px]">
@@ -249,10 +251,10 @@ icon: <TagIcon size={iconSize.small}/>,
249
251
  active: false }
250
252
  ].map(({ label, icon, active }) => (
251
253
  <div key={label} className={cls(
252
- "rounded-lg truncate hover:bg-surface-accent-300/75 dark:hover:bg-surface-accent-800/75 text-text-primary dark:text-surface-200 hover:text-surface-900 dark:hover:text-white flex flex-row items-center pr-4 h-10 font-semibold text-xs cursor-pointer",
253
- active ? "bg-surface-accent-200/60 dark:bg-surface-950 dark:bg-opacity-50" : ""
254
+ "rounded-lg truncate hover:bg-primary/5 dark:hover:bg-primary/5 text-text-primary dark:text-surface-200 hover:text-surface-900 dark:hover:text-white flex flex-row items-center pr-4 h-10 font-medium text-xs cursor-pointer",
255
+ active ? "bg-primary/8 dark:bg-primary/10 text-primary dark:text-primary" : ""
254
256
  )}>
255
- <div className="shrink-0 flex items-center justify-center w-[56px] h-[40px] text-text-secondary dark:text-text-secondary-dark">
257
+ <div className={cls("shrink-0 flex items-center justify-center w-[56px] h-[40px] transition-colors duration-150", active ? "text-primary dark:text-primary" : "text-surface-500 dark:text-text-secondary-dark")}>
256
258
  {icon}
257
259
  </div>
258
260
  <div className="text-text-primary dark:text-surface-200 font-inherit truncate">
@@ -623,15 +625,31 @@ selected: true }, { name: "Tags" }].map(c => (
623
625
  </div>
624
626
  </div>
625
627
  <div>
626
- <Typography variant="caption" color="secondary" className="block mb-2 font-mono">IconButton</Typography>
628
+ <Typography variant="caption" color="secondary" className="block mb-2 font-mono">IconButton — sizes</Typography>
627
629
  <div className="flex flex-wrap gap-3 items-center">
628
- {(["primary", "secondary", "inherit"] as const).map(c => (
629
- <IconButton key={c} color={c}><PencilIcon/></IconButton>
630
- ))}
631
- {(["small", "medium", "large"] as const).map(s => (
632
- <IconButton key={s} size={s}><Trash2Icon/></IconButton>
630
+ {([
631
+ { s: "smallest" as const, icon: <PencilIcon size={14}/> },
632
+ { s: "small" as const, icon: <PencilIcon size={16}/> },
633
+ { s: "medium" as const, icon: <PencilIcon size={20}/> },
634
+ { s: "large" as const, icon: <PencilIcon size={24}/> }
635
+ ]).map(({ s, icon }) => (
636
+ <div key={s} className="flex flex-col items-center gap-1">
637
+ <IconButton size={s}>{icon}</IconButton>
638
+ <Typography variant="caption" color="secondary">{s}</Typography>
639
+ </div>
633
640
  ))}
634
- <IconButton disabled><PlusIcon/></IconButton>
641
+ <div className="flex flex-col items-center gap-1">
642
+ <IconButton disabled><Trash2Icon size={20}/></IconButton>
643
+ <Typography variant="caption" color="secondary">disabled</Typography>
644
+ </div>
645
+ <div className="flex flex-col items-center gap-1">
646
+ <IconButton variant="filled"><PlusIcon size={20}/></IconButton>
647
+ <Typography variant="caption" color="secondary">filled</Typography>
648
+ </div>
649
+ <div className="flex flex-col items-center gap-1">
650
+ <IconButton shape="square"><SettingsIcon size={20}/></IconButton>
651
+ <Typography variant="caption" color="secondary">square</Typography>
652
+ </div>
635
653
  </div>
636
654
  </div>
637
655
  <div>
@@ -706,17 +724,37 @@ selected: true }, { name: "Tags" }].map(c => (
706
724
  SECTION: Chips & Alerts
707
725
  ═══════════════════════════════════════════════ */}
708
726
  <SectionBlock id="chips-alerts" title="Chips & Alerts">
709
- <Typography variant="caption" color="secondary" className="block mb-2 font-mono">Chip — colorScheme × size</Typography>
727
+ <Typography variant="caption" color="secondary" className="block mb-2 font-mono">Chip — colorScheme</Typography>
710
728
  <div className="flex flex-wrap gap-2 mb-4">
711
- {(["grayLight", "grayDark", "redLight", "redDark", "blueDark", "blueLight", "greenDark", "greenLight", "yellowLight", "yellowDark", "orangeLight", "purpleDark", "pinkLight"] as const).map(s => (
712
- <Chip key={s} colorScheme={s} size="small">{s}</Chip>
729
+ {(["blue", "teal", "red", "green", "yellow", "orange", "purple", "pink", "cyan", "indigo", "violet", "fuchsia", "rose", "emerald", "gray"] as const).map(s => (
730
+ <Chip key={s} colorScheme={s}>{s}</Chip>
713
731
  ))}
714
732
  </div>
715
- <div className="flex flex-wrap gap-2 mb-6">
716
- {(["smallest", "small", "medium"] as const).map(sz => (
717
- <Chip key={sz} colorScheme="blueDark" size={sz}>{sz}</Chip>
733
+ <Typography variant="caption" color="secondary" className="block mb-2 font-mono">Chip — sizes</Typography>
734
+ <div className="flex flex-wrap gap-2 items-center mb-4">
735
+ {(["smallest", "small", "medium", "large"] as const).map(sz => (
736
+ <Chip key={sz} colorScheme="blue" size={sz}>{sz}</Chip>
718
737
  ))}
719
738
  </div>
739
+ <Typography variant="caption" color="secondary" className="block mb-2 font-mono">Chip — outlined, error, clickable, icon</Typography>
740
+ <div className="flex flex-wrap gap-2 items-center mb-4">
741
+ <Chip colorScheme="red" outlined>Outlined Red</Chip>
742
+ <Chip colorScheme="blue" outlined>Outlined Blue</Chip>
743
+ <Chip error>Error</Chip>
744
+ <Chip error outlined>Error Outlined</Chip>
745
+ <Chip onClick={() => {}}>Clickable</Chip>
746
+ <Chip icon={<TagIcon size={12}/>} colorScheme="teal">With Icon</Chip>
747
+ <Chip>Default (no scheme)</Chip>
748
+ <Chip outlined>Default Outlined</Chip>
749
+ </div>
750
+ <Typography variant="caption" color="secondary" className="block mb-2 font-mono">FilterChip</Typography>
751
+ <div className="flex flex-wrap gap-2 items-center mb-6">
752
+ <FilterChip active>Active</FilterChip>
753
+ <FilterChip>Inactive</FilterChip>
754
+ <FilterChip icon={<FilterIcon size={12}/>} active>With Icon</FilterChip>
755
+ <FilterChip size="small">Small</FilterChip>
756
+ <FilterChip disabled>Disabled</FilterChip>
757
+ </div>
720
758
  <Typography variant="caption" color="secondary" className="block mb-2 font-mono">Alert — color variants</Typography>
721
759
  <div className="flex flex-col gap-2">
722
760
  <Alert color="info">Info — informational message</Alert>
@@ -850,136 +888,6 @@ roles: [] }
850
888
  </div>
851
889
  </div>
852
890
  </SectionBlock>
853
-
854
- {/* ═══════════════════════════════════════════════
855
- SECTION: Roles View
856
- ═══════════════════════════════════════════════ */}
857
- <SectionBlock id="roles" title="Roles View — RolesView.tsx">
858
- <Typography variant="body2" color="secondary" className="mb-4">
859
- Layout from <code className="font-mono text-xs">RolesView</code>: same header pattern, table, and <code className="font-mono text-xs">CollectionPermissionsMatrix</code> with <code className="font-mono text-xs">defaultBorderMixin</code>.
860
- </Typography>
861
- <div className="flex items-center mt-12">
862
- <Typography gutterBottom variant="h4" className="grow" component="h4">Roles</Typography>
863
- <Button startIcon={<PlusIcon/>}>Add role</Button>
864
- </div>
865
- <div className="w-full overflow-auto">
866
- <Table className="w-full">
867
- <TableHeader>
868
- <TableCell header className="w-16"></TableCell>
869
- <TableCell header>Role</TableCell>
870
- <TableCell header className="items-center">Is Admin</TableCell>
871
- </TableHeader>
872
- <TableBody>
873
- {[
874
- { id: "admin",
875
- name: "Admin",
876
- isAdmin: true },
877
- { id: "editor",
878
- name: "Editor",
879
- isAdmin: false },
880
- { id: "viewer",
881
- name: "Viewer",
882
- isAdmin: false }
883
- ].map(role => (
884
- <TableRow key={role.id}>
885
- <TableCell style={{ width: "64px" }}>
886
- {!role.isAdmin && (
887
- <Tooltip asChild title="Delete this role">
888
- <IconButton size="small"><Trash2Icon/></IconButton>
889
- </Tooltip>
890
- )}
891
- </TableCell>
892
- <TableCell>
893
- <Chip colorScheme={role.isAdmin ? "purpleDark" : "blueDark"} size="small">{role.name}</Chip>
894
- </TableCell>
895
- <TableCell className="items-center">
896
- <Checkbox checked={role.isAdmin ?? false} disabled/>
897
- </TableCell>
898
- </TableRow>
899
- ))}
900
- </TableBody>
901
- </Table>
902
- </div>
903
- {/* CollectionPermissionsMatrix — from RolesView line 365-406 */}
904
- <div className="mt-4">
905
- <Typography variant="label" className="mb-2 block text-surface-500 dark:text-surface-400 uppercase tracking-wide text-xs">
906
- Collection permissions
907
- </Typography>
908
- <div className={`rounded-lg overflow-hidden border w-full ${defaultBorderMixin}`}>
909
- <Table className="w-full">
910
- <TableHeader>
911
- <TableCell header>Collection</TableCell>
912
- {["Read", "Create", "Edit", "Delete"].map(op => (
913
- <TableCell key={op} header align="center" className="w-20">{op}</TableCell>
914
- ))}
915
- </TableHeader>
916
- <TableBody>
917
- {[{ name: "Posts",
918
- slug: "posts" }, { name: "Authors",
919
- slug: "authors" }].map(col => (
920
- <TableRow key={col.slug}>
921
- <TableCell>
922
- <div className="flex items-center gap-1.5">
923
- <span className="font-medium">{col.name}</span>
924
- <Tooltip title="No security rules defined — all operations unrestricted">
925
- <Chip size="smallest" colorScheme="gray">no rules</Chip>
926
- </Tooltip>
927
- </div>
928
- <span className="text-xs text-surface-400 font-mono">{col.slug}</span>
929
- </TableCell>
930
- {["select", "insert", "update", "delete"].map(op => (
931
- <TableCell key={op} align="center" className="w-20">
932
- <span className="text-green-500 dark:text-green-400 font-bold">✓</span>
933
- </TableCell>
934
- ))}
935
- </TableRow>
936
- ))}
937
- </TableBody>
938
- </Table>
939
- </div>
940
- </div>
941
- </SectionBlock>
942
-
943
- {/* ═══════════════════════════════════════════════
944
- SECTION: Role Dialog
945
- ═══════════════════════════════════════════════ */}
946
- <SectionBlock id="role-dialog" title="Role Dialog — RoleDetailsForm">
947
- <Typography variant="body2" color="secondary" className="mb-4">
948
- Exact structure of <code className="font-mono text-xs">RoleDetailsForm</code>: <code className="font-mono text-xs">col-span-4</code> grid, Role ID + Name + Is Admin checkbox.
949
- </Typography>
950
- <div className={`rounded-lg border w-full max-w-xl ${defaultBorderMixin}`}>
951
- <div className="px-6 pt-6 pb-2">
952
- <Typography variant="h4">Role</Typography>
953
- </div>
954
- <div className="px-6 py-4">
955
- <div className="grid grid-cols-12 gap-4">
956
- <div className="col-span-12 sm:col-span-4">
957
- <TextField name="id" required value="editor" onChange={() => {}} label="Role ID" disabled/>
958
- </div>
959
- <div className="col-span-12 sm:col-span-4">
960
- <TextField name="name" required value="Editor" onChange={() => {}} label="Role Name"/>
961
- </div>
962
- <div className="col-span-12 sm:col-span-4 flex items-start pt-2">
963
- <label className="flex items-center gap-2 cursor-pointer mt-3">
964
- <Checkbox checked={false} onCheckedChange={() => {}}/>
965
- <span className="font-medium">Is Admin</span>
966
- </label>
967
- </div>
968
- </div>
969
- </div>
970
- <div className="flex items-center justify-end gap-2 px-6 pb-6">
971
- <Button variant="text">Cancel</Button>
972
- <LoadingButton variant="filled" loading={false}>Update</LoadingButton>
973
- </div>
974
- </div>
975
- </SectionBlock>
976
-
977
- {/* Footer */}
978
- <div className="px-6 py-8">
979
- <Typography variant="caption" color="secondary">
980
- Hidden debug reference — <code className="font-mono text-xs">/debug/ui</code>. Not linked from sidebar.
981
- </Typography>
982
- </div>
983
891
  </div>
984
892
  </div>
985
893
  );
@@ -28,19 +28,29 @@ export function ErrorView({
28
28
  tooltip,
29
29
  onRetry
30
30
  }: ErrorViewProps): React.ReactElement {
31
- const component = error instanceof Error ? error.message : error;
32
- console.warn("ErrorView", JSON.stringify(error))
31
+ const message = error instanceof Error ? error.message : error;
32
+ // Extract error code from ApiError instances (e.g. PG error codes like "42P01")
33
+ const errorCode = error instanceof Error && "code" in error
34
+ ? (error as Error & { code?: string }).code
35
+ : undefined;
33
36
 
34
37
  const body = (
35
38
  <div
36
39
  className="flex flex-col m-2">
37
- <div className="flex items-start">
38
- <AlertTriangleIcon className="mx-2 mt-0.5"/>
39
- <div className="pl-2">
40
+ <div className="flex items-center gap-2">
41
+ <AlertTriangleIcon className="shrink-0"/>
42
+ <div>
40
43
  {title && <Typography
41
44
  variant={"body2"}
42
45
  className="font-medium text-text-primary">{title}</Typography>}
43
- <Typography variant={"body2"} className="text-text-secondary">{component}</Typography>
46
+ <Typography variant={"body2"} className="text-text-secondary">{message}</Typography>
47
+ {errorCode && (
48
+ <span
49
+ className="inline-block mt-1 px-1.5 py-0.5 text-[10px] font-mono rounded bg-surface-200 dark:bg-surface-700 text-text-secondary"
50
+ >
51
+ {errorCode}
52
+ </span>
53
+ )}
44
54
  {onRetry && (
45
55
  <div className="mt-3">
46
56
  <Button