@jini-ai/admin 0.6.15 → 0.6.16

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.
@@ -7,7 +7,7 @@
7
7
  * without a DOM environment so a leak fails loudly rather than passing quietly.
8
8
  */
9
9
  export type { AdminNavEntry, AdminPanel, AdminRoutePattern } from './manifest/types.js';
10
- export { buildAgentPageMap, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
10
+ export { buildAgentPageMap, buildAgentScreens, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
11
11
  export type { AdminNavGroup, AdminNavItem, AdminRegistryContext } from './manifest/rules.js';
12
12
  export type { AdminRoute } from './routing/types.js';
13
13
  export { DEFAULT_ADMIN_BASE, adminHref, currentRoutePath, matchRoute, stripTrailingSlash, } from './routing/rules.js';
@@ -6,7 +6,7 @@
6
6
  * boundary is enforced by this package's vitest config, which deliberately runs `src/core/**`
7
7
  * without a DOM environment so a leak fails loudly rather than passing quietly.
8
8
  */
9
- export { buildAgentPageMap, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
9
+ export { buildAgentPageMap, buildAgentScreens, buildNav, panelHref, resolveAgentPageId, resolvePanels, } from './manifest/rules.js';
10
10
  export { DEFAULT_ADMIN_BASE, adminHref, currentRoutePath, matchRoute, stripTrailingSlash, } from './routing/rules.js';
11
11
  // Permissions — affordance only, never an authorization boundary.
12
12
  export { hasPermission } from './permissions/rules.js';
@@ -99,4 +99,24 @@ export declare function resolveAgentPageId<T>({ panels, panelId, view }: {
99
99
  readonly panelId: string | null;
100
100
  readonly view: string | null;
101
101
  }): string | null;
102
+ /** Extend the existing page projection with descriptions, templates and entity contracts.
103
+ * Host aliases come from its router adapter, keeping one route owner. Runtime fallback protects
104
+ * older/untyped panels; compile-time contracts and host drift tests require descriptions. @complexity O(p * r) for p panels. */
105
+ export declare function buildAgentScreens<T>({ panels }: {
106
+ panels: readonly AdminPanel<T>[];
107
+ }, { defaultReachable, paths }?: {
108
+ defaultReachable?: boolean;
109
+ paths?: Readonly<Record<string, string>>;
110
+ }): {
111
+ id: string;
112
+ label: string;
113
+ description: string;
114
+ path: string;
115
+ routeTemplates: string[];
116
+ allowedQueryParams: Readonly<Record<string, readonly string[] | null>>;
117
+ entityTargets: {
118
+ type: string;
119
+ routeTemplate: string;
120
+ }[];
121
+ }[];
102
122
  //# sourceMappingURL=rules.d.ts.map
@@ -128,4 +128,38 @@ export function resolveAgentPageId({ panels, panelId, view }) {
128
128
  const matched = (panel.routes ?? []).find((r) => r.view === view);
129
129
  return matched?.agentPageId ?? panel.id;
130
130
  }
131
+ /** Extend the existing page projection with descriptions, templates and entity contracts.
132
+ * Host aliases come from its router adapter, keeping one route owner. Runtime fallback protects
133
+ * older/untyped panels; compile-time contracts and host drift tests require descriptions. @complexity O(p * r) for p panels. */
134
+ export function buildAgentScreens({ panels }, { defaultReachable = false, paths = {} } = {}) {
135
+ const pages = buildAgentPageMap({ panels }, { defaultReachable });
136
+ return Object.entries(pages).map(([id, originalPath]) => {
137
+ const panel = panels.find(candidate => candidate.id === id || candidate.routes?.some(route => route.agentPageId === id));
138
+ if (!panel)
139
+ throw new Error(`Missing owner for admin page "${id}"`);
140
+ return projectAgentScreen({ id, path: paths[id] ?? originalPath, panel, paths });
141
+ });
142
+ }
143
+ /** Project the sidebar label or title-case a published route id. */
144
+ function screenLabel({ id, panel }, _options = {}) {
145
+ const label = panel.id === id ? panel.nav?.label : undefined;
146
+ return label ?? id.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
147
+ }
148
+ /** Pure projection over the panel's own route declarations; no route may be invented here. */
149
+ function projectAgentScreen({ id, path, panel, paths }, _options = {}) {
150
+ const label = screenLabel({ id, panel });
151
+ const base = (paths[panel.id] ?? panelHref({ panelId: panel.id })).split('?')[0];
152
+ const template = (pattern) => `${base === '/' ? '' : base}${pattern}` || '/';
153
+ return {
154
+ id, label, description: panel.description?.trim() || label, path,
155
+ routeTemplates: [...new Set([path.split('?')[0], ...(panel.routes ?? []).map(route => template(route.pattern))])],
156
+ allowedQueryParams: panel.allowedQueryParams ?? {},
157
+ entityTargets: entitiesForPage({ id, panel }).map(entity => ({ type: entity.type, routeTemplate: template(entity.pattern) })),
158
+ };
159
+ }
160
+ /** A secondary destination reports its own page identity, so it must not advertise an editor
161
+ * whose route reports the parent page instead. */
162
+ function entitiesForPage({ id, panel }, _options = {}) {
163
+ return panel.id === id ? panel.agentEntities ?? [] : [];
164
+ }
131
165
  //# sourceMappingURL=rules.js.map
@@ -112,6 +112,15 @@ export interface AdminPanel<TRender = unknown> {
112
112
  * the page could not act on it without a translation table.
113
113
  */
114
114
  readonly id: string;
115
+ /** One plain-language sentence describing what an operator does here. */
116
+ readonly description: string;
117
+ /** Allowed route-state keys; null means a free text value, an array constrains its vocabulary. */
118
+ readonly allowedQueryParams?: Readonly<Record<string, readonly string[] | null>>;
119
+ /** Existing entity routes, relative to this panel. Empty pattern addresses the landing page. */
120
+ readonly agentEntities?: readonly {
121
+ readonly type: string;
122
+ readonly pattern: string;
123
+ }[];
115
124
  /** What the shell renders for this panel. */
116
125
  readonly render: TRender;
117
126
  /** Sidebar presence. Omit for a panel that is reachable by URL but not listed. */
@@ -46,6 +46,7 @@ export function createEntityPanel(args, options = {}) {
46
46
  const dependencies = { registry: args.registry, translate: args.translate, routes };
47
47
  return {
48
48
  id: args.panelId,
49
+ description: args.translate({ key: 'Browse and edit data records.' }),
49
50
  nav: options.nav ?? { label: args.translate({ key: 'Data' }) },
50
51
  agentReachable: options.agentReachable ?? false,
51
52
  ...(options.requires === undefined ? {} : { requires: options.requires }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jini-ai/admin",
3
- "version": "0.6.15",
3
+ "version": "0.6.16",
4
4
  "description": "Composable admin surface for Jini-hosted products: framework-free contracts, panel registry, and route model in `/core`; React shell and panels in `/react`. Import only the layers you need.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -95,6 +95,16 @@
95
95
  "react-dom"
96
96
  ]
97
97
  }
98
+ },
99
+ "optionalPeers": {
100
+ "@jini-ai/agentic": "opt-in: agent-aware ui and protocols; import @jini-ai/admin/react — Agent-aware UI and protocols could not load @jini-ai/agentic. Install @jini-ai/agentic to enable this feature, or check its installed version and native binaries.",
101
+ "@jini-ai/ui": "opt-in: ui integrations; import @jini-ai/admin/react — UI integrations could not load @jini-ai/ui. Install @jini-ai/ui to enable this feature, or check its installed version and native binaries.",
102
+ "@jini-ai/ui-kit": "opt-in: ui kit integrations; import @jini-ai/admin/react — UI kit integrations could not load @jini-ai/ui-kit. Install @jini-ai/ui-kit to enable this feature, or check its installed version and native binaries.",
103
+ "@jini-ai/agent-plugins": "opt-in: agent plugin integrations — Agent plugin integrations could not load @jini-ai/agent-plugins. Install @jini-ai/agent-plugins to enable this feature, or check its installed version and native binaries.",
104
+ "@jini-ai/cms": "opt-in: cms integrations; import @jini-ai/admin/media — CMS integrations could not load @jini-ai/cms. Install @jini-ai/cms to enable this feature, or check its installed version and native binaries.",
105
+ "@jini-ai/core": "opt-in: core module integrations; import @jini-ai/admin/react — Core module integrations could not load @jini-ai/core. Install @jini-ai/core to enable this feature, or check its installed version and native binaries.",
106
+ "react": "opt-in: react admin bindings; import @jini-ai/admin/react — React admin bindings could not load react. Install react to enable this feature, or check its installed version.",
107
+ "react-dom": "opt-in: react admin bindings; import @jini-ai/admin/react — React admin bindings could not load react-dom. Install react-dom to enable this feature, or check its installed version."
98
108
  }
99
109
  },
100
110
  "type": "module",
@@ -455,13 +465,31 @@
455
465
  "@jini-ai/cms": "^0.6.10",
456
466
  "@jini-ai/core": "^0.6.10"
457
467
  },
468
+ "devDependencies": {
469
+ "@testing-library/jest-dom": "^6.6.3",
470
+ "@testing-library/react": "^16.3.0",
471
+ "@types/react": "^19.2.0",
472
+ "@types/react-dom": "^19.2.0",
473
+ "@vitest/coverage-v8": "^2.1.9",
474
+ "jsdom": "^25.0.1",
475
+ "react": "^19.2.0",
476
+ "react-dom": "^19.2.0",
477
+ "@testing-library/user-event": "^14.6.1",
478
+ "@jini-ai/ui-kit": "^0.6.16",
479
+ "@jini-ai/agent-plugins": "^0.6.16",
480
+ "@jini-ai/cms": "^0.6.16",
481
+ "@jini-ai/agentic": "^0.6.16",
482
+ "@jini-ai/ui": "^0.6.16",
483
+ "@jini-ai/core": "^0.6.16"
484
+ },
485
+ "typesVersions": {
486
+ "*": {
487
+ "core/react": [
488
+ "./dist/core/react/index.d.ts"
489
+ ]
490
+ }
491
+ },
458
492
  "peerDependenciesMeta": {
459
- "react": {
460
- "optional": true
461
- },
462
- "react-dom": {
463
- "optional": true
464
- },
465
493
  "@jini-ai/agentic": {
466
494
  "optional": true
467
495
  },
@@ -479,30 +507,12 @@
479
507
  },
480
508
  "@jini-ai/core": {
481
509
  "optional": true
482
- }
483
- },
484
- "devDependencies": {
485
- "@testing-library/jest-dom": "^6.6.3",
486
- "@testing-library/react": "^16.3.0",
487
- "@types/react": "^19.2.0",
488
- "@types/react-dom": "^19.2.0",
489
- "@vitest/coverage-v8": "^2.1.9",
490
- "jsdom": "^25.0.1",
491
- "react": "^19.2.0",
492
- "react-dom": "^19.2.0",
493
- "@testing-library/user-event": "^14.6.1",
494
- "@jini-ai/agentic": "^0.6.15",
495
- "@jini-ai/agent-plugins": "^0.6.15",
496
- "@jini-ai/ui-kit": "^0.6.15",
497
- "@jini-ai/cms": "^0.6.15",
498
- "@jini-ai/core": "^0.6.15",
499
- "@jini-ai/ui": "^0.6.15"
500
- },
501
- "typesVersions": {
502
- "*": {
503
- "core/react": [
504
- "./dist/core/react/index.d.ts"
505
- ]
510
+ },
511
+ "react": {
512
+ "optional": true
513
+ },
514
+ "react-dom": {
515
+ "optional": true
506
516
  }
507
517
  },
508
518
  "scripts": {