@burdenoff/fe-libs 2026.1023.12 → 2026.1023.14

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.
@@ -11,6 +11,12 @@ export interface CommandAction {
11
11
  className?: string;
12
12
  }>;
13
13
  readonly keywords?: readonly string[];
14
+ /**
15
+ * Key hint shown next to the action. A modifier combo ("mod+n") is bound by
16
+ * the palette itself; a key sequence ("g h") is display-only here and must be
17
+ * bound by the host shell with `withCommandChords` (fe-libs/shared/keyboard),
18
+ * which builds the binding from this action so the two cannot drift.
19
+ */
14
20
  readonly shortcut?: string;
15
21
  readonly action: () => void | Promise<void>;
16
22
  readonly section?: string;
@@ -1,11 +1,12 @@
1
1
  import { useShellLabel as e } from "../shared/providers/shell/useShellLabel.js";
2
2
  import { cn as t } from "../utils/cn.js";
3
3
  import "../utils/index.js";
4
- import { useCallback as n, useEffect as r, useMemo as i, useState as a } from "react";
5
- import { Search as o, X as s } from "lucide-react";
6
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
4
+ import { isChordShortcut as n } from "../shared/keyboard/commandChords.js";
5
+ import { useCallback as r, useEffect as i, useMemo as a, useState as o } from "react";
6
+ import { Search as s, X as c } from "lucide-react";
7
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
7
8
  //#region src/chrome/CommandPalette.tsx
8
- var u = "mod+shift+k", d = {
9
+ var d = "mod+shift+k", f = {
9
10
  id: "command-palette",
10
11
  keys: {
11
12
  mac: ["cmd+shift+k"],
@@ -17,7 +18,7 @@ var u = "mod+shift+k", d = {
17
18
  action: () => void 0,
18
19
  global: !0
19
20
  };
20
- function f(e) {
21
+ function p(e) {
21
22
  let t = e.toLowerCase().split("+").map((e) => e.trim()), n = t.length - 1;
22
23
  return {
23
24
  mod: t.includes("mod"),
@@ -28,52 +29,52 @@ function f(e) {
28
29
  key: t[n] || ""
29
30
  };
30
31
  }
31
- function p(e, t) {
32
- let n = f(t);
32
+ function m(e, t) {
33
+ let n = p(t);
33
34
  return n.mod && !(e.metaKey || e.ctrlKey) || n.ctrl && !e.ctrlKey || n.alt !== e.altKey || n.shift !== e.shiftKey || n.meta && !e.metaKey || !e.key ? !1 : e.key.toLowerCase() === n.key;
34
35
  }
35
- function m({ actions: d, shortcuts: f = [], placeholder: m, className: h, toggleShortcut: g = u }) {
36
- let _ = e(), [v, y] = a(!1), [b, x] = a(""), [S, C] = a(0), w = i(() => d.filter((e) => !e.hidden), [d]), T = n(async (e) => {
36
+ function h({ actions: f, shortcuts: p = [], placeholder: h, className: g, toggleShortcut: _ = d }) {
37
+ let v = e(), [y, b] = o(!1), [x, S] = o(""), [C, w] = o(0), T = a(() => f.filter((e) => !e.hidden), [f]), E = r(async (e) => {
37
38
  try {
38
- await e.action(), y(!1);
39
+ await e.action(), b(!1);
39
40
  } catch (e) {
40
41
  console.error("Failed to execute command:", e);
41
42
  }
42
- }, []), E = i(() => {
43
- if (!b.trim()) return w;
44
- let e = b.toLowerCase();
45
- return w.filter((t) => {
43
+ }, []), D = a(() => {
44
+ if (!x.trim()) return T;
45
+ let e = x.toLowerCase();
46
+ return T.filter((t) => {
46
47
  let n = t.label.toLowerCase().includes(e), r = t.keywords?.some((t) => t.toLowerCase().includes(e)), i = t.description?.toLowerCase().includes(e);
47
48
  return n || r || i;
48
49
  });
49
- }, [w, b]);
50
- r(() => {
51
- v || (x(""), C(0));
52
- }, [v]), r(() => {
50
+ }, [T, x]);
51
+ i(() => {
52
+ y || (S(""), w(0));
53
+ }, [y]), i(() => {
53
54
  let e = (e) => {
54
- if (p(e, g)) {
55
- e.preventDefault(), y((e) => !e);
55
+ if (m(e, _)) {
56
+ e.preventDefault(), b((e) => !e);
56
57
  return;
57
58
  }
58
- if (v) {
59
- if (e.key === "Escape") e.preventDefault(), y(!1);
60
- else if (e.key === "ArrowDown") e.preventDefault(), C((e) => e < E.length - 1 ? e + 1 : e);
61
- else if (e.key === "ArrowUp") e.preventDefault(), C((e) => e > 0 ? e - 1 : e);
59
+ if (y) {
60
+ if (e.key === "Escape") e.preventDefault(), b(!1);
61
+ else if (e.key === "ArrowDown") e.preventDefault(), w((e) => e < D.length - 1 ? e + 1 : e);
62
+ else if (e.key === "ArrowUp") e.preventDefault(), w((e) => e > 0 ? e - 1 : e);
62
63
  else if (e.key === "Enter") {
63
64
  e.preventDefault();
64
- let t = E[S];
65
- t && T(t);
65
+ let t = D[C];
66
+ t && E(t);
66
67
  }
67
68
  return;
68
69
  }
69
- for (let t of f) if (p(e, t.keys)) {
70
+ for (let t of p) if (m(e, t.keys)) {
70
71
  e.preventDefault();
71
- let n = w.find((e) => e.id === t.actionId);
72
- n && T(n);
72
+ let n = T.find((e) => e.id === t.actionId);
73
+ n && E(n);
73
74
  return;
74
75
  }
75
- for (let t of w) if (t.shortcut && p(e, t.shortcut)) {
76
- e.preventDefault(), T(t);
76
+ for (let t of T) if (!n(t.shortcut) && t.shortcut && m(e, t.shortcut)) {
77
+ e.preventDefault(), E(t);
77
78
  return;
78
79
  }
79
80
  };
@@ -81,84 +82,84 @@ function m({ actions: d, shortcuts: f = [], placeholder: m, className: h, toggle
81
82
  document.removeEventListener("keydown", e);
82
83
  };
83
84
  }, [
84
- T,
85
85
  E,
86
- v,
87
- S,
88
- f,
89
- g,
90
- w
86
+ D,
87
+ y,
88
+ C,
89
+ p,
90
+ _,
91
+ T
91
92
  ]);
92
- let D = i(() => E.reduce((e, t) => {
93
+ let O = a(() => D.reduce((e, t) => {
93
94
  let n = t.section || "Actions";
94
95
  return e[n] || (e[n] = []), e[n].push(t), e;
95
- }, {}), [E]);
96
- if (r(() => {
97
- C(0);
98
- }, [E]), !v) return null;
99
- let O = 0;
100
- return /* @__PURE__ */ c("div", {
101
- className: t("fixed inset-0 z-[9999] flex items-center justify-center bg-bg-overlay backdrop-blur-md", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", h),
102
- onClick: () => y(!1),
103
- "data-state": v ? "open" : "closed",
96
+ }, {}), [D]);
97
+ if (i(() => {
98
+ w(0);
99
+ }, [D]), !y) return null;
100
+ let k = 0;
101
+ return /* @__PURE__ */ l("div", {
102
+ className: t("fixed inset-0 z-[9999] flex items-center justify-center bg-bg-overlay backdrop-blur-md", "data-[state=open]:animate-in data-[state=closed]:animate-out", "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0", g),
103
+ onClick: () => b(!1),
104
+ "data-state": y ? "open" : "closed",
104
105
  role: "dialog",
105
106
  "aria-modal": "true",
106
- children: /* @__PURE__ */ c("div", {
107
+ children: /* @__PURE__ */ l("div", {
107
108
  className: "w-full max-w-lg mx-4 max-h-[85dvh] overflow-hidden",
108
109
  onClick: (e) => e.stopPropagation(),
109
- children: /* @__PURE__ */ l("div", {
110
+ children: /* @__PURE__ */ u("div", {
110
111
  className: "overflow-hidden rounded-2xl border border-border-subtle bg-bg-elevated shadow-[var(--shadow-elevation-5)]",
111
112
  children: [
112
- /* @__PURE__ */ l("div", {
113
+ /* @__PURE__ */ u("div", {
113
114
  className: "flex items-center border-b border-border-subtle px-4",
114
115
  children: [
115
- /* @__PURE__ */ c(o, { className: "size-4 mr-2 flex-shrink-0 text-text-muted" }),
116
- /* @__PURE__ */ c("input", {
116
+ /* @__PURE__ */ l(s, { className: "size-4 mr-2 flex-shrink-0 text-text-muted" }),
117
+ /* @__PURE__ */ l("input", {
117
118
  type: "text",
118
- placeholder: m ?? _("search.commandPlaceholder", "Type a command or search..."),
119
- value: b,
120
- onChange: (e) => x(e.target.value),
119
+ placeholder: h ?? v("search.commandPlaceholder", "Type a command or search..."),
120
+ value: x,
121
+ onChange: (e) => S(e.target.value),
121
122
  className: "flex-1 bg-transparent py-3.5 text-sm text-text-primary outline-none placeholder:text-text-placeholder",
122
123
  autoFocus: !0
123
124
  }),
124
- b && /* @__PURE__ */ c("button", {
125
+ x && /* @__PURE__ */ l("button", {
125
126
  type: "button",
126
- onClick: () => x(""),
127
+ onClick: () => S(""),
127
128
  className: "ml-2 rounded-button p-1 text-text-muted hover:bg-bg-sunken hover:text-text-primary transition-colors",
128
- "aria-label": _("common.clearSearch", "Clear search"),
129
- children: /* @__PURE__ */ c(s, { className: "size-4" })
129
+ "aria-label": v("common.clearSearch", "Clear search"),
130
+ children: /* @__PURE__ */ l(c, { className: "size-4" })
130
131
  })
131
132
  ]
132
133
  }),
133
- /* @__PURE__ */ l("div", {
134
+ /* @__PURE__ */ u("div", {
134
135
  className: "max-h-96 overflow-y-auto p-2",
135
- children: [E.length === 0 && /* @__PURE__ */ c("div", {
136
+ children: [D.length === 0 && /* @__PURE__ */ l("div", {
136
137
  className: "py-6 text-center text-sm text-text-muted",
137
- children: _("search.noCommandsFound", "No commands found.")
138
- }), Object.entries(D).map(([e, n]) => /* @__PURE__ */ l("div", {
138
+ children: v("search.noCommandsFound", "No commands found.")
139
+ }), Object.entries(O).map(([e, n]) => /* @__PURE__ */ u("div", {
139
140
  className: "mb-2 last:mb-0",
140
- children: [/* @__PURE__ */ c("div", {
141
+ children: [/* @__PURE__ */ l("div", {
141
142
  className: "px-2 py-1.5 text-[11px] font-medium uppercase tracking-wider text-text-muted",
142
143
  children: e
143
144
  }), n.map((e) => {
144
- let n = e.icon, r = O;
145
- return O++, /* @__PURE__ */ l("button", {
145
+ let n = e.icon, r = k;
146
+ return k++, /* @__PURE__ */ u("button", {
146
147
  type: "button",
147
- onClick: () => void T(e),
148
- className: t("w-full flex items-start gap-3 px-2.5 py-2 text-sm rounded-button transition-colors text-left", r === S ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
148
+ onClick: () => void E(e),
149
+ className: t("w-full flex items-start gap-3 px-2.5 py-2 text-sm rounded-button transition-colors text-left", r === C ? "bg-[var(--color-accent-soft)] text-text-primary" : "text-text-secondary hover:bg-bg-sunken hover:text-text-primary"),
149
150
  children: [
150
- n && /* @__PURE__ */ c(n, { className: "size-4 mt-0.5 flex-shrink-0" }),
151
- /* @__PURE__ */ l("div", {
151
+ n && /* @__PURE__ */ l(n, { className: "size-4 mt-0.5 flex-shrink-0" }),
152
+ /* @__PURE__ */ u("div", {
152
153
  className: "flex-1 min-w-0",
153
- children: [/* @__PURE__ */ c("div", {
154
+ children: [/* @__PURE__ */ l("div", {
154
155
  className: "font-medium text-text-primary",
155
156
  children: e.label
156
- }), e.description && /* @__PURE__ */ c("div", {
157
+ }), e.description && /* @__PURE__ */ l("div", {
157
158
  className: "text-xs text-text-muted",
158
159
  children: e.description
159
160
  })]
160
161
  }),
161
- e.shortcut && /* @__PURE__ */ c("kbd", {
162
+ e.shortcut && /* @__PURE__ */ l("kbd", {
162
163
  className: "ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border border-border-subtle bg-bg-sunken px-1.5 font-mono text-[10px] font-medium text-text-secondary",
163
164
  children: e.shortcut
164
165
  })
@@ -167,32 +168,32 @@ function m({ actions: d, shortcuts: f = [], placeholder: m, className: h, toggle
167
168
  })]
168
169
  }, e))]
169
170
  }),
170
- /* @__PURE__ */ l("div", {
171
+ /* @__PURE__ */ u("div", {
171
172
  className: "border-t border-border-subtle px-3 py-2 text-xs text-text-muted flex items-center justify-between",
172
173
  children: [
173
- /* @__PURE__ */ l("div", { children: [
174
- /* @__PURE__ */ c("kbd", {
174
+ /* @__PURE__ */ u("div", { children: [
175
+ /* @__PURE__ */ l("kbd", {
175
176
  className: "inline-flex h-5 select-none items-center gap-1 rounded border border-border-subtle bg-bg-sunken px-1.5 font-mono text-[10px] text-text-secondary",
176
177
  children: "↑↓"
177
178
  }),
178
179
  " ",
179
- _("search.navigate", "Navigate")
180
+ v("search.navigate", "Navigate")
180
181
  ] }),
181
- /* @__PURE__ */ l("div", { children: [
182
- /* @__PURE__ */ c("kbd", {
182
+ /* @__PURE__ */ u("div", { children: [
183
+ /* @__PURE__ */ l("kbd", {
183
184
  className: "inline-flex h-5 select-none items-center gap-1 rounded border border-border-subtle bg-bg-sunken px-1.5 font-mono text-[10px] text-text-secondary",
184
185
  children: "Enter"
185
186
  }),
186
187
  " ",
187
- _("search.execute", "Execute")
188
+ v("search.execute", "Execute")
188
189
  ] }),
189
- /* @__PURE__ */ l("div", { children: [
190
- /* @__PURE__ */ c("kbd", {
190
+ /* @__PURE__ */ u("div", { children: [
191
+ /* @__PURE__ */ l("kbd", {
191
192
  className: "inline-flex h-5 select-none items-center gap-1 rounded border border-border-subtle bg-bg-sunken px-1.5 font-mono text-[10px] text-text-secondary",
192
193
  children: "Esc"
193
194
  }),
194
195
  " ",
195
- _("common.close", "Close")
196
+ v("common.close", "Close")
196
197
  ] })
197
198
  ]
198
199
  })
@@ -202,4 +203,4 @@ function m({ actions: d, shortcuts: f = [], placeholder: m, className: h, toggle
202
203
  });
203
204
  }
204
205
  //#endregion
205
- export { d as COMMAND_PALETTE_HELP_SHORTCUT, u as COMMAND_PALETTE_SHORTCUT, m as CommandPalette };
206
+ export { f as COMMAND_PALETTE_HELP_SHORTCUT, d as COMMAND_PALETTE_SHORTCUT, h as CommandPalette };
@@ -8,6 +8,12 @@ export interface DynamicFooterProps {
8
8
  tagline: string;
9
9
  /** Version string */
10
10
  version?: string;
11
+ /**
12
+ * Copyright line, without the leading "©" (forwarded to `Footer`). Defaults
13
+ * to the shared "<year> Burdenoff. All rights reserved." label; products
14
+ * held by another entity pass their holder here (BUR-SOP-029).
15
+ */
16
+ copyright?: string;
11
17
  /** Custom footer columns (overrides defaults) */
12
18
  columns?: FooterColumn[];
13
19
  /** Custom social links (overrides API-fetched) */
@@ -22,4 +28,4 @@ export interface DynamicFooterProps {
22
28
  */
23
29
  hideOnInstalledApp?: boolean;
24
30
  }
25
- export declare function DynamicFooter({ productSlug, productName, tagline, version, columns: customColumns, socialLinks: customSocialLinks, legalPageOrder, deferRemoteData, hideOnInstalledApp, }: DynamicFooterProps): import('react/jsx-runtime').JSX.Element;
31
+ export declare function DynamicFooter({ productSlug, productName, tagline, version, copyright, columns: customColumns, socialLinks: customSocialLinks, legalPageOrder, deferRemoteData, hideOnInstalledApp, }: DynamicFooterProps): import('react/jsx-runtime').JSX.Element;
@@ -60,12 +60,12 @@ var y = _`
60
60
  function w(e) {
61
61
  return e.trim().replace(/[^A-Za-z0-9 ]/g, "").split(/\s+/).map((e, t) => t === 0 ? e.toLowerCase() : e.charAt(0).toUpperCase() + e.slice(1).toLowerCase()).join("");
62
62
  }
63
- function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0", columns: h, socialLinks: _, legalPageOrder: T = C, deferRemoteData: E = !1, hideOnInstalledApp: D = !0 }) {
64
- let { workspaceId: O, projectId: k } = n(), A = e("global"), { t: j } = t(), [M, N] = l(null), [P, F] = l(null);
63
+ function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0", copyright: h, columns: _, socialLinks: T, legalPageOrder: E = C, deferRemoteData: D = !1, hideOnInstalledApp: O = !0 }) {
64
+ let { workspaceId: k, projectId: A } = n(), j = e("global"), { t: M } = t(), [N, P] = l(null), [F, I] = l(null);
65
65
  s(() => {
66
- if (!A || E) return;
66
+ if (!j || D) return;
67
67
  let e = !1;
68
- return A.query({
68
+ return j.query({
69
69
  query: y,
70
70
  variables: { slug: u },
71
71
  fetchPolicy: "cache-first"
@@ -73,7 +73,7 @@ function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0",
73
73
  if (e) return;
74
74
  let n = t.data?.publicProduct;
75
75
  if (n) {
76
- if (!_) {
76
+ if (!T) {
77
77
  let e = n.socialLinks;
78
78
  if (e && Object.keys(e).length > 0) {
79
79
  let t = Object.entries(e).map(([e, t]) => ({
@@ -82,10 +82,10 @@ function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0",
82
82
  href: t,
83
83
  icon: S[e.toLowerCase()] || /* @__PURE__ */ g(d, { className: "size-5" })
84
84
  }));
85
- t.length > 0 && F(t);
85
+ t.length > 0 && I(t);
86
86
  }
87
87
  }
88
- return A.query({
88
+ return j.query({
89
89
  query: b,
90
90
  variables: {
91
91
  productId: n.id,
@@ -98,28 +98,28 @@ function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0",
98
98
  if (e || !t) return;
99
99
  let n = t.data?.publicContentPages?.items;
100
100
  if (!n) return;
101
- let r = T.map((e) => n.find((t) => t.slug === e)).filter((e) => e != null).map((e) => ({
101
+ let r = E.map((e) => n.find((t) => t.slug === e)).filter((e) => e != null).map((e) => ({
102
102
  id: e.slug,
103
103
  label: e.title,
104
104
  href: `/legal/${x[e.slug] || e.slug}`
105
105
  }));
106
- r.length > 0 && N(r);
106
+ r.length > 0 && P(r);
107
107
  }).catch(() => {}), () => {
108
108
  e = !0;
109
109
  };
110
110
  }, [
111
- _,
112
- E,
113
- A,
114
111
  T,
112
+ D,
113
+ j,
114
+ E,
115
115
  u
116
116
  ]);
117
- let I = c(() => {
118
- let e = h ?? r, t = M ? e.map((e) => e.title === "Legal" ? {
117
+ let L = c(() => {
118
+ let e = _ ?? r, t = N ? e.map((e) => e.title === "Legal" ? {
119
119
  ...e,
120
- links: M
120
+ links: N
121
121
  } : e) : e, n = (e, t) => {
122
- let n = j(e);
122
+ let n = M(e);
123
123
  return n === e ? t : n;
124
124
  };
125
125
  return t.map((e) => ({
@@ -131,21 +131,22 @@ function T({ productSlug: u, productName: f, tagline: p, version: m = "v1.0.0",
131
131
  })
132
132
  }));
133
133
  }, [
134
- M,
135
- h,
136
- j
134
+ N,
135
+ _,
136
+ M
137
137
  ]);
138
138
  return /* @__PURE__ */ g(a, {
139
139
  logo: /* @__PURE__ */ g(o, { size: "sidenav" }),
140
140
  productName: f,
141
141
  tagline: p,
142
- columns: I,
143
- socialLinks: _ ?? P ?? i,
142
+ columns: L,
143
+ socialLinks: T ?? F ?? i,
144
144
  version: m,
145
- hideOnInstalledApp: D,
145
+ copyright: h,
146
+ hideOnInstalledApp: O,
146
147
  context: {
147
- workspace: O ?? void 0,
148
- project: k ?? void 0
148
+ workspace: k ?? void 0,
149
+ project: A ?? void 0
149
150
  },
150
151
  renderLink: ({ href: e, external: t, children: n, className: r }) => t ? /* @__PURE__ */ g("a", {
151
152
  href: e,
@@ -70,7 +70,7 @@ export { resolveAuthBridgeConfig, resolveShellAuthBridgeConfig } from './authBri
70
70
  export type { RuntimeAuthBridgeConfig } from './authBridgeUrls';
71
71
  export { CANONICAL_DOCS_HOSTS, buildProductDocsUrl, canonicalizeDocsOrigin, getCatalogDocsOrigin, normalizeDocsPath, resolveDocsEnvironment, resolveProductDocsOrigin, toEnvironmentDocsOrigin, toTrustedDocsOrigin, } from './docsUrls';
72
72
  export type { DocsEnvironment, ResolveDocsOriginOptions } from './docsUrls';
73
- export { PRODUCTS, PRODUCT_URLS, BOTMANOR_PRODUCT_SLUG, getProductUrl, getProductsWithUrls, findProductConfig, } from './products';
73
+ export { PRODUCTS, PRODUCT_URLS, BOTMANOR_PRODUCT_SLUG, getProductUrl, getProductsWithUrls, findProductConfig, isAlphaAppShell, toAlphaAppUrl, toCurrentEstateAppUrl, } from './products';
74
74
  export type { ProductConfig } from './products';
75
75
  export { PRODUCT_SLUG_RENAMES, PRODUCT_SLUG_EMIT, normalizeProductSlug, legacyProductSlug, isSameProductSlug, emitProductSlug, } from './productSlug';
76
76
  export type { ProductSlugRename } from './productSlug';
@@ -72,6 +72,28 @@ export declare const PRODUCT_URLS: {
72
72
  };
73
73
  };
74
74
  export declare const PRODUCTS: ProductConfig[];
75
+ /**
76
+ * Whether the running shell belongs to the alpha estate.
77
+ *
78
+ * - Desktop (Electron): the baked runtime config's `environment`.
79
+ * - Capacitor: the build's deploy target (the WebView origin is always localhost).
80
+ * - Web: the `alphaapp.*` / `alphaadmin.*` hosts.
81
+ */
82
+ export declare function isAlphaAppShell(hostname?: string): boolean;
83
+ /**
84
+ * The alpha twin of a production app-shell URL: `https://app.<domain>/…` →
85
+ * `https://alphaapp.<domain>/…` for a known product domain. Any other URL
86
+ * (already alpha, a non-product host, unparseable) is returned unchanged, so
87
+ * the call is idempotent and never invents a host outside the product estate.
88
+ */
89
+ export declare function toAlphaAppUrl(url: string): string;
90
+ /**
91
+ * A product's app-shell URL in the estate the current shell runs in: alpha
92
+ * shells link to the other products' alpha shells (cross-product SSO stays on
93
+ * the alpha session), prod shells to prod. Accepts the registry's `appUrl`
94
+ * as well as catalog URLs.
95
+ */
96
+ export declare function toCurrentEstateAppUrl(url: string): string;
75
97
  export declare function getProductUrl(productId: string): string;
76
98
  /** Catalog entry for a product id, accepting either spelling of a renamed slug. */
77
99
  export declare function findProductConfig(productId: string): ProductConfig | undefined;
@@ -1,6 +1,9 @@
1
- import { emitProductSlug as e, isSameProductSlug as t, legacyProductSlug as n } from "./productSlug.js";
1
+ import { isCapacitorNative as e } from "../native/capacitor.js";
2
+ import { getDesktopRuntimeConfig as t } from "../native/desktop.js";
3
+ import { ALL_PRODUCT_DOMAINS as n, ALPHA_HOSTS as r, getCurrentHostname as i, resolveNativeDeployTarget as a } from "./gatewayUrls.js";
4
+ import { emitProductSlug as o, isSameProductSlug as s, legacyProductSlug as c } from "./productSlug.js";
2
5
  //#region src/shared/config/products.ts
3
- var r = e("botmanor"), i = {
6
+ var l = o("botmanor"), u = {
4
7
  development: {
5
8
  fluidgrids: "http://localhost:5147/",
6
9
  bigconsole: "http://localhost:5148/",
@@ -48,7 +51,7 @@ var r = e("botmanor"), i = {
48
51
  theculturepost: "https://app.theculturepost.com/",
49
52
  theglobalfuel: "https://app.theglobalfuel.com/"
50
53
  }
51
- }, a = [
54
+ }, d = [
52
55
  {
53
56
  id: "vibecontrols",
54
57
  name: "VibeControls",
@@ -78,7 +81,7 @@ var r = e("botmanor"), i = {
78
81
  description: "AI development console"
79
82
  },
80
83
  {
81
- id: r,
84
+ id: l,
82
85
  name: "BotManor",
83
86
  icon: "BM",
84
87
  color: "#f59e0b",
@@ -317,27 +320,47 @@ var r = e("botmanor"), i = {
317
320
  description: "Energy, minerals & resources platform"
318
321
  }
319
322
  ];
320
- function o() {
323
+ function f() {
321
324
  return "production";
322
325
  }
323
- function s(e, t) {
324
- return e[t] ?? e[n(t)];
326
+ function p(e, t) {
327
+ return e[t] ?? e[c(t)];
325
328
  }
326
- function c(e) {
327
- return s(i[o()], e) || "#";
329
+ function m(n = i()) {
330
+ let o = t()?.environment;
331
+ return o ? o === "alpha" : e() ? a() === "alpha" : r.has(n.toLowerCase());
328
332
  }
329
- function l(e) {
330
- return a.find((n) => t(n.id, e));
333
+ function h(e) {
334
+ let t;
335
+ try {
336
+ t = new URL(e);
337
+ } catch {
338
+ return e;
339
+ }
340
+ let r = t.hostname.toLowerCase();
341
+ if (t.protocol !== "https:" || !r.startsWith("app.")) return e;
342
+ let i = r.slice(4);
343
+ return n.includes(i) ? (t.hostname = `alphaapp.${i}`, t.toString()) : e;
344
+ }
345
+ function g(e) {
346
+ return m() ? h(e) : e;
347
+ }
348
+ function _(e) {
349
+ let t = f(), n = p(u[t], e);
350
+ return n ? t === "production" ? g(n) : n : "#";
351
+ }
352
+ function v(e) {
353
+ return d.find((t) => s(t.id, e));
331
354
  }
332
- function u(e) {
333
- let t = s(i.production, e);
355
+ function y(e) {
356
+ let t = p(u.production, e);
334
357
  return t ? `${t}icons/icon.svg` : void 0;
335
358
  }
336
- function d() {
337
- return a.map((e) => ({
359
+ function b() {
360
+ return d.map((e) => ({
338
361
  ...e,
339
- url: c(e.id)
362
+ url: _(e.id)
340
363
  }));
341
364
  }
342
365
  //#endregion
343
- export { r as BOTMANOR_PRODUCT_SLUG, a as PRODUCTS, i as PRODUCT_URLS, l as findProductConfig, u as getProductLogoUrl, c as getProductUrl, d as getProductsWithUrls };
366
+ export { l as BOTMANOR_PRODUCT_SLUG, d as PRODUCTS, u as PRODUCT_URLS, v as findProductConfig, y as getProductLogoUrl, _ as getProductUrl, b as getProductsWithUrls, m as isAlphaAppShell, h as toAlphaAppUrl, g as toCurrentEstateAppUrl };