@dyrected/core 2.16.0 → 2.17.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.cjs CHANGED
@@ -1792,8 +1792,10 @@ __export(index_exports, {
1792
1792
  BASE62_ALPHABET: () => BASE62_ALPHABET,
1793
1793
  BUILTIN_JEXL_HELPERS: () => BUILTIN_JEXL_HELPERS,
1794
1794
  CROCKFORD_BASE32: () => CROCKFORD_BASE32,
1795
+ CURRENT_NAV_PREFERENCES_VERSION: () => CURRENT_NAV_PREFERENCES_VERSION,
1795
1796
  ConfigValidationError: () => ConfigValidationError,
1796
1797
  ConflictError: () => ConflictError,
1798
+ DEFAULT_USER_NAV_PREFERENCES: () => DEFAULT_USER_NAV_PREFERENCES,
1797
1799
  DuplicateKeyError: () => DuplicateKeyError,
1798
1800
  DyrectedAIError: () => DyrectedAIError,
1799
1801
  DyrectedError: () => DyrectedError,
@@ -1806,6 +1808,7 @@ __export(index_exports, {
1806
1808
  NON_NEGOTIABLE_CREDENTIAL_FIELDS: () => NON_NEGOTIABLE_CREDENTIAL_FIELDS,
1807
1809
  NotFoundError: () => NotFoundError,
1808
1810
  PII_REGEX_PATTERNS: () => PII_REGEX_PATTERNS,
1811
+ RESERVED_NAVIGATION_SLUGS: () => RESERVED_NAVIGATION_SLUGS,
1809
1812
  ValidationError: () => ValidationError,
1810
1813
  WORKFLOW_HISTORY_COLLECTION: () => WORKFLOW_HISTORY_COLLECTION,
1811
1814
  abs: () => abs,
@@ -1816,6 +1819,7 @@ __export(index_exports, {
1816
1819
  assertValidDeclarativeHookExpression: () => assertValidDeclarativeHookExpression,
1817
1820
  assertValidDeclarativeHookResult: () => assertValidDeclarativeHookResult,
1818
1821
  assertValidDeclarativeHooksInConfig: () => assertValidDeclarativeHooksInConfig,
1822
+ assertValidNavigationSlugs: () => assertValidNavigationSlugs,
1819
1823
  assertValidPreviewUrlsInConfig: () => assertValidPreviewUrlsInConfig,
1820
1824
  availableWorkflowTransitions: () => availableWorkflowTransitions,
1821
1825
  buildSqlOrderBy: () => buildSqlOrderBy,
@@ -1830,6 +1834,7 @@ __export(index_exports, {
1830
1834
  collectDeclarativeHookValidationIssues: () => collectDeclarativeHookValidationIssues,
1831
1835
  collectPreviewUrlValidationIssues: () => collectPreviewUrlValidationIssues,
1832
1836
  compact: () => compact,
1837
+ compileNavigation: () => compileNavigation,
1833
1838
  count: () => count,
1834
1839
  createLifecycleEvent: () => createLifecycleEvent,
1835
1840
  createWorkflowDocument: () => createWorkflowDocument,
@@ -1870,6 +1875,7 @@ __export(index_exports, {
1870
1875
  defineTimeField: () => defineTimeField,
1871
1876
  defineUrlField: () => defineUrlField,
1872
1877
  defineView: () => defineView,
1878
+ defineWorkspace: () => defineWorkspace,
1873
1879
  diffDays: () => diffDays,
1874
1880
  dispatchLifecycleEvent: () => dispatchLifecycleEvent,
1875
1881
  dispatchPendingLifecycleEvents: () => dispatchPendingLifecycleEvents,
@@ -1936,9 +1942,11 @@ __export(index_exports, {
1936
1942
  parseSqlWhere: () => parseSqlWhere,
1937
1943
  pow: () => pow,
1938
1944
  prefixedId: () => prefixedId,
1945
+ pruneNavigationForUser: () => pruneNavigationForUser,
1939
1946
  publishedStateName: () => publishedStateName,
1940
1947
  publishingWorkflow: () => publishingWorkflow,
1941
1948
  readingTime: () => readingTime,
1949
+ reconcileNavigation: () => reconcileNavigation,
1942
1950
  registerJexlHelpers: () => registerJexlHelpers,
1943
1951
  replace: () => replace,
1944
1952
  resolveAdminAuthCollection: () => resolveAdminAuthCollection,
@@ -2042,6 +2050,21 @@ var DyrectedAIError = class _DyrectedAIError extends Error {
2042
2050
  }
2043
2051
  };
2044
2052
 
2053
+ // src/types/navigation.ts
2054
+ function defineWorkspace(options) {
2055
+ return options;
2056
+ }
2057
+ var CURRENT_NAV_PREFERENCES_VERSION = 1;
2058
+ var DEFAULT_USER_NAV_PREFERENCES = {
2059
+ _version: CURRENT_NAV_PREFERENCES_VERSION,
2060
+ pinned: [],
2061
+ hidden: [],
2062
+ groupOrder: [],
2063
+ itemOrder: {},
2064
+ groups: [],
2065
+ items: []
2066
+ };
2067
+
2045
2068
  // src/auth/token.ts
2046
2069
  var import_jose = require("jose");
2047
2070
 
@@ -3918,7 +3941,33 @@ function normalizeConfig(config) {
3918
3941
  }
3919
3942
  ];
3920
3943
  }
3944
+ if (!existingFieldNames.has("__preferences")) {
3945
+ fields = [
3946
+ ...fields,
3947
+ {
3948
+ name: "__preferences",
3949
+ type: "json",
3950
+ label: "Preferences",
3951
+ promoted: true,
3952
+ admin: {
3953
+ hidden: true,
3954
+ readOnly: true
3955
+ }
3956
+ }
3957
+ ];
3958
+ }
3921
3959
  fields = fields.map((field) => {
3960
+ if (field.name === "__preferences") {
3961
+ return {
3962
+ ...field,
3963
+ promoted: true,
3964
+ admin: {
3965
+ ...field.admin || {},
3966
+ hidden: true,
3967
+ readOnly: true
3968
+ }
3969
+ };
3970
+ }
3922
3971
  if (field.name === "email") {
3923
3972
  return {
3924
3973
  ...field,
@@ -6725,6 +6774,525 @@ function sanitizeDocForAI(options) {
6725
6774
  return result;
6726
6775
  }
6727
6776
 
6777
+ // src/utils/navigation-reconciler.ts
6778
+ var normalizeKey = (val) => (val || "").toLowerCase().replace(/[^a-z0-9]/g, "-");
6779
+ function reconcileNavigation(baseTree, prefs, schemas, options) {
6780
+ if (!baseTree) {
6781
+ return {
6782
+ groups: [],
6783
+ ungrouped: [],
6784
+ pinnedItems: []
6785
+ };
6786
+ }
6787
+ const includeHidden = options?.includeHidden ?? false;
6788
+ const effectivePrefs = prefs || DEFAULT_USER_NAV_PREFERENCES;
6789
+ const hiddenSet = new Set(effectivePrefs.hidden || []);
6790
+ const knownCollectionSlugs = new Set((schemas?.collections || []).map((c) => c.slug));
6791
+ const knownGlobalSlugs = new Set((schemas?.globals || []).map((g) => g.slug));
6792
+ const groupsList = [];
6793
+ const findGroup = (query) => {
6794
+ const qNorm = normalizeKey(query);
6795
+ return groupsList.find(
6796
+ (g) => g.id === query || g.slug === query || g.name === query || normalizeKey(g.id) === qNorm || normalizeKey(g.slug) === qNorm || normalizeKey(g.name) === qNorm
6797
+ );
6798
+ };
6799
+ for (const g of baseTree.groups || []) {
6800
+ groupsList.push({
6801
+ ...g,
6802
+ items: (g.items || []).map((item) => ({ ...item }))
6803
+ });
6804
+ }
6805
+ for (const ug of effectivePrefs.groups || []) {
6806
+ const existing = findGroup(ug.slug || ug.name);
6807
+ if (!existing) {
6808
+ groupsList.push({
6809
+ id: ug.slug ? `group_${ug.slug}` : `group_${normalizeKey(ug.name)}`,
6810
+ name: ug.name,
6811
+ slug: ug.slug || normalizeKey(ug.name),
6812
+ icon: ug.icon || "Folder",
6813
+ defaultExpanded: ug.defaultExpanded ?? true,
6814
+ order: ug.order ?? 100,
6815
+ items: []
6816
+ });
6817
+ }
6818
+ }
6819
+ for (const itemOpt of effectivePrefs.items || []) {
6820
+ const targetGroup = typeof itemOpt.group === "object" ? itemOpt.group?.name : itemOpt.group;
6821
+ const groupKey = targetGroup || "Custom";
6822
+ let group = findGroup(groupKey);
6823
+ if (!group) {
6824
+ group = {
6825
+ id: `group_${normalizeKey(groupKey)}`,
6826
+ name: groupKey,
6827
+ slug: normalizeKey(groupKey),
6828
+ icon: "Folder",
6829
+ defaultExpanded: true,
6830
+ order: 99,
6831
+ items: []
6832
+ };
6833
+ groupsList.push(group);
6834
+ }
6835
+ const itemId = itemOpt.slug ? `workspace_${itemOpt.slug}` : itemOpt.collection ? `collection_${itemOpt.collection}` : `custom_${Math.random()}`;
6836
+ for (const otherGroup of groupsList) {
6837
+ if (otherGroup !== group) {
6838
+ otherGroup.items = otherGroup.items.filter((i) => i.slug !== itemOpt.slug && i.id !== itemId);
6839
+ }
6840
+ }
6841
+ const existingIndex = group.items.findIndex((i) => i.id === itemId || i.slug === itemOpt.slug);
6842
+ const existingItem = existingIndex >= 0 ? group.items[existingIndex] : void 0;
6843
+ const col = schemas?.collections?.find((c) => c.slug === itemOpt.collection);
6844
+ const colViews = col?.views || [];
6845
+ const baseViews = existingItem?.views || [];
6846
+ const userViews = itemOpt.views || [];
6847
+ const userViewSlugs = new Set(userViews.map((v) => v.slug));
6848
+ let mergedViews = [
6849
+ ...baseViews.filter((v) => !userViewSlugs.has(v.slug)),
6850
+ ...userViews
6851
+ ];
6852
+ if (mergedViews.length === 0 && itemOpt.collection) {
6853
+ if (colViews.length > 0) {
6854
+ mergedViews = [...colViews];
6855
+ } else {
6856
+ mergedViews = [
6857
+ {
6858
+ slug: "default",
6859
+ label: col?.label || col?.labels?.plural || itemOpt.label || "All Records",
6860
+ layout: "table",
6861
+ collection: itemOpt.collection
6862
+ }
6863
+ ];
6864
+ }
6865
+ }
6866
+ mergedViews = mergedViews.map((v) => ({
6867
+ ...v,
6868
+ collection: v.collection || itemOpt.collection || existingItem?.collection
6869
+ }));
6870
+ const compiledItem = {
6871
+ id: itemId,
6872
+ type: itemOpt.slug ? "workspace" : itemOpt.collection ? "collection" : itemOpt.global ? "global" : "workspace",
6873
+ slug: itemOpt.slug || itemOpt.collection || itemOpt.global || "item",
6874
+ label: itemOpt.label || itemOpt.slug || "Item",
6875
+ icon: itemOpt.icon || existingItem?.icon || col?.admin?.icon || "Briefcase",
6876
+ group: group.name,
6877
+ order: itemOpt.order ?? existingItem?.order ?? 100,
6878
+ views: mergedViews,
6879
+ collection: itemOpt.collection || existingItem?.collection,
6880
+ global: itemOpt.global || existingItem?.global,
6881
+ badge: itemOpt.badge || existingItem?.badge,
6882
+ access: itemOpt.access || existingItem?.access
6883
+ };
6884
+ if (existingIndex >= 0) {
6885
+ group.items[existingIndex] = compiledItem;
6886
+ } else {
6887
+ group.items.push(compiledItem);
6888
+ }
6889
+ }
6890
+ const allAvailableItems = [];
6891
+ const reconciledGroups = [];
6892
+ for (const group of groupsList) {
6893
+ const isGroupHidden = hiddenSet.has(group.id) || !!group.slug && hiddenSet.has(group.slug) || hiddenSet.has(group.name);
6894
+ if (!includeHidden && isGroupHidden) {
6895
+ continue;
6896
+ }
6897
+ const visibleItems = [];
6898
+ for (const item of group.items) {
6899
+ const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
6900
+ if (!includeHidden && isItemHidden) {
6901
+ continue;
6902
+ }
6903
+ const allViews = item.views || [];
6904
+ const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
6905
+ const compoundKey = `${item.slug}_${v.slug}`;
6906
+ return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
6907
+ });
6908
+ let isTombstone = false;
6909
+ if (schemas && schemas.collections && schemas.collections.length > 0) {
6910
+ if (item.collection && !knownCollectionSlugs.has(item.collection)) {
6911
+ isTombstone = true;
6912
+ }
6913
+ }
6914
+ if (schemas && schemas.globals && schemas.globals.length > 0) {
6915
+ if (item.global && !knownGlobalSlugs.has(item.global)) {
6916
+ isTombstone = true;
6917
+ }
6918
+ }
6919
+ const processedItem = {
6920
+ ...item,
6921
+ views: visibleViews,
6922
+ ...isTombstone ? { isTombstone: true } : {}
6923
+ };
6924
+ visibleItems.push(processedItem);
6925
+ allAvailableItems.push(processedItem);
6926
+ }
6927
+ const groupKey = group.slug || group.name;
6928
+ const itemOrderList = effectivePrefs.itemOrder?.[group.id] || effectivePrefs.itemOrder?.[groupKey];
6929
+ if (itemOrderList && itemOrderList.length > 0) {
6930
+ visibleItems.sort((a, b) => {
6931
+ const aIdx = itemOrderList.indexOf(a.id) >= 0 ? itemOrderList.indexOf(a.id) : a.slug ? itemOrderList.indexOf(a.slug) : -1;
6932
+ const bIdx = itemOrderList.indexOf(b.id) >= 0 ? itemOrderList.indexOf(b.id) : b.slug ? itemOrderList.indexOf(b.slug) : -1;
6933
+ if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
6934
+ if (aIdx >= 0) return -1;
6935
+ if (bIdx >= 0) return 1;
6936
+ return (a.order ?? 100) - (b.order ?? 100);
6937
+ });
6938
+ } else {
6939
+ visibleItems.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
6940
+ }
6941
+ reconciledGroups.push({
6942
+ ...group,
6943
+ items: visibleItems
6944
+ });
6945
+ }
6946
+ if (effectivePrefs.groupOrder && effectivePrefs.groupOrder.length > 0) {
6947
+ reconciledGroups.sort((a, b) => {
6948
+ const aIdx = effectivePrefs.groupOrder.indexOf(a.id) >= 0 ? effectivePrefs.groupOrder.indexOf(a.id) : a.slug ? effectivePrefs.groupOrder.indexOf(a.slug) : effectivePrefs.groupOrder.indexOf(a.name);
6949
+ const bIdx = effectivePrefs.groupOrder.indexOf(b.id) >= 0 ? effectivePrefs.groupOrder.indexOf(b.id) : b.slug ? effectivePrefs.groupOrder.indexOf(b.slug) : effectivePrefs.groupOrder.indexOf(b.name);
6950
+ if (aIdx >= 0 && bIdx >= 0) return aIdx - bIdx;
6951
+ if (aIdx >= 0) return -1;
6952
+ if (bIdx >= 0) return 1;
6953
+ return (a.order ?? 100) - (b.order ?? 100);
6954
+ });
6955
+ } else {
6956
+ reconciledGroups.sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
6957
+ }
6958
+ const reconciledUngrouped = (baseTree.ungrouped || []).filter((item) => {
6959
+ const isItemHidden = hiddenSet.has(item.id) || !!item.slug && hiddenSet.has(item.slug);
6960
+ return includeHidden || !isItemHidden;
6961
+ }).map((item) => {
6962
+ const allViews = item.views || [];
6963
+ const visibleViews = includeHidden ? allViews : allViews.filter((v) => {
6964
+ const compoundKey = `${item.slug}_${v.slug}`;
6965
+ return !hiddenSet.has(compoundKey) && !hiddenSet.has(v.slug);
6966
+ });
6967
+ const processed = { ...item, views: visibleViews };
6968
+ allAvailableItems.push(processed);
6969
+ return processed;
6970
+ });
6971
+ const pinnedItems = [];
6972
+ for (const pin of effectivePrefs.pinned || []) {
6973
+ const match = allAvailableItems.find((i) => i.slug === pin.slug || i.id === pin.slug);
6974
+ if (match) {
6975
+ pinnedItems.push({
6976
+ ...match,
6977
+ id: `pin_${match.id}`,
6978
+ label: pin.label || match.label,
6979
+ icon: pin.icon || match.icon
6980
+ });
6981
+ } else {
6982
+ pinnedItems.push({
6983
+ id: `pin_${pin.slug}`,
6984
+ type: pin.type,
6985
+ slug: pin.slug,
6986
+ label: pin.label || pin.slug,
6987
+ icon: pin.icon || "Star",
6988
+ order: 0,
6989
+ views: []
6990
+ });
6991
+ }
6992
+ }
6993
+ return {
6994
+ groups: reconciledGroups,
6995
+ ungrouped: reconciledUngrouped,
6996
+ pinnedItems
6997
+ };
6998
+ }
6999
+
7000
+ // src/utils/navigation.ts
7001
+ var RESERVED_NAVIGATION_SLUGS = /* @__PURE__ */ new Set([
7002
+ "collections",
7003
+ "globals",
7004
+ "setup",
7005
+ "api",
7006
+ "login",
7007
+ "logout",
7008
+ "settings",
7009
+ "admin",
7010
+ "preferences",
7011
+ "auth"
7012
+ ]);
7013
+ function assertValidNavigationSlugs(navigation) {
7014
+ for (const item of navigation) {
7015
+ if (item.slug) {
7016
+ const normalizedSlug = item.slug.toLowerCase().trim().replace(/^\//, "");
7017
+ if (RESERVED_NAVIGATION_SLUGS.has(normalizedSlug)) {
7018
+ throw new Error(
7019
+ `[Dyrected Config] The slug '${item.slug}' in navigation item '${item.label || item.slug}' is a reserved system path and cannot be used as an operational workspace slug.`
7020
+ );
7021
+ }
7022
+ }
7023
+ }
7024
+ }
7025
+ function resolveGroupMeta(groupProp, defaultGroupName = "Collections") {
7026
+ if (!groupProp) {
7027
+ return { name: defaultGroupName };
7028
+ }
7029
+ if (typeof groupProp === "string") {
7030
+ return { name: groupProp };
7031
+ }
7032
+ return { name: groupProp.name, meta: groupProp };
7033
+ }
7034
+ function compileNavigation(config) {
7035
+ const collections = config.collections || [];
7036
+ const globals = config.globals || [];
7037
+ const explicitNav = config.admin?.navigation || [];
7038
+ assertValidNavigationSlugs(explicitNav);
7039
+ const collectionsBySlug = /* @__PURE__ */ new Map();
7040
+ for (const col of collections) {
7041
+ collectionsBySlug.set(col.slug, col);
7042
+ }
7043
+ const globalsBySlug = /* @__PURE__ */ new Map();
7044
+ for (const gl of globals) {
7045
+ globalsBySlug.set(gl.slug, gl);
7046
+ }
7047
+ const mentionedCollections = /* @__PURE__ */ new Set();
7048
+ const mentionedGlobals = /* @__PURE__ */ new Set();
7049
+ const injectedViewsByCollection = /* @__PURE__ */ new Map();
7050
+ for (const item of explicitNav) {
7051
+ if (item.addToCollection) {
7052
+ const existing = injectedViewsByCollection.get(item.addToCollection) || [];
7053
+ if (item.views && item.views.length > 0) {
7054
+ injectedViewsByCollection.set(item.addToCollection, [...existing, ...item.views]);
7055
+ }
7056
+ }
7057
+ if (item.collection) mentionedCollections.add(item.collection);
7058
+ if (item.global) mentionedGlobals.add(item.global);
7059
+ }
7060
+ const groupMetadataMap = /* @__PURE__ */ new Map();
7061
+ const itemsByGroup = /* @__PURE__ */ new Map();
7062
+ const ungroupedItems = [];
7063
+ const registerItem = (groupName, item) => {
7064
+ if (!groupName) {
7065
+ ungroupedItems.push(item);
7066
+ } else {
7067
+ const list = itemsByGroup.get(groupName) || [];
7068
+ list.push(item);
7069
+ itemsByGroup.set(groupName, list);
7070
+ }
7071
+ };
7072
+ for (const item of explicitNav) {
7073
+ if (item.addToCollection && !item.slug && !item.collection && !item.global && !item.dashboard && !item.href) {
7074
+ continue;
7075
+ }
7076
+ const { name: groupName, meta: groupMeta } = resolveGroupMeta(item.group, void 0);
7077
+ if (groupName && groupMeta) {
7078
+ groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
7079
+ }
7080
+ if (item.dashboard) {
7081
+ registerItem(groupName, {
7082
+ id: "dashboard",
7083
+ type: "dashboard",
7084
+ slug: "dashboard",
7085
+ label: item.label || "Dashboard",
7086
+ icon: item.icon || "LayoutDashboard",
7087
+ group: groupName,
7088
+ order: item.order ?? 0,
7089
+ views: [],
7090
+ access: item.access
7091
+ });
7092
+ continue;
7093
+ }
7094
+ if (item.collection) {
7095
+ const col = collectionsBySlug.get(item.collection);
7096
+ const combinedViews = [
7097
+ ...col?.views || [],
7098
+ ...injectedViewsByCollection.get(item.collection) || [],
7099
+ ...item.views || []
7100
+ ];
7101
+ registerItem(groupName, {
7102
+ id: item.slug || `collection_${item.collection}`,
7103
+ type: item.slug ? "workspace" : "collection",
7104
+ slug: item.slug || item.collection,
7105
+ collection: item.collection,
7106
+ label: item.label || col?.labels?.plural || item.collection,
7107
+ icon: item.icon || col?.admin?.icon || "Folder",
7108
+ group: groupName,
7109
+ order: item.order ?? col?.admin?.order ?? 100,
7110
+ views: combinedViews,
7111
+ badge: item.badge,
7112
+ access: item.access || col?.access?.read
7113
+ });
7114
+ continue;
7115
+ }
7116
+ if (item.global) {
7117
+ const gl = globalsBySlug.get(item.global);
7118
+ registerItem(groupName, {
7119
+ id: item.slug || `global_${item.global}`,
7120
+ type: "global",
7121
+ slug: item.slug || item.global,
7122
+ global: item.global,
7123
+ label: item.label || gl?.label || item.global,
7124
+ icon: item.icon || "Settings",
7125
+ group: groupName,
7126
+ order: item.order ?? 100,
7127
+ views: [],
7128
+ badge: item.badge,
7129
+ access: item.access
7130
+ });
7131
+ continue;
7132
+ }
7133
+ if (item.href) {
7134
+ registerItem(groupName, {
7135
+ id: `link_${item.slug || item.href}`,
7136
+ type: "link",
7137
+ slug: item.slug || "link",
7138
+ label: item.label || "Link",
7139
+ href: item.href,
7140
+ icon: item.icon || "ExternalLink",
7141
+ group: groupName,
7142
+ order: item.order ?? 100,
7143
+ views: [],
7144
+ badge: item.badge,
7145
+ access: item.access
7146
+ });
7147
+ continue;
7148
+ }
7149
+ const workspaceSlug = item.slug || "workspace";
7150
+ registerItem(groupName, {
7151
+ id: `workspace_${workspaceSlug}`,
7152
+ type: "workspace",
7153
+ slug: workspaceSlug,
7154
+ label: item.label || workspaceSlug,
7155
+ icon: item.icon || "Briefcase",
7156
+ group: groupName,
7157
+ order: item.order ?? 100,
7158
+ views: item.views || [],
7159
+ badge: item.badge,
7160
+ access: item.access
7161
+ });
7162
+ }
7163
+ for (const col of collections) {
7164
+ if (mentionedCollections.has(col.slug)) continue;
7165
+ if (col.admin?.hidden) continue;
7166
+ const groupProp = col.admin?.group;
7167
+ const { name: groupName, meta: groupMeta } = resolveGroupMeta(groupProp, "Collections");
7168
+ if (groupMeta) {
7169
+ groupMetadataMap.set(groupName, { ...groupMetadataMap.get(groupName) || {}, ...groupMeta });
7170
+ }
7171
+ const combinedViews = [
7172
+ ...col.views || [],
7173
+ ...injectedViewsByCollection.get(col.slug) || []
7174
+ ];
7175
+ registerItem(groupName, {
7176
+ id: `collection_${col.slug}`,
7177
+ type: "collection",
7178
+ slug: col.slug,
7179
+ collection: col.slug,
7180
+ label: col.labels?.plural || col.slug,
7181
+ icon: col.admin?.icon || "Folder",
7182
+ group: groupName,
7183
+ order: col?.admin?.order ?? 100,
7184
+ views: combinedViews,
7185
+ access: col.access?.read
7186
+ });
7187
+ }
7188
+ for (const gl of globals) {
7189
+ if (mentionedGlobals.has(gl.slug)) continue;
7190
+ const groupName = "Configuration";
7191
+ registerItem(groupName, {
7192
+ id: `global_${gl.slug}`,
7193
+ type: "global",
7194
+ slug: gl.slug,
7195
+ global: gl.slug,
7196
+ label: gl.label || gl.slug,
7197
+ icon: "Settings",
7198
+ group: groupName,
7199
+ order: 100,
7200
+ views: []
7201
+ });
7202
+ }
7203
+ const sortAndSpliceItems = (items) => {
7204
+ const sorted = [...items].sort((a, b) => a.order - b.order);
7205
+ const explicitMap = /* @__PURE__ */ new Map();
7206
+ for (const opt of explicitNav) {
7207
+ if (opt.slug) explicitMap.set(opt.slug, opt);
7208
+ if (opt.collection) explicitMap.set(opt.collection, opt);
7209
+ if (opt.global) explicitMap.set(opt.global, opt);
7210
+ }
7211
+ for (const opt of explicitNav) {
7212
+ const itemKey = opt.slug || opt.collection || opt.global;
7213
+ if (!itemKey) continue;
7214
+ if (opt.after) {
7215
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7216
+ const targetIdx = sorted.findIndex((i) => i.slug === opt.after);
7217
+ if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
7218
+ const [moved] = sorted.splice(itemIdx, 1);
7219
+ const newTargetIdx = sorted.findIndex((i) => i.slug === opt.after);
7220
+ sorted.splice(newTargetIdx + 1, 0, moved);
7221
+ }
7222
+ } else if (opt.before) {
7223
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7224
+ const targetIdx = sorted.findIndex((i) => i.slug === opt.before);
7225
+ if (itemIdx !== -1 && targetIdx !== -1 && itemIdx !== targetIdx) {
7226
+ const [moved] = sorted.splice(itemIdx, 1);
7227
+ const newTargetIdx = sorted.findIndex((i) => i.slug === opt.before);
7228
+ sorted.splice(newTargetIdx, 0, moved);
7229
+ }
7230
+ } else if (opt.position === "first") {
7231
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7232
+ if (itemIdx > 0) {
7233
+ const [moved] = sorted.splice(itemIdx, 1);
7234
+ sorted.unshift(moved);
7235
+ }
7236
+ } else if (opt.position === "last") {
7237
+ const itemIdx = sorted.findIndex((i) => i.slug === itemKey);
7238
+ if (itemIdx !== -1 && itemIdx < sorted.length - 1) {
7239
+ const [moved] = sorted.splice(itemIdx, 1);
7240
+ sorted.push(moved);
7241
+ }
7242
+ }
7243
+ }
7244
+ return sorted;
7245
+ };
7246
+ const compiledGroups = [];
7247
+ for (const [groupName, rawItems] of itemsByGroup.entries()) {
7248
+ const meta = groupMetadataMap.get(groupName);
7249
+ const sortedItems = sortAndSpliceItems(rawItems);
7250
+ compiledGroups.push({
7251
+ id: `group_${groupName.toLowerCase().replace(/\s+/g, "_")}`,
7252
+ name: groupName,
7253
+ slug: meta?.slug || groupName.toLowerCase().replace(/\s+/g, "-"),
7254
+ icon: meta?.icon,
7255
+ defaultExpanded: meta?.defaultExpanded ?? true,
7256
+ order: meta?.order ?? 100,
7257
+ items: sortedItems
7258
+ });
7259
+ }
7260
+ compiledGroups.sort((a, b) => a.order - b.order);
7261
+ return {
7262
+ groups: compiledGroups,
7263
+ ungrouped: sortAndSpliceItems(ungroupedItems)
7264
+ };
7265
+ }
7266
+ function pruneNavigationForUser(tree, user) {
7267
+ const userRoles = /* @__PURE__ */ new Set();
7268
+ if (user?.role) userRoles.add(user.role);
7269
+ if (Array.isArray(user?.roles)) {
7270
+ for (const r of user.roles) userRoles.add(r);
7271
+ }
7272
+ const isAllowed = (item) => {
7273
+ if (!item.access) return true;
7274
+ if (Array.isArray(item.access)) {
7275
+ if (item.access.length === 0) return true;
7276
+ return item.access.some((role) => userRoles.has(role));
7277
+ }
7278
+ return true;
7279
+ };
7280
+ const prunedGroups = [];
7281
+ for (const group of tree.groups) {
7282
+ const visibleItems = group.items.filter(isAllowed);
7283
+ if (visibleItems.length > 0) {
7284
+ prunedGroups.push({
7285
+ ...group,
7286
+ items: visibleItems
7287
+ });
7288
+ }
7289
+ }
7290
+ return {
7291
+ groups: prunedGroups,
7292
+ ungrouped: tree.ungrouped.filter(isAllowed)
7293
+ };
7294
+ }
7295
+
6728
7296
  // src/index.ts
6729
7297
  var import_zod = require("zod");
6730
7298
  function defineCollection(config) {
@@ -6792,8 +7360,10 @@ function defineTab(args) {
6792
7360
  BASE62_ALPHABET,
6793
7361
  BUILTIN_JEXL_HELPERS,
6794
7362
  CROCKFORD_BASE32,
7363
+ CURRENT_NAV_PREFERENCES_VERSION,
6795
7364
  ConfigValidationError,
6796
7365
  ConflictError,
7366
+ DEFAULT_USER_NAV_PREFERENCES,
6797
7367
  DuplicateKeyError,
6798
7368
  DyrectedAIError,
6799
7369
  DyrectedError,
@@ -6806,6 +7376,7 @@ function defineTab(args) {
6806
7376
  NON_NEGOTIABLE_CREDENTIAL_FIELDS,
6807
7377
  NotFoundError,
6808
7378
  PII_REGEX_PATTERNS,
7379
+ RESERVED_NAVIGATION_SLUGS,
6809
7380
  ValidationError,
6810
7381
  WORKFLOW_HISTORY_COLLECTION,
6811
7382
  abs,
@@ -6816,6 +7387,7 @@ function defineTab(args) {
6816
7387
  assertValidDeclarativeHookExpression,
6817
7388
  assertValidDeclarativeHookResult,
6818
7389
  assertValidDeclarativeHooksInConfig,
7390
+ assertValidNavigationSlugs,
6819
7391
  assertValidPreviewUrlsInConfig,
6820
7392
  availableWorkflowTransitions,
6821
7393
  buildSqlOrderBy,
@@ -6830,6 +7402,7 @@ function defineTab(args) {
6830
7402
  collectDeclarativeHookValidationIssues,
6831
7403
  collectPreviewUrlValidationIssues,
6832
7404
  compact,
7405
+ compileNavigation,
6833
7406
  count,
6834
7407
  createLifecycleEvent,
6835
7408
  createWorkflowDocument,
@@ -6870,6 +7443,7 @@ function defineTab(args) {
6870
7443
  defineTimeField,
6871
7444
  defineUrlField,
6872
7445
  defineView,
7446
+ defineWorkspace,
6873
7447
  diffDays,
6874
7448
  dispatchLifecycleEvent,
6875
7449
  dispatchPendingLifecycleEvents,
@@ -6936,9 +7510,11 @@ function defineTab(args) {
6936
7510
  parseSqlWhere,
6937
7511
  pow,
6938
7512
  prefixedId,
7513
+ pruneNavigationForUser,
6939
7514
  publishedStateName,
6940
7515
  publishingWorkflow,
6941
7516
  readingTime,
7517
+ reconcileNavigation,
6942
7518
  registerJexlHelpers,
6943
7519
  replace,
6944
7520
  resolveAdminAuthCollection,