@dyrected/core 2.24.2 → 2.25.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/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as Field, B as Block, C as CollectionConfig, D as DyrectedConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, G as GlobalConfig, a as DeclarativeHookExpression, W as WorkflowConfig, b as AuthenticatedUser, c as WorkflowTransition, L as LifecycleEventName, d as LifecycleEvent, e as DatabaseAdapter, f as BaseDocument, H as HookRequestContext, T as TaskRunResult, g as TaskConfig, h as DetailActionOptions, i as DetailAction, j as DetailComputedOptions, k as ComputedHandler, l as DetailComputed, m as DetailCustomOptions, n as DetailCustom, o as DetailDividerOptions, p as DetailDivider, q as DisplayFieldOptions, r as DetailField, s as DetailItem, t as DetailGridOptions, u as DetailGrid, v as DetailRepeatOptions, w as DetailRepeat, x as DetailSectionOptions, y as DetailSection, z as DetailTabOptions, E as DetailTab, I as DetailTabsOptions, J as DetailTabs, K as DetailTextOptions, M as DetailText, N as DetailSchema, O as DyrectedContext, Q as AIConfig, R as AIPIIConfig, S as IdStrategy, U as CompiledNavTree, V as CompiledNavItem, X as UserNavigationPreferences, Y as DefineWorkspaceOptions, Z as ViewConfig, _ as DateTimeField, $ as RelationshipField, a0 as SelectField, a1 as ArrayField, a2 as FieldType, a3 as BooleanField, a4 as AuthConfig, a5 as AccessPolicyResolver, a6 as DateField, a7 as TextField, a8 as EmailField, a9 as IconField, aa as ImageField, ab as JoinField, ac as JsonField, ad as MoneyField, ae as MultiSelectField, af as NumberField, ag as ObjectField, ah as RadioField, ai as RichTextField, aj as RowField, ak as TextareaField, al as TimeField, am as UrlField } from './app-Bg34iyUz.js';
2
- export { an as AIAction, ao as AIActionStatus, ap as AIActionType, aq as AIAuditActionType, ar as AIAuditRecord, as as AIChunk, at as AIMessage, au as AIPIIPattern, av as AIRateLimitResult, aw as AIRateLimitStore, ax as AIThread, ay as AIToolContext, az as AIToolDefinition, aA as AI_ACTIONS_COLLECTION, aB as AI_AUDIT_COLLECTION, aC as AI_CHUNKS_COLLECTION, aD as AI_MESSAGES_COLLECTION, aE as AI_THREADS_COLLECTION, aF as AccessConfig, aG as AccessFunction, aH as AccessFunctionArgs, aI as AccessResult, aJ as AccessRule, aK as ActionConfig, aL as ActionConfigBase, aM as ActionConfigWithHandler, aN as ActionConfigWithMutation, aO as ActionContext, aP as ActionType, aQ as AdminAuthAccessResolution, aR as AdminAuthClaimMapping, aS as AdminAuthMember, aT as AdminAuthProvider, aU as AdminAuthProviderMembersConfig, aV as AdminAuthProvisioningMode, aW as AdminAuthResolveAccessArgs, aX as AdminAuthResolvedIdentity, aY as AdminConfig, aZ as AdminDashboardComponentSlots, a_ as AdminIconName, a$ as AggregateArgs, b0 as AggregateCastType, b1 as AggregateCollectionResult, b2 as AggregateInput, b3 as AggregateOperation, b4 as AggregateResult, b5 as AppTrashConfig, b6 as BaseAdminAuthProvider, b7 as BaseFieldAdmin, b8 as BlockVariant, b9 as BlocksField, ba as BooleanFieldAdmin, bb as BooleanFormat, bc as CURRENT_NAV_PREFERENCES_VERSION, bd as CUSTOM_VIEW_FETCH_ALL_LIMIT, be as CalendarViewConfig, bf as CardsViewConfig, bg as CharacterLimitFieldAdmin, bh as CharacterLimitFieldConfig, bi as CollectionAfterChangeHook, bj as CollectionAfterDeleteHook, bk as CollectionAfterReadHook, bl as CollectionAfterReadHookEntry, bm as CollectionAfterRestoreHook, bn as CollectionAfterTransitionHook, bo as CollectionAfterTrashHook, bp as CollectionBeforeChangeHook, bq as CollectionBeforeChangeHookEntry, br as CollectionBeforeCommitHook, bs as CollectionBeforeDeleteHook, bt as CollectionBeforeReadHook, bu as CollectionBeforeReadHookEntry, bv as CollectionBeforeRestoreHook, bw as CollectionBeforeTransitionHook, bx as CollectionBeforeTrashHook, by as CollectionEmailConfig, bz as CollectionIndex, bA as CollectionListComponentSlots, bB as CollectionRAGConfig, bC as CollectionSchemaResult, bD as CollectionSummaryResult, bE as CollectionViewComponentSlots, bF as CompiledNavGroup, bG as CountOperation, bH as CustomAdminAuthProvider, bI as CustomViewConfig, bJ as CustomViewDataConfig, bK as DEFAULT_USER_NAV_PREFERENCES, bL as DateFieldAdmin, bM as DateFormat, bN as DefaultValueContext, bO as DefineActionOptions, bP as DefineCalendarViewOptions, bQ as DefineCardsViewOptions, bR as DefineCustomViewOptions, bS as DefineGanttViewOptions, bT as DefineKanbanViewOptions, bU as DefinePageInput, bV as DefineTableViewOptions, bW as DefineViewBaseOptions, bX as DefineViewOptions, bY as DetailSpan, bZ as DisplayTone, b_ as DisplayVariant, b$ as DistinctCountOperation, c0 as DistinctValuesOperation, c1 as DocumentFormComponentSlots, c2 as DynamicOptionItem, c3 as DynamicOptionsConfig, c4 as DynamicOptionsResolver, c5 as DynamicOptionsResolverArgs, c6 as DyrectedAIContext, c7 as DyrectedLoggerConfig, c8 as DyrectedObservabilityConfig, c9 as EmailFieldAdmin, ca as EmailTemplateArgs, cb as EmailTemplateResult, cc as FORM_SHELLS, cd as FieldAdminHooks, ce as FieldAdminOnChangeHook, cf as FieldAdminOnChangeHookArgs, cg as FieldAdminOptionsHook, ch as FieldAdminOptionsHookArgs, ci as FieldAdminOptionsHookResult, cj as FieldAfterReadHook, ck as FieldAfterReadHookArgs, cl as FieldBase, cm as FieldBeforeChangeHook, cn as FieldBeforeChangeHookArgs, co as FieldHook, cp as FieldHooks, cq as FileData, cr as FormShell, cs as GanttViewConfig, ct as GetDocumentResult, cu as GlobalAfterChangeHook, cv as GlobalAfterReadHook, cw as GlobalAfterReadHookEntry, cx as GlobalBeforeChangeHook, cy as GlobalBeforeChangeHookEntry, cz as GlobalBeforeReadHook, cA as GlobalBeforeReadHookEntry, cB as GlobalFormComponentSlots, cC as GlobalRAGConfig, cD as GlobalSchemaResult, cE as GlobalSummaryResult, cF as HeadingLevel, cG as HookFunction, cH as IconFieldAdmin, cI as ImageService, cJ as ImageTransformOptions, cK as InferAggregateResult, cL as JoinFieldAdmin, cM as JsonFieldAdmin, cN as JsonFormat, cO as KanbanViewConfig, cP as LIFECYCLE_EVENT_NAMES, cQ as LifecycleEventHandler, cR as LinkFormat, cS as MetricColor, cT as MoneyFieldAdmin, cU as MoneyFieldConfig, cV as MultiSelectFieldAdmin, cW as NamedAccessPolicy, cX as NavBadgeConfig, cY as NavGroup, cZ as NumberFieldAdmin, c_ as NumberFormat, c$ as NumberLimitFieldAdmin, d0 as NumberLimitFieldConfig, d1 as NumericOperation, d2 as NumericUpdateOperator, d3 as OIDCAdminAuthProvider, d4 as OptionFormat, d5 as OutboundEmail, d6 as PageAction, d7 as PageBreadcrumbItem, d8 as PageConfig, d9 as PaginatedResult, da as PinnedNavItemRef, db as PublicAdminAuthProvider, dc as QueryCollectionResult, dd as RAGSearchResult, de as RadioFieldAdmin, df as RateLimitConfig, dg as ReadonlyDatabaseAdapter, dh as RichTextFeature, di as RichTextFieldConfig, dj as SelectFieldAdmin, dk as SqlWhereResult, dl as StorageAdapter, dm as TableViewConfig, dn as TaskContext, dp as TaskLogger, dq as TaskRunStatus, dr as TextFieldAdmin, ds as TextFormat, dt as TextareaFieldAdmin, du as TrashConfig, dv as TrustProxyConfig, dw as TypedField, dx as UploadConfig, dy as UrlFieldAdmin, dz as UrlLinkValue, dA as ViewActionFeatures, dB as ViewComponentSlots, dC as ViewConfigBase, dD as ViewLayout, dE as ViewMetric, dF as ViewMetricGroupBy, dG as ViewMetricGroupByConfig, dH as ViewMetricTrendConfig, dI as ViewSubMetric, dJ as WhereClause, dK as WhereOperator, dL as WhereOperatorName, dM as WordLimitFieldAdmin, dN as WordLimitFieldConfig, dO as WorkflowMetadata, dP as WorkflowOnTransitionContext, dQ as WorkflowOnTransitionHook, dR as WorkflowRole, dS as WorkflowState, dT as WorkflowTransitionContext, dU as coerceBooleanWhere, dV as defineAction, dW as defineCalendarView, dX as defineCardsView, dY as defineCustomView, dZ as defineGanttView, d_ as defineKanbanView, d$ as definePage, e0 as defineTableView, e1 as defineView, e2 as defineWorkspace, e3 as isAICollection, e4 as parseMongoWhere, e5 as parseSqlWhere } from './app-Bg34iyUz.js';
1
+ import { F as Field, B as Block, C as CollectionConfig, D as DyrectedConfig, A as AdminAuthConfig, P as PublicAdminAuthConfig, G as GlobalConfig, a as DeclarativeHookExpression, W as WorkflowConfig, b as AuthenticatedUser, c as WorkflowTransition, L as LifecycleEventName, d as LifecycleEvent, e as DatabaseAdapter, f as BaseDocument, H as HookRequestContext, T as TaskRunResult, g as TaskConfig, h as DetailActionOptions, i as DetailAction, j as DetailComputedOptions, k as ComputedHandler, l as DetailComputed, m as DetailCustomOptions, n as DetailCustom, o as DetailDividerOptions, p as DetailDivider, q as DisplayFieldOptions, r as DetailField, s as DetailItem, t as DetailGridOptions, u as DetailGrid, v as DetailRepeatOptions, w as DetailRepeat, x as DetailSectionOptions, y as DetailSection, z as DetailTabOptions, E as DetailTab, I as DetailTabsOptions, J as DetailTabs, K as DetailTextOptions, M as DetailText, N as DetailSchema, O as DyrectedContext, Q as AIConfig, R as AIPIIConfig, S as IdStrategy, U as CompiledNavTree, V as CompiledNavItem, X as UserNavigationPreferences, Y as NavigationRolePolicy, Z as DefineWorkspaceOptions, _ as ViewConfig, $ as AdminConfig, a0 as AdminStringKey, a1 as DateTimeField, a2 as RelationshipField, a3 as SelectField, a4 as ArrayField, a5 as FieldType, a6 as BooleanField, a7 as AuthConfig, a8 as AccessPolicyResolver, a9 as DateField, aa as TextField, ab as EmailField, ac as IconField, ad as ImageField, ae as JoinField, af as JsonField, ag as MoneyField, ah as MultiSelectField, ai as NumberField, aj as ObjectField, ak as RadioField, al as RichTextField, am as RowField, an as TextareaField, ao as TimeField, ap as UrlField } from './app-Ti5woUOq.js';
2
+ export { aq as AIAction, ar as AIActionStatus, as as AIActionType, at as AIAuditActionType, au as AIAuditRecord, av as AIChunk, aw as AIMessage, ax as AIPIIPattern, ay as AIRateLimitResult, az as AIRateLimitStore, aA as AIThread, aB as AIToolContext, aC as AIToolDefinition, aD as AI_ACTIONS_COLLECTION, aE as AI_AUDIT_COLLECTION, aF as AI_CHUNKS_COLLECTION, aG as AI_MESSAGES_COLLECTION, aH as AI_THREADS_COLLECTION, aI as AccessConfig, aJ as AccessFunction, aK as AccessFunctionArgs, aL as AccessResult, aM as AccessRule, aN as ActionConfig, aO as ActionConfigBase, aP as ActionConfigWithHandler, aQ as ActionConfigWithMutation, aR as ActionContext, aS as ActionType, aT as AdminAuthAccessResolution, aU as AdminAuthClaimMapping, aV as AdminAuthMember, aW as AdminAuthProvider, aX as AdminAuthProviderMembersConfig, aY as AdminAuthProvisioningMode, aZ as AdminAuthResolveAccessArgs, a_ as AdminAuthResolvedIdentity, a$ as AdminDashboardComponentSlots, b0 as AdminIconName, b1 as AdminStrings, b2 as AggregateArgs, b3 as AggregateCastType, b4 as AggregateCollectionResult, b5 as AggregateInput, b6 as AggregateOperation, b7 as AggregateResult, b8 as AppTrashConfig, b9 as BaseAdminAuthProvider, ba as BaseFieldAdmin, bb as BlockVariant, bc as BlocksField, bd as BooleanFieldAdmin, be as BooleanFormat, bf as CURRENT_NAV_PREFERENCES_VERSION, bg as CUSTOM_VIEW_FETCH_ALL_LIMIT, bh as CalendarViewConfig, bi as CardsViewConfig, bj as CharacterLimitFieldAdmin, bk as CharacterLimitFieldConfig, bl as CollectionAfterChangeHook, bm as CollectionAfterDeleteHook, bn as CollectionAfterReadHook, bo as CollectionAfterReadHookEntry, bp as CollectionAfterRestoreHook, bq as CollectionAfterTransitionHook, br as CollectionAfterTrashHook, bs as CollectionBeforeChangeHook, bt as CollectionBeforeChangeHookEntry, bu as CollectionBeforeCommitHook, bv as CollectionBeforeDeleteHook, bw as CollectionBeforeReadHook, bx as CollectionBeforeReadHookEntry, by as CollectionBeforeRestoreHook, bz as CollectionBeforeTransitionHook, bA as CollectionBeforeTrashHook, bB as CollectionEmailConfig, bC as CollectionIndex, bD as CollectionListComponentSlots, bE as CollectionRAGConfig, bF as CollectionSchemaResult, bG as CollectionSummaryResult, bH as CollectionViewComponentSlots, bI as CompiledNavGroup, bJ as CountOperation, bK as CustomAdminAuthProvider, bL as CustomViewConfig, bM as CustomViewDataConfig, bN as DEFAULT_USER_NAV_PREFERENCES, bO as DateFieldAdmin, bP as DateFormat, bQ as DefaultValueContext, bR as DefineActionOptions, bS as DefineCalendarViewOptions, bT as DefineCardsViewOptions, bU as DefineCustomViewOptions, bV as DefineGanttViewOptions, bW as DefineKanbanViewOptions, bX as DefinePageInput, bY as DefineTableViewOptions, bZ as DefineViewBaseOptions, b_ as DefineViewOptions, b$ as DetailSpan, c0 as DisplayTone, c1 as DisplayVariant, c2 as DistinctCountOperation, c3 as DistinctValuesOperation, c4 as DocumentFormComponentSlots, c5 as DynamicOptionItem, c6 as DynamicOptionsConfig, c7 as DynamicOptionsResolver, c8 as DynamicOptionsResolverArgs, c9 as DyrectedAIContext, ca as DyrectedLoggerConfig, cb as DyrectedObservabilityConfig, cc as EmailFieldAdmin, cd as EmailTemplateArgs, ce as EmailTemplateResult, cf as FORM_SHELLS, cg as FieldAdminHooks, ch as FieldAdminOnChangeHook, ci as FieldAdminOnChangeHookArgs, cj as FieldAdminOptionsHook, ck as FieldAdminOptionsHookArgs, cl as FieldAdminOptionsHookResult, cm as FieldAfterReadHook, cn as FieldAfterReadHookArgs, co as FieldBase, cp as FieldBeforeChangeHook, cq as FieldBeforeChangeHookArgs, cr as FieldHook, cs as FieldHooks, ct as FileData, cu as FormShell, cv as GanttViewConfig, cw as GetDocumentResult, cx as GlobalAfterChangeHook, cy as GlobalAfterReadHook, cz as GlobalAfterReadHookEntry, cA as GlobalBeforeChangeHook, cB as GlobalBeforeChangeHookEntry, cC as GlobalBeforeReadHook, cD as GlobalBeforeReadHookEntry, cE as GlobalFormComponentSlots, cF as GlobalRAGConfig, cG as GlobalSchemaResult, cH as GlobalSummaryResult, cI as HeadingLevel, cJ as HookFunction, cK as IconFieldAdmin, cL as ImageService, cM as ImageTransformOptions, cN as InferAggregateResult, cO as JoinFieldAdmin, cP as JsonFieldAdmin, cQ as JsonFormat, cR as KanbanViewConfig, cS as LIFECYCLE_EVENT_NAMES, cT as LifecycleEventHandler, cU as LinkFormat, cV as MetricColor, cW as MoneyFieldAdmin, cX as MoneyFieldConfig, cY as MultiSelectFieldAdmin, cZ as NAVIGATION_POLICY_KEY, c_ as NamedAccessPolicy, c$ as NavBadgeConfig, d0 as NavGroup, d1 as NumberFieldAdmin, d2 as NumberFormat, d3 as NumberLimitFieldAdmin, d4 as NumberLimitFieldConfig, d5 as NumericOperation, d6 as NumericUpdateOperator, d7 as OIDCAdminAuthProvider, d8 as OptionFormat, d9 as OutboundEmail, da as PageAction, db as PageBreadcrumbItem, dc as PageConfig, dd as PaginatedResult, de as PinnedNavItemRef, df as PublicAdminAuthProvider, dg as QueryCollectionResult, dh as RAGSearchResult, di as RadioFieldAdmin, dj as RateLimitConfig, dk as ReadonlyDatabaseAdapter, dl as RichTextFeature, dm as RichTextFieldConfig, dn as SelectFieldAdmin, dp as SqlWhereResult, dq as StorageAdapter, dr as TableViewConfig, ds as TaskContext, dt as TaskLogger, du as TaskRunStatus, dv as TextFieldAdmin, dw as TextFormat, dx as TextareaFieldAdmin, dy as TrashConfig, dz as TrustProxyConfig, dA as TypedField, dB as UploadConfig, dC as UrlFieldAdmin, dD as UrlLinkValue, dE as ViewActionFeatures, dF as ViewComponentSlots, dG as ViewConfigBase, dH as ViewLayout, dI as ViewMetric, dJ as ViewMetricGroupBy, dK as ViewMetricGroupByConfig, dL as ViewMetricTrendConfig, dM as ViewSubMetric, dN as WhereClause, dO as WhereOperator, dP as WhereOperatorName, dQ as WordLimitFieldAdmin, dR as WordLimitFieldConfig, dS as WorkflowMetadata, dT as WorkflowOnTransitionContext, dU as WorkflowOnTransitionHook, dV as WorkflowRole, dW as WorkflowState, dX as WorkflowTransitionContext, dY as coerceBooleanWhere, dZ as defineAction, d_ as defineCalendarView, d$ as defineCardsView, e0 as defineCustomView, e1 as defineGanttView, e2 as defineKanbanView, e3 as definePage, e4 as defineTableView, e5 as defineView, e6 as defineWorkspace, e7 as isAICollection, e8 as parseMongoWhere, e9 as parseSqlWhere } from './app-Ti5woUOq.js';
3
3
  import jexl from 'jexl';
4
4
  import { Context } from 'hono';
5
5
  export { z } from 'zod';
@@ -1537,7 +1537,9 @@ interface ReconciledNavTree extends CompiledNavTree {
1537
1537
  * Reconciles developer-defined navigation tree from code (`baseTree`)
1538
1538
  * with operator personal/role preferences (`prefs`), ensuring:
1539
1539
  * 1. Sparse delta merging (newly deployed code resources are never dropped).
1540
- * 2. User-created custom groups and operational workspaces are injected.
1540
+ * 2. User-created custom groups and operational workspaces are injected. Items
1541
+ * already in the server tree keep the server's `access` and `badge`; a
1542
+ * preference can never replace them. Injected items are flagged `userDefined`.
1541
1543
  * 3. User visibility overrides (`hidden`) are respected.
1542
1544
  * 4. User ordering overrides (`groupOrder`, `itemOrder`) are applied.
1543
1545
  * 5. Pinned favorite shortcuts are extracted.
@@ -1550,12 +1552,46 @@ interface ReconcileNavigationOptions {
1550
1552
  * If false (default), hidden groups, items, and views are omitted.
1551
1553
  */
1552
1554
  includeHidden?: boolean;
1555
+ /**
1556
+ * If false, preferences can only reorder, rename, regroup or hide items that
1557
+ * already exist in the base tree. Items and pins the base tree does not contain
1558
+ * are ignored. Use this when the server has locked the user's menu to a role
1559
+ * policy. Defaults to true.
1560
+ */
1561
+ allowUserItems?: boolean;
1553
1562
  }
1554
1563
  declare function reconcileNavigation(baseTree: CompiledNavTree | null | undefined, prefs: UserNavigationPreferences | null | undefined, schemas?: {
1555
1564
  collections?: Array<any>;
1556
1565
  globals?: Array<any>;
1557
1566
  }, options?: ReconcileNavigationOptions): ReconciledNavTree;
1558
1567
 
1568
+ /**
1569
+ * Reduces a navigation preference to the parts that are safe to share as a
1570
+ * role or global default: ordering, hiding, groups, and regrouped items that
1571
+ * already exist in the server tree. Pinned shortcuts and any item the server
1572
+ * tree does not contain (the publisher's own workspaces) are dropped, so
1573
+ * publishing a layout can never smuggle the publisher's items to other users.
1574
+ */
1575
+ declare function sanitizeNavigationLayout(prefs: UserNavigationPreferences | null | undefined, tree: CompiledNavTree): UserNavigationPreferences;
1576
+ /**
1577
+ * Validates a stored or submitted role policy and returns a normalised copy,
1578
+ * or `null` when the value is not a policy.
1579
+ */
1580
+ declare function parseNavigationRolePolicy(value: unknown, tree: CompiledNavTree): NavigationRolePolicy | null;
1581
+ /**
1582
+ * Applies the role menu policies that govern a user to their access-pruned tree.
1583
+ *
1584
+ * `policies` has one entry per role the user holds: the role's policy, or `null`
1585
+ * when the role has none. The menu is locked only if every role has an enforced
1586
+ * policy; a role without one leaves the user unrestricted, because policy narrows
1587
+ * the menu and never grants access. A locked menu is the union of what the
1588
+ * enforced roles allow, arranged by the first policy's layout.
1589
+ *
1590
+ * `access` is applied before this runs, so a policy can never reveal an item the
1591
+ * user cannot access.
1592
+ */
1593
+ declare function applyNavigationRolePolicies(tree: CompiledNavTree, policies: Array<NavigationRolePolicy | null | undefined>): CompiledNavTree;
1594
+
1559
1595
  /**
1560
1596
  * System route prefixes reserved by Dyrected.
1561
1597
  * Cannot be used as operational workspace slugs.
@@ -1731,6 +1767,30 @@ declare function getDefaultEmailTemplate(purpose: "invite" | "resetPassword" | "
1731
1767
  rawHtml: string;
1732
1768
  };
1733
1769
 
1770
+ type BrandingSource = Pick<AdminConfig, "branding" | "meta" | "strings"> | null | undefined;
1771
+ /**
1772
+ * The name of the product as editors should see it.
1773
+ *
1774
+ * Resolution order: `branding.appName`, `branding.logoText`, then the name inside
1775
+ * `meta.titleSuffix` (without its leading `-` or `—`). Returns an empty string when
1776
+ * none is set. It never returns "Dyrected", so white-labelled products do not show
1777
+ * the underlying CMS name; callers decide what neutral wording to use instead.
1778
+ *
1779
+ * @example
1780
+ * resolveAppName({ meta: { titleSuffix: '— Acme Admin' } }) // 'Acme Admin'
1781
+ */
1782
+ declare function resolveAppName(admin: BrandingSource): string;
1783
+ /**
1784
+ * The text appended to every admin page `<title>`: `meta.titleSuffix` when set,
1785
+ * otherwise `- <app name>`, otherwise an empty string.
1786
+ */
1787
+ declare function resolveTitleSuffix(admin: BrandingSource): string;
1788
+ /**
1789
+ * Looks up an overridable admin string, replacing `{appName}` with the resolved app name.
1790
+ * Returns `fallback` when the host did not override the key.
1791
+ */
1792
+ declare function resolveAdminString(admin: BrandingSource, key: AdminStringKey, fallback: string): string;
1793
+
1734
1794
  type TopLevelFieldName<TFields extends readonly Field[]> = Extract<TFields[number], {
1735
1795
  name: string;
1736
1796
  }>["name"];
@@ -2390,4 +2450,4 @@ declare function defineTab<const T extends readonly Field[]>(args: {
2390
2450
  fields: T;
2391
2451
  }): T;
2392
2452
 
2393
- export { AIConfig, type AIErrorCode, AIPIIConfig, type AccessConditions, AccessPolicyResolver, AdminAuthConfig, type AdminComponentsValidationIssue, ArrayField, AuthConfig, type AuthDocFields, AuthenticatedUser, BASE62_ALPHABET, BUILTIN_JEXL_HELPERS, BaseDocument, Block, BooleanField, CROCKFORD_BASE32, CollectionConfig, CompiledNavItem, CompiledNavTree, ComputedHandler, type ConfigDiagnostic, ConfigValidationError, ConflictError, type CreatedAtFieldOptions, type CreatedByFieldOptions, DatabaseAdapter, DateField, DateTimeField, type DeclarativeAccessSurface, type DeclarativeAccessValidationIssue, type DeclarativeAdminSurface, type DeclarativeAdminValidationIssue, DeclarativeHookExpression, type DeclarativeHookSurface, type DeclarativeHookValidationIssue, type DeclarativePreviewSurface, type DeclarativePreviewValidationIssue, DefineWorkspaceOptions, DetailAction, DetailActionOptions, DetailComputed, DetailComputedOptions, DetailCustom, DetailCustomOptions, DetailDivider, DetailDividerOptions, DetailField, DetailGrid, DetailGridOptions, DetailItem, DetailRepeat, DetailRepeatOptions, DetailSchema, DetailSection, DetailSectionOptions, DetailTab, DetailTabOptions, DetailTabs, DetailTabsOptions, DetailText, DetailTextOptions, DisplayFieldOptions, DuplicateKeyError, DyrectedAIError, DyrectedConfig, DyrectedError, EMAIL_TEMPLATES_COLLECTION_CONFIG, EMAIL_TEMPLATES_COLLECTION_SLUG, EmailField, type ExprFunction, type ExtractCollectionSlugs, Field, FieldConditionBuilder, FieldType, ForbiddenError, type GenerateIdOptions, GlobalConfig, HookRequestContext, INTERNAL_SYSTEM_COLLECTIONS, IconField, IdStrategy, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, type LayoutOptions, LifecycleEvent, LifecycleEventName, MatchBuilder, MoneyField, MultiSelectField, NANOID_ALPHABET, NON_NEGOTIABLE_CREDENTIAL_FIELDS, NotFoundError, NumberField, ObjectField, PII_REGEX_PATTERNS, type Prettify, PublicAdminAuthConfig, type PublishingWorkflowOptions, RESERVED_NAVIGATION_SLUGS, RadioField, type ReconcileNavigationOptions, type ReconciledNavTree, type ReferencedComponentKeys, RelationshipField, type ResolvedTrashConfig, type RestoreDocumentOptions, RichTextField, type RoleOption, type RolesFieldOptions, RowField, type SanitizeDocForAIOptions, SelectField, type SoftDeleteOptions, type SortClause, type SortDirection, type SqlSortDialect, type SystemDocFields, TASK_LOCKS_COLLECTION, TASK_LOCKS_COLLECTION_CONFIG, TRASH_COLLECTION, TRASH_COLLECTION_CONFIG, TaskConfig, TaskRunResult, type TaskRunner, type TemplateBlock, TextField, TextareaField, TimeField, UNASSIGNED_GROUP_KEY, type UpdatedAtFieldOptions, type UpdatedByFieldOptions, type UploadDocFields, UrlField, UserNavigationPreferences, ValidationError, ViewConfig, WORKFLOW_HISTORY_COLLECTION, type WhenFunction, WorkflowConfig, WorkflowTransition, abs, addDays, alertBox, asCollection, assertValidAdminConditionsInConfig, assertValidDeclarativeAccessInConfig, assertValidDeclarativeHookExpression, assertValidDeclarativeHookResult, assertValidDeclarativeHooksInConfig, assertValidNavigationSlugs, assertValidPreviewUrlsInConfig, assertValidTrashInConfig, availableWorkflowTransitions, buildSqlOrderBy, canViewWorkflowDraft, capitalize, ceil, clamp, coalesce, collectAdminComponentsValidationIssues, collectAdminConditionValidationIssues, collectConfigDiagnostics, collectDeclarativeAccessValidationIssues, collectDeclarativeHookValidationIssues, collectPreviewUrlValidationIssues, collectReferencedComponentKeys, compact, compileNavigation, count, createLifecycleEvent, createTaskRunner, createTrashPurgeTask, createWorkflowDocument, ctaButton, defaultValue, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineCreatedAtField, defineCreatedByField, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMoneyField, defineMultiSelectField, defineNumberField, defineObjectField, definePublishingWorkflow, defineRadioField, defineRelationshipField, defineRichTextField, defineRoles, defineRolesField, defineRowField, defineSelectField, defineTab, defineTask, defineTextField, defineTextareaField, defineTimeField, defineUpdatedAtField, defineUpdatedByField, defineUrlField, detailBox, diffDays, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, displayAction, displayComputed, displayCustom, displayCustomComponent, displayDivider, displayField, displayGrid, displayRepeat, displaySection, displayTab, displayTabs, displayText, divider, emailTokens, endsWith, escapeHtml, evaluateDetailComputed, evaluateJexl, evaluateJexlSync, eventList, executeFieldAfterRead, executeFieldBeforeChange, expr, first, floor, formatConfigDiagnostics, formatDate, formatJexlValue, generateDefaultDetailSchema, generateDocumentId, generateId, generateOpenApi, get, getAdminAuthCollection, getAdminRoleForCollection, getAllowedSitesForUser, getDefaultEmailTemplate, getPublicAdminAuthConfig, getTrashEntryId, heading, includes, initializeWorkflowDocument, isConfigValidationError, isDetailItemVisible, isEmpty, isFuture, isPast, isSiteAuthorized, isUserAdmin, join, last, layout, length, lower, maskEmail, maskPhone, maskTextPII, materializeWorkflowDocument, max, min, nanoid, nextCronRun, normalizeConfig, normalizeDetailItem, normalizeGroupKey, now, numericFieldNames, paragraph, parseCron, parseSort, pow, prefixedId, pruneNavigationByAccess, pruneNavigationForUser, publishedStateName, publishingWorkflow, readingTime, reconcileNavigation, registerJexlHelpers, replace, resolveAdminAuthCollection, resolveAllCollectionViews, resolveAuthorizedSiteId, resolveBlocks, resolveDocumentTitle, resolveIndexName, resolvePrefix, resolveTrashConfig, restoreDocument, round, row, runCollectionHooks, safeLinkDetailBox, sanitizeDocForAI, saveWorkflowDraft, sectionLabel, simplePublishingWorkflow, slugify, softDeleteDocument, spacer, sqrt, startsWith, sum, table, today, transitionWorkflow, trim, truncate, ulid, unique, upper, uuid, validateAdminConditionExpression, validateDeclarativeAccessExpression, validateDeclarativeHookExpression, validatePreviewUrlExpression, when, wordCount, workflowCapabilities };
2453
+ export { AIConfig, type AIErrorCode, AIPIIConfig, type AccessConditions, AccessPolicyResolver, AdminAuthConfig, type AdminComponentsValidationIssue, AdminConfig, AdminStringKey, ArrayField, AuthConfig, type AuthDocFields, AuthenticatedUser, BASE62_ALPHABET, BUILTIN_JEXL_HELPERS, BaseDocument, Block, BooleanField, CROCKFORD_BASE32, CollectionConfig, CompiledNavItem, CompiledNavTree, ComputedHandler, type ConfigDiagnostic, ConfigValidationError, ConflictError, type CreatedAtFieldOptions, type CreatedByFieldOptions, DatabaseAdapter, DateField, DateTimeField, type DeclarativeAccessSurface, type DeclarativeAccessValidationIssue, type DeclarativeAdminSurface, type DeclarativeAdminValidationIssue, DeclarativeHookExpression, type DeclarativeHookSurface, type DeclarativeHookValidationIssue, type DeclarativePreviewSurface, type DeclarativePreviewValidationIssue, DefineWorkspaceOptions, DetailAction, DetailActionOptions, DetailComputed, DetailComputedOptions, DetailCustom, DetailCustomOptions, DetailDivider, DetailDividerOptions, DetailField, DetailGrid, DetailGridOptions, DetailItem, DetailRepeat, DetailRepeatOptions, DetailSchema, DetailSection, DetailSectionOptions, DetailTab, DetailTabOptions, DetailTabs, DetailTabsOptions, DetailText, DetailTextOptions, DisplayFieldOptions, DuplicateKeyError, DyrectedAIError, DyrectedConfig, DyrectedError, EMAIL_TEMPLATES_COLLECTION_CONFIG, EMAIL_TEMPLATES_COLLECTION_SLUG, EmailField, type ExprFunction, type ExtractCollectionSlugs, Field, FieldConditionBuilder, FieldType, ForbiddenError, type GenerateIdOptions, GlobalConfig, HookRequestContext, INTERNAL_SYSTEM_COLLECTIONS, IconField, IdStrategy, ImageField, type InferDocShape, JoinField, JsonField, LIFECYCLE_EVENTS_COLLECTION, type LayoutOptions, LifecycleEvent, LifecycleEventName, MatchBuilder, MoneyField, MultiSelectField, NANOID_ALPHABET, NON_NEGOTIABLE_CREDENTIAL_FIELDS, NavigationRolePolicy, NotFoundError, NumberField, ObjectField, PII_REGEX_PATTERNS, type Prettify, PublicAdminAuthConfig, type PublishingWorkflowOptions, RESERVED_NAVIGATION_SLUGS, RadioField, type ReconcileNavigationOptions, type ReconciledNavTree, type ReferencedComponentKeys, RelationshipField, type ResolvedTrashConfig, type RestoreDocumentOptions, RichTextField, type RoleOption, type RolesFieldOptions, RowField, type SanitizeDocForAIOptions, SelectField, type SoftDeleteOptions, type SortClause, type SortDirection, type SqlSortDialect, type SystemDocFields, TASK_LOCKS_COLLECTION, TASK_LOCKS_COLLECTION_CONFIG, TRASH_COLLECTION, TRASH_COLLECTION_CONFIG, TaskConfig, TaskRunResult, type TaskRunner, type TemplateBlock, TextField, TextareaField, TimeField, UNASSIGNED_GROUP_KEY, type UpdatedAtFieldOptions, type UpdatedByFieldOptions, type UploadDocFields, UrlField, UserNavigationPreferences, ValidationError, ViewConfig, WORKFLOW_HISTORY_COLLECTION, type WhenFunction, WorkflowConfig, WorkflowTransition, abs, addDays, alertBox, applyNavigationRolePolicies, asCollection, assertValidAdminConditionsInConfig, assertValidDeclarativeAccessInConfig, assertValidDeclarativeHookExpression, assertValidDeclarativeHookResult, assertValidDeclarativeHooksInConfig, assertValidNavigationSlugs, assertValidPreviewUrlsInConfig, assertValidTrashInConfig, availableWorkflowTransitions, buildSqlOrderBy, canViewWorkflowDraft, capitalize, ceil, clamp, coalesce, collectAdminComponentsValidationIssues, collectAdminConditionValidationIssues, collectConfigDiagnostics, collectDeclarativeAccessValidationIssues, collectDeclarativeHookValidationIssues, collectPreviewUrlValidationIssues, collectReferencedComponentKeys, compact, compileNavigation, count, createLifecycleEvent, createTaskRunner, createTrashPurgeTask, createWorkflowDocument, ctaButton, defaultValue, defineArrayField, defineBlock, defineBlocksField, defineBooleanField, defineCollection, defineConfig, defineCreatedAtField, defineCreatedByField, defineDateField, defineDateTimeField, defineEmailField, defineField, defineGlobal, defineIconField, defineImageField, defineJoinField, defineJsonField, defineMoneyField, defineMultiSelectField, defineNumberField, defineObjectField, definePublishingWorkflow, defineRadioField, defineRelationshipField, defineRichTextField, defineRoles, defineRolesField, defineRowField, defineSelectField, defineTab, defineTask, defineTextField, defineTextareaField, defineTimeField, defineUpdatedAtField, defineUpdatedByField, defineUrlField, detailBox, diffDays, dispatchLifecycleEvent, dispatchPendingLifecycleEvents, displayAction, displayComputed, displayCustom, displayCustomComponent, displayDivider, displayField, displayGrid, displayRepeat, displaySection, displayTab, displayTabs, displayText, divider, emailTokens, endsWith, escapeHtml, evaluateDetailComputed, evaluateJexl, evaluateJexlSync, eventList, executeFieldAfterRead, executeFieldBeforeChange, expr, first, floor, formatConfigDiagnostics, formatDate, formatJexlValue, generateDefaultDetailSchema, generateDocumentId, generateId, generateOpenApi, get, getAdminAuthCollection, getAdminRoleForCollection, getAllowedSitesForUser, getDefaultEmailTemplate, getPublicAdminAuthConfig, getTrashEntryId, heading, includes, initializeWorkflowDocument, isConfigValidationError, isDetailItemVisible, isEmpty, isFuture, isPast, isSiteAuthorized, isUserAdmin, join, last, layout, length, lower, maskEmail, maskPhone, maskTextPII, materializeWorkflowDocument, max, min, nanoid, nextCronRun, normalizeConfig, normalizeDetailItem, normalizeGroupKey, now, numericFieldNames, paragraph, parseCron, parseNavigationRolePolicy, parseSort, pow, prefixedId, pruneNavigationByAccess, pruneNavigationForUser, publishedStateName, publishingWorkflow, readingTime, reconcileNavigation, registerJexlHelpers, replace, resolveAdminAuthCollection, resolveAdminString, resolveAllCollectionViews, resolveAppName, resolveAuthorizedSiteId, resolveBlocks, resolveDocumentTitle, resolveIndexName, resolvePrefix, resolveTitleSuffix, resolveTrashConfig, restoreDocument, round, row, runCollectionHooks, safeLinkDetailBox, sanitizeDocForAI, sanitizeNavigationLayout, saveWorkflowDraft, sectionLabel, simplePublishingWorkflow, slugify, softDeleteDocument, spacer, sqrt, startsWith, sum, table, today, transitionWorkflow, trim, truncate, ulid, unique, upper, uuid, validateAdminConditionExpression, validateDeclarativeAccessExpression, validateDeclarativeHookExpression, validatePreviewUrlExpression, when, wordCount, workflowCapabilities };
package/dist/index.js CHANGED
@@ -1866,6 +1866,7 @@ var DyrectedAIError = class _DyrectedAIError extends Error {
1866
1866
  function defineWorkspace(options) {
1867
1867
  return options;
1868
1868
  }
1869
+ var NAVIGATION_POLICY_KEY = "admin:navigation-policy";
1869
1870
  var CURRENT_NAV_PREFERENCES_VERSION = 1;
1870
1871
  var DEFAULT_USER_NAV_PREFERENCES = {
1871
1872
  _version: CURRENT_NAV_PREFERENCES_VERSION,
@@ -4207,6 +4208,32 @@ function isUserAdmin(user, collection) {
4207
4208
  return false;
4208
4209
  }
4209
4210
 
4211
+ // src/utils/app-name.ts
4212
+ var SUFFIX_SEPARATOR = /^[\s\-\u2013\u2014|\u00b7:]+/;
4213
+ function resolveAppName(admin) {
4214
+ const candidates = [
4215
+ admin?.branding?.appName,
4216
+ admin?.branding?.logoText,
4217
+ admin?.meta?.titleSuffix?.replace(SUFFIX_SEPARATOR, "")
4218
+ ];
4219
+ for (const candidate of candidates) {
4220
+ const value = typeof candidate === "string" ? candidate.trim() : "";
4221
+ if (value) return value;
4222
+ }
4223
+ return "";
4224
+ }
4225
+ function resolveTitleSuffix(admin) {
4226
+ const explicit = admin?.meta?.titleSuffix;
4227
+ if (typeof explicit === "string") return explicit.trim();
4228
+ const name = resolveAppName(admin);
4229
+ return name ? `- ${name}` : "";
4230
+ }
4231
+ function resolveAdminString(admin, key, fallback) {
4232
+ const override = admin?.strings?.[key];
4233
+ const text = typeof override === "string" && override.trim() ? override : fallback;
4234
+ return text.replace(/\{appName\}/g, resolveAppName(admin));
4235
+ }
4236
+
4210
4237
  // src/utils/block-references.ts
4211
4238
  function mergeUniqueBlocks(blocks) {
4212
4239
  const seen = /* @__PURE__ */ new Map();
@@ -4576,7 +4603,7 @@ function layout({ preheader, title, content, footer, siteName }) {
4576
4603
  ${table(
4577
4604
  row(
4578
4605
  table(
4579
- row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${sectionLabel(siteName ?? "Dyrected")}${heading(title)}`, "padding:30px 32px 24px") + row(resolvedContent, "padding:0 32px 32px") + row(
4606
+ row("&nbsp;", `height:5px;background:${emailTokens.colors.accent};font-size:0;line-height:0`) + row(`${siteName?.trim() ? sectionLabel(siteName) : ""}${heading(title)}`, "padding:30px 32px 24px") + row(resolvedContent, "padding:0 32px 32px") + row(
4580
4607
  `${divider()}${paragraph(resolvedFooter, "20px 0 6px")}${paragraph("Privacy: this message contains account-related information; please avoid forwarding it.", "10px 0px 0px 0px", "12px")}`,
4581
4608
  "padding:0 32px 28px"
4582
4609
  ),
@@ -4591,14 +4618,15 @@ function layout({ preheader, title, content, footer, siteName }) {
4591
4618
  </html>`;
4592
4619
  }
4593
4620
  function getDefaultEmailTemplate(purpose, options) {
4594
- const siteName = options?.siteName ?? "Dyrected";
4621
+ const siteName = options?.siteName?.trim() ?? "";
4622
+ const account = siteName ? `${siteName} account` : "account";
4595
4623
  switch (purpose) {
4596
4624
  case "invite":
4597
4625
  return {
4598
- subject: `You've been invited to ${siteName}`,
4626
+ subject: siteName ? `You've been invited to ${siteName}` : "You've been invited",
4599
4627
  rawHtml: layout({
4600
4628
  siteName,
4601
- preheader: `You've been invited to join ${siteName}.`,
4629
+ preheader: siteName ? `You've been invited to join ${siteName}.` : "You've been invited to join.",
4602
4630
  title: "You've been invited",
4603
4631
  content: [
4604
4632
  paragraph("Use the invitation link below to create your account. The link expires in 7 days."),
@@ -4615,7 +4643,7 @@ function getDefaultEmailTemplate(purpose, options) {
4615
4643
  subject: "Reset your password",
4616
4644
  rawHtml: layout({
4617
4645
  siteName,
4618
- preheader: `Reset your ${siteName} password.`,
4646
+ preheader: siteName ? `Reset your ${siteName} password.` : "Reset your password.",
4619
4647
  title: "Reset your password",
4620
4648
  content: [
4621
4649
  paragraph("We received a request to reset your password. The reset link expires in 1 hour."),
@@ -4629,10 +4657,10 @@ function getDefaultEmailTemplate(purpose, options) {
4629
4657
  };
4630
4658
  case "welcome":
4631
4659
  return {
4632
- subject: `Welcome to ${siteName} \u2014 your account is ready`,
4660
+ subject: siteName ? `Welcome to ${siteName} \u2014 your account is ready` : "Welcome \u2014 your account is ready",
4633
4661
  rawHtml: layout({
4634
4662
  siteName,
4635
- preheader: `Your ${siteName} account is ready.`,
4663
+ preheader: `Your ${account} is ready.`,
4636
4664
  title: "Welcome \u2014 your account is ready",
4637
4665
  content: [paragraph("Your account has been created. You can now log in with:"), detailBox("{{email}}")],
4638
4666
  footer: "If you didn't create this account, you can safely ignore this email."
@@ -4643,7 +4671,7 @@ function getDefaultEmailTemplate(purpose, options) {
4643
4671
  subject: "Your password has been changed",
4644
4672
  rawHtml: layout({
4645
4673
  siteName,
4646
- preheader: `Your ${siteName} password was changed.`,
4674
+ preheader: siteName ? `Your ${siteName} password was changed.` : "Your password was changed.",
4647
4675
  title: "Password changed",
4648
4676
  content: [
4649
4677
  paragraph("The password for your account has been changed."),
@@ -4654,12 +4682,12 @@ function getDefaultEmailTemplate(purpose, options) {
4654
4682
  };
4655
4683
  default:
4656
4684
  return {
4657
- subject: `Notification from ${siteName}`,
4685
+ subject: siteName ? `Notification from ${siteName}` : "Account notification",
4658
4686
  rawHtml: layout({
4659
4687
  siteName,
4660
- preheader: `Account notification from ${siteName}.`,
4688
+ preheader: siteName ? `Account notification from ${siteName}.` : "Account notification.",
4661
4689
  title: "Account Notification",
4662
- content: [paragraph(`This is an account notification from ${siteName}.`)],
4690
+ content: [paragraph(siteName ? `This is an account notification from ${siteName}.` : "This is an account notification.")],
4663
4691
  footer: "If you have any questions, please reach out to our team."
4664
4692
  })
4665
4693
  };
@@ -4895,7 +4923,7 @@ var EMAIL_TEMPLATES_COLLECTION_CONFIG = {
4895
4923
  beforeChange: [
4896
4924
  async ({ data, req }) => {
4897
4925
  if (data && data.format === "html") {
4898
- const siteName = req?.config?.admin?.branding?.logoText || "Dyrected";
4926
+ const siteName = resolveAppName(req?.config?.admin);
4899
4927
  const defaults = getDefaultEmailTemplate(data.purpose || "invite", { siteName });
4900
4928
  if (!data.subject) {
4901
4929
  data.subject = defaults.subject;
@@ -5768,12 +5796,13 @@ function getGlobalTag(global) {
5768
5796
  return `Global: ${global.label || global.slug}`;
5769
5797
  }
5770
5798
  function generateOpenApi(config) {
5799
+ const appName = resolveAppName(config.admin);
5771
5800
  const spec = {
5772
5801
  openapi: "3.0.0",
5773
5802
  info: {
5774
- title: "Dyrected API",
5803
+ title: appName ? `${appName} API` : "API",
5775
5804
  version: "1.0.0",
5776
- description: "Automatically generated OpenAPI specification for the Dyrected project."
5805
+ description: appName ? `Automatically generated OpenAPI specification for ${appName}.` : "Automatically generated OpenAPI specification for this project."
5777
5806
  },
5778
5807
  components: {
5779
5808
  schemas: {
@@ -5884,7 +5913,7 @@ function generateOpenApi(config) {
5884
5913
  spec.paths["/api/schemas"] = {
5885
5914
  get: {
5886
5915
  tags: ["System"],
5887
- summary: "Get the serialized Dyrected schema",
5916
+ summary: "Get the serialized schema",
5888
5917
  security: [],
5889
5918
  responses: {
5890
5919
  200: { description: "Collection and global schema definitions" }
@@ -7982,6 +8011,7 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
7982
8011
  };
7983
8012
  }
7984
8013
  const includeHidden = options?.includeHidden ?? false;
8014
+ const allowUserItems = options?.allowUserItems ?? true;
7985
8015
  const effectivePrefs = prefs || DEFAULT_USER_NAV_PREFERENCES;
7986
8016
  const hiddenSet = new Set(effectivePrefs.hidden || []);
7987
8017
  const knownCollectionSlugs = new Set((schemas?.collections || []).map((c) => c.slug));
@@ -8013,8 +8043,14 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8013
8043
  });
8014
8044
  }
8015
8045
  }
8046
+ const ungroupedKeys = /* @__PURE__ */ new Set();
8047
+ for (const item of baseTree.ungrouped || []) {
8048
+ ungroupedKeys.add(item.id);
8049
+ if (item.slug) ungroupedKeys.add(item.slug);
8050
+ }
8016
8051
  for (const itemOpt of effectivePrefs.items || []) {
8017
8052
  const itemId = itemOpt.slug ? `workspace_${itemOpt.slug}` : itemOpt.collection ? `collection_${itemOpt.collection}` : `custom_${Math.random()}`;
8053
+ if (ungroupedKeys.has(itemId) || itemOpt.slug && ungroupedKeys.has(itemOpt.slug)) continue;
8018
8054
  let existingItem;
8019
8055
  let existingParentGroup;
8020
8056
  for (const g of groupsList) {
@@ -8025,6 +8061,7 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8025
8061
  break;
8026
8062
  }
8027
8063
  }
8064
+ if (!existingItem && !allowUserItems) continue;
8028
8065
  const targetGroup = typeof itemOpt.group === "object" ? itemOpt.group?.name : itemOpt.group || existingParentGroup?.name || "Custom";
8029
8066
  const groupKey = targetGroup;
8030
8067
  let group = findGroup(groupKey);
@@ -8054,7 +8091,7 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8054
8091
  const baseViews = existingItem?.views || [];
8055
8092
  const userViews = (itemOpt.views || []).map((uv) => {
8056
8093
  const bv = baseViews.find((b) => b.slug === uv.slug);
8057
- if (!bv) return uv;
8094
+ if (!bv) return { ...uv, access: void 0 };
8058
8095
  const metricsScope = uv.metricsScope ?? bv.metricsScope;
8059
8096
  let metrics = uv.metrics;
8060
8097
  if (Array.isArray(uv.metrics) && Array.isArray(bv.metrics)) {
@@ -8075,6 +8112,7 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8075
8112
  return {
8076
8113
  ...bv,
8077
8114
  ...uv,
8115
+ access: bv.access,
8078
8116
  metricsScope,
8079
8117
  metrics
8080
8118
  };
@@ -8111,10 +8149,12 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8111
8149
  group: group.name,
8112
8150
  order: itemOpt.order ?? existingItem?.order ?? 100,
8113
8151
  views: mergedViews,
8114
- collection: itemOpt.collection || existingItem?.collection,
8115
- global: itemOpt.global || existingItem?.global,
8116
- badge: itemOpt.badge || existingItem?.badge,
8117
- access: itemOpt.access || existingItem?.access
8152
+ collection: existingItem?.collection || itemOpt.collection,
8153
+ global: existingItem?.global || itemOpt.global,
8154
+ // Access and badges are policy/server-computed: only the server tree may set them.
8155
+ badge: existingItem?.badge,
8156
+ access: existingItem?.access,
8157
+ ...existingItem ? existingItem.userDefined ? { userDefined: true } : {} : { userDefined: true }
8118
8158
  };
8119
8159
  if (existingIndex >= 0) {
8120
8160
  group.items[existingIndex] = compiledItem;
@@ -8213,7 +8253,7 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8213
8253
  label: pin.label || match.label,
8214
8254
  icon: pin.icon || match.icon
8215
8255
  });
8216
- } else {
8256
+ } else if (allowUserItems) {
8217
8257
  pinnedItems.push({
8218
8258
  id: `pin_${pin.slug}`,
8219
8259
  type: pin.type,
@@ -8232,6 +8272,61 @@ function reconcileNavigation(baseTree, prefs, schemas, options) {
8232
8272
  };
8233
8273
  }
8234
8274
 
8275
+ // src/utils/navigation-policy.ts
8276
+ var isStringArray = (value) => Array.isArray(value) && value.every((v) => typeof v === "string");
8277
+ var itemKeys = (item) => [item.id, ...item.slug ? [item.slug] : []];
8278
+ var allTreeItems = (tree) => [
8279
+ ...tree.groups.flatMap((g) => g.items),
8280
+ ...tree.ungrouped
8281
+ ];
8282
+ function sanitizeNavigationLayout(prefs, tree) {
8283
+ const known = new Set(allTreeItems(tree).flatMap(itemKeys));
8284
+ return {
8285
+ _version: CURRENT_NAV_PREFERENCES_VERSION,
8286
+ hidden: isStringArray(prefs?.hidden) ? prefs.hidden : [],
8287
+ groupOrder: isStringArray(prefs?.groupOrder) ? prefs.groupOrder : [],
8288
+ itemOrder: Object.fromEntries(
8289
+ Object.entries(prefs?.itemOrder ?? {}).filter(([, order]) => isStringArray(order))
8290
+ ),
8291
+ groups: Array.isArray(prefs?.groups) ? prefs.groups : [],
8292
+ items: (Array.isArray(prefs?.items) ? prefs.items : []).filter((item) => {
8293
+ if (item.slug) return known.has(item.slug) || known.has(`workspace_${item.slug}`);
8294
+ if (item.collection) return known.has(`collection_${item.collection}`);
8295
+ if (item.global) return known.has(`global_${item.global}`);
8296
+ return false;
8297
+ })
8298
+ };
8299
+ }
8300
+ function parseNavigationRolePolicy(value, tree) {
8301
+ if (!value || typeof value !== "object") return null;
8302
+ const raw = value;
8303
+ if (typeof raw.enforced !== "boolean" || !isStringArray(raw.allowed)) return null;
8304
+ const layout2 = sanitizeNavigationLayout(raw.layout, tree);
8305
+ return {
8306
+ _version: CURRENT_NAV_PREFERENCES_VERSION,
8307
+ enforced: raw.enforced,
8308
+ allowed: raw.allowed,
8309
+ layout: {
8310
+ _version: layout2._version,
8311
+ hidden: layout2.hidden,
8312
+ groupOrder: layout2.groupOrder,
8313
+ itemOrder: layout2.itemOrder,
8314
+ groups: layout2.groups,
8315
+ items: layout2.items
8316
+ }
8317
+ };
8318
+ }
8319
+ function applyNavigationRolePolicies(tree, policies) {
8320
+ const enforced = policies.filter((p) => !!p?.enforced);
8321
+ if (policies.length === 0 || enforced.length !== policies.length) return tree;
8322
+ const allowed = new Set(enforced.flatMap((p) => p.allowed));
8323
+ const isAllowed = (item) => itemKeys(item).some((key) => allowed.has(key));
8324
+ const groups = tree.groups.map((g) => ({ ...g, items: g.items.filter(isAllowed) })).filter((g) => g.items.length > 0);
8325
+ const filtered = { groups, ungrouped: tree.ungrouped.filter(isAllowed) };
8326
+ const reconciled = reconcileNavigation(filtered, enforced[0].layout, void 0, { allowUserItems: false });
8327
+ return { groups: reconciled.groups, ungrouped: reconciled.ungrouped, locked: true };
8328
+ }
8329
+
8235
8330
  // src/utils/navigation.ts
8236
8331
  var RESERVED_NAVIGATION_SLUGS = /* @__PURE__ */ new Set([
8237
8332
  "collections",
@@ -8879,6 +8974,7 @@ export {
8879
8974
  LIFECYCLE_EVENT_NAMES,
8880
8975
  MatchBuilder,
8881
8976
  NANOID_ALPHABET,
8977
+ NAVIGATION_POLICY_KEY,
8882
8978
  NON_NEGOTIABLE_CREDENTIAL_FIELDS,
8883
8979
  NotFoundError,
8884
8980
  PII_REGEX_PATTERNS,
@@ -8893,6 +8989,7 @@ export {
8893
8989
  abs,
8894
8990
  addDays,
8895
8991
  alertBox,
8992
+ applyNavigationRolePolicies,
8896
8993
  asCollection,
8897
8994
  assertValidAdminConditionsInConfig,
8898
8995
  assertValidDeclarativeAccessInConfig,
@@ -9048,6 +9145,7 @@ export {
9048
9145
  paragraph,
9049
9146
  parseCron,
9050
9147
  parseMongoWhere,
9148
+ parseNavigationRolePolicy,
9051
9149
  parseSort,
9052
9150
  parseSqlWhere,
9053
9151
  pow,
@@ -9061,12 +9159,15 @@ export {
9061
9159
  registerJexlHelpers,
9062
9160
  replace,
9063
9161
  resolveAdminAuthCollection,
9162
+ resolveAdminString,
9064
9163
  resolveAllCollectionViews,
9164
+ resolveAppName,
9065
9165
  resolveAuthorizedSiteId,
9066
9166
  resolveBlocks,
9067
9167
  resolveDocumentTitle,
9068
9168
  resolveIndexName,
9069
9169
  resolvePrefix,
9170
+ resolveTitleSuffix,
9070
9171
  resolveTrashConfig,
9071
9172
  restoreDocument,
9072
9173
  round,
@@ -9074,6 +9175,7 @@ export {
9074
9175
  runCollectionHooks,
9075
9176
  safeLinkDetailBox,
9076
9177
  sanitizeDocForAI,
9178
+ sanitizeNavigationLayout,
9077
9179
  saveWorkflowDraft,
9078
9180
  sectionLabel,
9079
9181
  simplePublishingWorkflow,