@riducms/plugin 0.2.3 → 0.4.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.
package/src/plugin.ts CHANGED
@@ -1,19 +1,30 @@
1
1
  import type { FieldType } from "@riducms/protocol";
2
2
  import type { RegisteredPluginField } from "./field";
3
3
  import { ADMIN_PLUGIN_API_VERSION } from "@riducms/protocol";
4
- import type {
5
- OperationCapabilities,
6
- SchemaCollection,
7
- SchemaField,
8
- SchemaGlobal,
9
- SchemaManifest,
10
- } from "@riducms/protocol";
11
- import type { Component, Snippet } from "svelte";
12
-
13
- import type { FieldDocument } from "./authoring";
14
- import type { AdminI18n, PluginMessageCatalog, ExtensionTranslationKey } from "./i18n";
4
+ import type { PluginMessageCatalog, ExtensionTranslationKey } from "./i18n";
15
5
  import { freezeAdminMessages, validateAdminMessages } from "./i18n";
16
6
  import type { RowLabelPlugin } from "./row-label";
7
+ import type {
8
+ AdminAccountComponent,
9
+ AdminAccountSurface,
10
+ AdminBrandComponent,
11
+ AdminContributions,
12
+ AdminCoreView,
13
+ AdminDashboardPanel,
14
+ AdminDocumentAction,
15
+ AdminDocumentView,
16
+ AdminListCellRenderer,
17
+ AdminListResultsRenderer,
18
+ AdminLoginComponent,
19
+ AdminLogoutButton,
20
+ AdminNavigationComponent,
21
+ AdminProvider,
22
+ AdminRoute,
23
+ AdminShellSlot,
24
+ ResolvedAdminExtensions,
25
+ } from "./admin-extensions";
26
+
27
+ export * from "./admin-extensions";
17
28
 
18
29
  export { ADMIN_PLUGIN_API_VERSION };
19
30
 
@@ -43,7 +54,7 @@ export interface AdminPlugin extends AdminContributions {
43
54
  * Go chooses one with `.Admin(field.Admin{Editor: field.PluginComponent(pluginKey, componentName, config)})`.
44
55
  * These may edit built-in fields or an explicitly named plugin field type.
45
56
  */
46
- components?: Readonly<
57
+ fieldEditors?: Readonly<
47
58
  Record<
48
59
  string,
49
60
  RegisteredPluginField &
@@ -61,315 +72,6 @@ export interface AdminPlugin extends AdminContributions {
61
72
  assets?: readonly string[];
62
73
  }
63
74
 
64
- /**
65
- * Places where a plugin or application can add admin UI. Arrays keep registration
66
- * order; application entries follow plugin entries. Replacement slots allow one
67
- * matching replacement. Conflicts are reported instead of silently overriding UI.
68
- */
69
- export interface AdminContributions {
70
- /** Authenticated admin routes contributed by this package. */
71
- routes?: readonly AdminRoute[];
72
- /** Panels composed into or replacing the authenticated dashboard. */
73
- dashboard?: readonly AdminDashboardPanel[];
74
- /** Components composed around or replacing the sign-in screen. */
75
- login?: readonly AdminLoginComponent[];
76
- /** Components composed around or replacing the framework account screens. */
77
- account?: readonly AdminAccountComponent[];
78
- /** Components composed around or replacing the framework navigation. */
79
- navigation?: readonly AdminNavigationComponent[];
80
- /** Replaces the default sign-out control in the account menu. */
81
- logoutButton?: AdminLogoutButton;
82
- /** Wraps or replaces framework collection, global, and not-found views. */
83
- views?: readonly AdminCoreView[];
84
- /** Replaces framework-owned brand graphics at exact shell surfaces. */
85
- branding?: readonly AdminBrandComponent[];
86
- /** Adds global header, action, or account-settings menu components. */
87
- shell?: readonly AdminShellComponent[];
88
- /** Wraps the admin in statically ordered Svelte context providers. */
89
- providers?: readonly AdminProvider[];
90
- /** Collection list cells rendered for exact collection/field pairs. */
91
- listCells?: readonly AdminListCell[];
92
- /** Actions rendered alongside framework-owned document actions. */
93
- documentActions?: readonly AdminDocumentAction[];
94
- /** Read-only or operational views rendered beside Edit and API. */
95
- documentViews?: readonly AdminDocumentView[];
96
- }
97
-
98
- /** Common data supplied to admin extension components. Use the generated SDK for requests. */
99
- export interface AdminExtensionProps {
100
- /** Resolved Go schema available to this admin session; it may omit inaccessible resources. */
101
- manifest: SchemaManifest;
102
- /** Signed-in user document, absent on screens where no user is signed in yet. */
103
- user?: FieldDocument;
104
- /** Current admin interface translations and formatting preferences. */
105
- i18n: AdminI18n;
106
- }
107
-
108
- /** Sign-in operations provided to a custom login screen. */
109
- export interface AdminLoginExtensionHost {
110
- /** Sign in, check admin access and enter the admin. Rejects if authentication/access fails. */
111
- login: (credentials: { email: string; password: string }) => Promise<void>;
112
- /** Show a temporary success or error message. Does not perform an operation itself. */
113
- notify: (tone: AdminExtensionNotificationTone, title: string, message?: string) => void;
114
- }
115
-
116
- export interface AdminLoginComponentProps extends AdminExtensionProps {
117
- /** Render with `{@render defaultView()}` to keep Ridu's sign-in screen inside your wrapper. */
118
- defaultView: Snippet;
119
- host: AdminLoginExtensionHost;
120
- }
121
-
122
- /** Add content before/after sign-in, or replace it with a custom screen. */
123
- export interface AdminLoginComponent {
124
- key: string;
125
- component: Component<AdminLoginComponentProps>;
126
- /** Defaults to after. Only one replace entry is allowed across plugins and the application. */
127
- position?: "before" | "after" | "replace";
128
- }
129
-
130
- export type AdminAccountSurface = "profile" | "security";
131
-
132
- /** Operations for keeping a custom account screen in sync with the signed-in session. */
133
- export interface AdminAccountExtensionHost {
134
- /** Reloads the signed-in document and updates the shell identity. */
135
- refreshUser: () => Promise<FieldDocument | undefined>;
136
- /** Ends the current session and returns to sign-in. */
137
- logout: () => Promise<void>;
138
- /** Show a temporary success or error message. */
139
- notify: (tone: AdminExtensionNotificationTone, title: string, message?: string) => void;
140
- }
141
-
142
- export interface AdminAccountComponentProps extends AdminExtensionProps {
143
- surface: AdminAccountSurface;
144
- /** Render with `{@render defaultView()}` to keep Ridu's account screen inside your wrapper. */
145
- defaultView: Snippet;
146
- host: AdminAccountExtensionHost;
147
- }
148
-
149
- /** Add to or replace the profile/security screen identified by `surface`. */
150
- export interface AdminAccountComponent {
151
- key: string;
152
- surface: AdminAccountSurface;
153
- component: Component<AdminAccountComponentProps>;
154
- /** Defaults to after. Each account surface permits at most one replacement. */
155
- position?: "before" | "after" | "replace";
156
- }
157
-
158
- /** Choose the whole navigation's boundary, the resource-link list's boundary, or replace navigation. */
159
- export type AdminNavigationPosition = "before" | "beforeLinks" | "afterLinks" | "after" | "replace";
160
-
161
- export interface AdminNavigationComponentProps extends AdminExtensionProps {
162
- /** Render with `{@render defaultView()}` to include Ridu's navigation in a replacement. */
163
- defaultView: Snippet;
164
- }
165
-
166
- /** Insert a component at a navigation position. At most one entry can replace navigation. */
167
- export interface AdminNavigationComponent {
168
- key: string;
169
- component: Component<AdminNavigationComponentProps>;
170
- position: AdminNavigationPosition;
171
- }
172
-
173
- export interface AdminLogoutExtensionHost {
174
- /** End the current session and return to sign-in. Await this instead of only changing local UI. */
175
- logout: () => Promise<void>;
176
- }
177
-
178
- export interface AdminLogoutButtonProps extends AdminExtensionProps {
179
- host: AdminLogoutExtensionHost;
180
- }
181
-
182
- /** Replace the account menu's sign-out button; call the supplied `host.logout()` on activation. */
183
- export interface AdminLogoutButton {
184
- key: string;
185
- component: Component<AdminLogoutButtonProps>;
186
- }
187
-
188
- export type AdminCoreViewSurface =
189
- "collectionList" | "collectionCreate" | "collectionEdit" | "global" | "notFound";
190
-
191
- /** Refresh tools for a custom collection/global screen. They do not save documents. */
192
- export interface AdminCoreViewHost {
193
- /** Fetch the current Go schema again and update the admin's schema-dependent UI. */
194
- refreshManifest: () => Promise<void>;
195
- /** Notify Ridu that your code changed documents so dependent lists/lookups can refresh. */
196
- documentsChanged: () => void;
197
- /** Show a temporary success or error message. */
198
- notify: (tone: AdminExtensionNotificationTone, title: string, message?: string) => void;
199
- }
200
-
201
- export interface AdminCoreViewProps extends AdminExtensionProps {
202
- surface: AdminCoreViewSurface;
203
- collection?: SchemaCollection;
204
- global?: SchemaGlobal;
205
- documentID?: string;
206
- /** Render with `{@render defaultView()}` to wrap the normal screen rather than rebuilding it. */
207
- defaultView: Snippet;
208
- host: AdminCoreViewHost;
209
- }
210
-
211
- interface AdminCollectionCoreView {
212
- key: string;
213
- surface: "collectionList" | "collectionCreate" | "collectionEdit";
214
- /** Omit to replace this surface for every collection. */
215
- collection?: string;
216
- component: Component<AdminCoreViewProps>;
217
- }
218
-
219
- interface AdminGlobalCoreView {
220
- key: string;
221
- surface: "global";
222
- /** Omit to replace this surface for every global. */
223
- global?: string;
224
- component: Component<AdminCoreViewProps>;
225
- }
226
-
227
- interface AdminNotFoundCoreView {
228
- key: string;
229
- surface: "notFound";
230
- component: Component<AdminCoreViewProps>;
231
- }
232
-
233
- /**
234
- * Replace a collection/global/not-found screen. A resource-specific registration
235
- * wins over an all-resources fallback for that surface; duplicate targets fail.
236
- * The supplied `defaultView` lets your component wrap the existing screen.
237
- */
238
- export type AdminCoreView = AdminCollectionCoreView | AdminGlobalCoreView | AdminNotFoundCoreView;
239
-
240
- export type AdminBrandSurface = "loginLogo" | "navigationLogo" | "accountAvatar";
241
-
242
- export interface AdminBrandComponentProps extends AdminExtensionProps {
243
- surface: AdminBrandSurface;
244
- }
245
-
246
- /** Replace one logo/avatar location. Only one component may claim each surface. */
247
- export interface AdminBrandComponent {
248
- key: string;
249
- surface: AdminBrandSurface;
250
- component: Component<AdminBrandComponentProps>;
251
- }
252
-
253
- export type AdminShellPosition = "header" | "actions" | "settingsMenu";
254
-
255
- export interface AdminShellComponentProps extends AdminExtensionProps {
256
- position: AdminShellPosition;
257
- }
258
-
259
- /** Add UI to the global header, actions area or account settings menu in registration order. */
260
- export interface AdminShellComponent {
261
- key: string;
262
- position: AdminShellPosition;
263
- component: Component<AdminShellComponentProps>;
264
- }
265
-
266
- export interface AdminProviderProps {
267
- /** Render `{@render defaultView()}` after setting context so the nested providers/admin appear. */
268
- defaultView: Snippet;
269
- i18n: AdminI18n;
270
- }
271
-
272
- /** Wrap the admin in a Svelte context provider. Render the supplied `defaultView` to continue the UI. */
273
- export interface AdminProvider {
274
- key: string;
275
- component: Component<AdminProviderProps>;
276
- }
277
-
278
- export interface AdminDashboardPanelProps {
279
- manifest: SchemaManifest;
280
- user?: FieldDocument;
281
- i18n: AdminI18n;
282
- }
283
-
284
- /** Add dashboard content before/after Ridu's overview, or replace that overview. */
285
- export interface AdminDashboardPanel {
286
- /** Unique name among dashboard entries; duplicates throw. Array order controls display order. */
287
- key: string;
288
- component: Component<AdminDashboardPanelProps>;
289
- /** Defaults to after. Replace hides Ridu's overview and may only be registered once. */
290
- position?: "before" | "after" | "replace";
291
- }
292
-
293
- /** Data for displaying a collection table cell. This is not an editable document-form binding. */
294
- export interface AdminListCellProps {
295
- collection: SchemaCollection;
296
- field: SchemaField;
297
- document: FieldDocument;
298
- /** This field's saved value. Check its type before rendering; changing it does not save a document. */
299
- value: unknown;
300
- i18n: AdminI18n;
301
- }
302
-
303
- /** Replace the cell display for one collection/field pair; duplicate targets are rejected. */
304
- export interface AdminListCell {
305
- key: string;
306
- /** Collection slug, for example `"posts"`. */
307
- collection: string;
308
- /** Top-level field name/path in that collection, for example `"title"`. */
309
- field: string;
310
- /** Fallback column heading. */
311
- label: string;
312
- /** Optional translation key from this plugin's messages, or app:... for application entries. */
313
- labelKey?: ExtensionTranslationKey;
314
- component: Component<AdminListCellProps>;
315
- }
316
-
317
- export type AdminExtensionNotificationTone = "success" | "error";
318
-
319
- /** Tools for a document action or extra document tab after it completes its own operation. */
320
- export interface AdminDocumentExtensionHost {
321
- /** Reload the current document from the server. This is not a save of unsaved form edits. */
322
- refresh: () => Promise<void>;
323
- /** Show a temporary success or error message. */
324
- notify: (tone: AdminExtensionNotificationTone, title: string, message?: string) => void;
325
- }
326
-
327
- /** Saved document data for actions/tabs; use the generated SDK for application-specific operations. */
328
- export interface AdminDocumentExtensionProps {
329
- collection: SchemaCollection;
330
- document: FieldDocument;
331
- host: AdminDocumentExtensionHost;
332
- i18n: AdminI18n;
333
- }
334
-
335
- /** Add a component alongside the document's standard actions. Implement the operation in that component. */
336
- export interface AdminDocumentAction {
337
- key: string;
338
- /** Omit to show the action for every non-global collection. */
339
- collection?: string;
340
- /** Hide unless this document operation is allowed. Visibility does not replace server authorization. */
341
- requires?: keyof OperationCapabilities;
342
- component: Component<AdminDocumentExtensionProps>;
343
- }
344
-
345
- /** Add a tab beside Edit and API. Keys `edit` and `api` are reserved for Ridu. */
346
- export interface AdminDocumentView {
347
- key: string;
348
- label: string;
349
- labelKey?: ExtensionTranslationKey;
350
- /** Collection or global slug; omit to show for every collection and global. */
351
- collection?: string;
352
- component: Component<AdminDocumentExtensionProps>;
353
- }
354
-
355
- export interface AdminRouteNavigation {
356
- /** Human-readable navigation label inside the authenticated admin shell. */
357
- label: string;
358
- labelKey?: ExtensionTranslationKey;
359
- /** Optional grouping hint reserved for shells that render grouped plugin navigation. */
360
- group?: string;
361
- }
362
-
363
- /** One statically bundled route mounted beneath the authenticated admin layout. */
364
- export interface AdminRoute {
365
- /** Relative admin path, without a leading slash. Paired plugins must match their Go descriptor. */
366
- path: string;
367
- /** Svelte route component rendered by the framework router. */
368
- component: Component;
369
- /** Optional navigation entry; omit it for a route reachable only by links or redirects. */
370
- navigation?: AdminRouteNavigation;
371
- }
372
-
373
75
  /**
374
76
  * Go plugin metadata written into Ridu's generated admin registration file.
375
77
  * Do not hand-maintain a second copy: `ridu generate` resolves the executable Go
@@ -400,26 +102,6 @@ export interface AdminPluginPair {
400
102
  backend: BackendAdminPlugin;
401
103
  }
402
104
 
403
- /** Checked plugin registrations collected for Ridu's admin runtime. Normally consumed by generated code. */
404
- export interface ResolvedAdminExtensions {
405
- rowLabels: readonly RowLabelPlugin[];
406
- routes: readonly AdminRoute[];
407
- dashboard: readonly AdminDashboardPanel[];
408
- login: readonly AdminLoginComponent[];
409
- account: readonly AdminAccountComponent[];
410
- navigation: readonly AdminNavigationComponent[];
411
- logoutButton: AdminLogoutButton | undefined;
412
- views: readonly AdminCoreView[];
413
- branding: readonly AdminBrandComponent[];
414
- shell: readonly AdminShellComponent[];
415
- providers: readonly AdminProvider[];
416
- listCells: readonly AdminListCell[];
417
- documentActions: readonly AdminDocumentAction[];
418
- documentViews: readonly AdminDocumentView[];
419
- messages: Readonly<Record<string, PluginMessageCatalog>>;
420
- applicationMessages: PluginMessageCatalog | undefined;
421
- }
422
-
423
105
  /**
424
106
  * Check that installed Go/admin plugin packages agree on identity, API version,
425
107
  * pairing version, routes, assets and field types. Ridu's generated file calls this;
@@ -478,15 +160,16 @@ export function resolveAdminExtensions(
478
160
  application?: AdminContributions & { messages?: PluginMessageCatalog }
479
161
  ): ResolvedAdminExtensions {
480
162
  const routes: AdminRoute[] = [];
481
- const dashboard: AdminDashboardPanel[] = [];
163
+ const dashboardPanels: AdminDashboardPanel[] = [];
482
164
  const login: AdminLoginComponent[] = [];
483
165
  const account: AdminAccountComponent[] = [];
484
166
  const navigation: AdminNavigationComponent[] = [];
485
- const views: AdminCoreView[] = [];
167
+ const coreViews: AdminCoreView[] = [];
486
168
  const branding: AdminBrandComponent[] = [];
487
- const shell: AdminShellComponent[] = [];
169
+ const shellSlots: AdminShellSlot[] = [];
488
170
  const providers: AdminProvider[] = [];
489
- const listCells: AdminListCell[] = [];
171
+ const listCellRenderers: AdminListCellRenderer[] = [];
172
+ const listResultsRenderers: AdminListResultsRenderer[] = [];
490
173
  const documentActions: AdminDocumentAction[] = [];
491
174
  const documentViews: AdminDocumentView[] = [];
492
175
  const rowLabels: RowLabelPlugin[] = [];
@@ -494,6 +177,8 @@ export function resolveAdminExtensions(
494
177
  string,
495
178
  PluginMessageCatalog
496
179
  >;
180
+ // These identities represent mount slots, not package-local names. Claiming one
181
+ // set across every source prevents application UI from silently shadowing a plugin.
497
182
  const identities = new Set<string>();
498
183
  let replacementDashboard: string | undefined;
499
184
  let replacementLogin: string | undefined;
@@ -501,6 +186,8 @@ export function resolveAdminExtensions(
501
186
  let replacementNavigation: string | undefined;
502
187
  let logoutButton: AdminLogoutButton | undefined;
503
188
 
189
+ // Preserve authored order within each source, with the application consistently
190
+ // following installed plugins for additive surfaces and provider nesting.
504
191
  const sources = [
505
192
  ...plugins.map((plugin) => ({ ...plugin, namespace: `plugin.${plugin.key}:` })),
506
193
  ...(application === undefined
@@ -508,7 +195,7 @@ export function resolveAdminExtensions(
508
195
  : [{ ...application, key: "application", namespace: "app:", rowLabels: [] }]),
509
196
  ];
510
197
  for (const plugin of sources) {
511
- validateContributions(plugin, plugin.namespace === "app:");
198
+ validateContributions(plugin);
512
199
  for (const rowLabel of plugin.rowLabels ?? []) {
513
200
  if (rowLabel.key !== plugin.key) {
514
201
  throw new Error(
@@ -536,10 +223,12 @@ export function resolveAdminExtensions(
536
223
  }
537
224
  for (const route of plugin.routes ?? []) {
538
225
  validatePluginLabelKey(plugin, route.navigation?.labelKey, `route ${route.path}`);
226
+ // Treat differently cased paths as one identity rather than allowing
227
+ // registrations whose reachability depends on a host or proxy's normalization.
539
228
  claim(identities, `route:${route.path.toLowerCase()}`, `Admin plugin route ${route.path}`);
540
229
  routes.push(route);
541
230
  }
542
- for (const panel of plugin.dashboard ?? []) {
231
+ for (const panel of plugin.dashboardPanels ?? []) {
543
232
  claim(identities, `dashboard:${panel.key}`, `Admin dashboard panel ${panel.key}`);
544
233
  if (panel.position === "replace") {
545
234
  if (replacementDashboard !== undefined) {
@@ -549,7 +238,7 @@ export function resolveAdminExtensions(
549
238
  }
550
239
  replacementDashboard = panel.key;
551
240
  }
552
- dashboard.push(panel);
241
+ dashboardPanels.push(panel);
553
242
  }
554
243
  for (const component of plugin.login ?? []) {
555
244
  claim(identities, `login:${component.key}`, `Admin login component ${component.key}`);
@@ -609,7 +298,7 @@ export function resolveAdminExtensions(
609
298
  );
610
299
  logoutButton = plugin.logoutButton;
611
300
  }
612
- for (const view of plugin.views ?? []) {
301
+ for (const view of plugin.coreViews ?? []) {
613
302
  const resource =
614
303
  "collection" in view
615
304
  ? (view.collection ?? "*")
@@ -621,7 +310,7 @@ export function resolveAdminExtensions(
621
310
  `core-view:${view.surface}:${resource}`,
622
311
  `Admin core view ${view.surface}.${resource}`
623
312
  );
624
- views.push(view);
313
+ coreViews.push(view);
625
314
  }
626
315
  for (const component of plugin.branding ?? []) {
627
316
  claim(
@@ -631,26 +320,34 @@ export function resolveAdminExtensions(
631
320
  );
632
321
  branding.push(component);
633
322
  }
634
- for (const component of plugin.shell ?? []) {
323
+ for (const component of plugin.shellSlots ?? []) {
635
324
  claim(
636
325
  identities,
637
326
  `shell:${component.position}:${component.key}`,
638
- `Admin shell component ${component.key}`
327
+ `Admin shell slot ${component.key}`
639
328
  );
640
- shell.push(component);
329
+ shellSlots.push(component);
641
330
  }
642
331
  for (const provider of plugin.providers ?? []) {
643
332
  claim(identities, `provider:${provider.key}`, `Admin provider ${provider.key}`);
644
333
  providers.push(provider);
645
334
  }
646
- for (const cell of plugin.listCells ?? []) {
335
+ for (const cell of plugin.listCellRenderers ?? []) {
647
336
  validatePluginLabelKey(plugin, cell.labelKey, `list cell ${cell.collection}.${cell.field}`);
648
337
  claim(
649
338
  identities,
650
339
  `list-cell:${cell.collection}:${cell.field}`,
651
- `Admin list cell ${cell.collection}.${cell.field}`
340
+ `Admin list-cell renderer ${cell.collection}.${cell.field}`
341
+ );
342
+ listCellRenderers.push(cell);
343
+ }
344
+ for (const results of plugin.listResultsRenderers ?? []) {
345
+ claim(
346
+ identities,
347
+ `list-results:${results.collection}`,
348
+ `Admin list-results renderer ${results.collection}`
652
349
  );
653
- listCells.push(cell);
350
+ listResultsRenderers.push(results);
654
351
  }
655
352
  for (const action of plugin.documentActions ?? []) {
656
353
  claim(
@@ -679,16 +376,17 @@ export function resolveAdminExtensions(
679
376
  application?.messages === undefined ? undefined : freezeAdminMessages(application.messages),
680
377
  rowLabels: Object.freeze(rowLabels),
681
378
  routes: Object.freeze(routes),
682
- dashboard: Object.freeze(dashboard),
379
+ dashboardPanels: Object.freeze(dashboardPanels),
683
380
  login: Object.freeze(login),
684
381
  account: Object.freeze(account),
685
382
  navigation: Object.freeze(navigation),
686
383
  logoutButton,
687
- views: Object.freeze(views),
384
+ coreViews: Object.freeze(coreViews),
688
385
  branding: Object.freeze(branding),
689
- shell: Object.freeze(shell),
386
+ shellSlots: Object.freeze(shellSlots),
690
387
  providers: Object.freeze(providers),
691
- listCells: Object.freeze(listCells),
388
+ listCellRenderers: Object.freeze(listCellRenderers),
389
+ listResultsRenderers: Object.freeze(listResultsRenderers),
692
390
  documentActions: Object.freeze(documentActions),
693
391
  documentViews: Object.freeze(documentViews),
694
392
  messages: Object.freeze(messages),
@@ -728,7 +426,7 @@ function assertSameValues(label: string, backend: readonly string[], admin: read
728
426
  }
729
427
  }
730
428
 
731
- // Application routes are literal paths: no router
429
+ // Admin routes are literal paths: no router
732
430
  // patterns, query strings, normalization aliases or framework namespace shadowing.
733
431
  const reservedRouteRoots = new Set([
734
432
  "account",
@@ -741,31 +439,29 @@ const reservedRouteRoots = new Set([
741
439
  "request-verification",
742
440
  "verify-email",
743
441
  ]);
744
- function validateContributions(
745
- source: AdminContributions & { messages?: PluginMessageCatalog },
746
- local: boolean
747
- ) {
442
+ function validateContributions(source: AdminContributions & { messages?: PluginMessageCatalog }) {
748
443
  if (source.messages !== undefined) validateAdminMessages("application", source.messages);
749
444
  const groups = [
750
445
  "routes",
751
- "dashboard",
446
+ "dashboardPanels",
752
447
  "login",
753
448
  "account",
754
449
  "navigation",
755
- "views",
450
+ "coreViews",
756
451
  "branding",
757
- "shell",
452
+ "shellSlots",
758
453
  "providers",
759
- "listCells",
454
+ "listCellRenderers",
455
+ "listResultsRenderers",
760
456
  "documentActions",
761
457
  "documentViews",
762
458
  ] as const;
763
459
  const positions = {
764
- dashboard: ["before", "after", "replace"],
460
+ dashboardPanels: ["before", "after", "replace"],
765
461
  login: ["before", "after", "replace"],
766
462
  account: ["before", "after", "replace"],
767
463
  navigation: ["before", "beforeLinks", "afterLinks", "after", "replace"],
768
- shell: ["header", "actions", "settingsMenu"],
464
+ shellSlots: ["header", "actions", "settingsMenu"],
769
465
  } as const;
770
466
  for (const group of groups) {
771
467
  const entries = source[group];
@@ -785,12 +481,12 @@ function validateContributions(
785
481
  const allowed: readonly string[] = positions[group as keyof typeof positions];
786
482
  const position = "position" in entry ? entry.position : undefined;
787
483
  if (
788
- (position === undefined && (group === "navigation" || group === "shell")) ||
484
+ (position === undefined && (group === "navigation" || group === "shellSlots")) ||
789
485
  (position !== undefined && !allowed.includes(position))
790
486
  )
791
487
  throw new Error(`Admin ${group} registration has an invalid position.`);
792
488
  }
793
- if (group === "account" || group === "branding" || group === "views") {
489
+ if (group === "account" || group === "branding" || group === "coreViews") {
794
490
  const allowed =
795
491
  group === "account"
796
492
  ? ["profile", "security"]
@@ -802,7 +498,7 @@ function validateContributions(
802
498
  }
803
499
  }
804
500
  }
805
- for (const route of local ? (source.routes ?? []) : []) {
501
+ for (const route of source.routes ?? []) {
806
502
  if (
807
503
  typeof route.path !== "string" ||
808
504
  !/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(route.path) ||