@burdenoff/fe-libs 2026.1022.1 → 2026.1022.3

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.
@@ -12,6 +12,16 @@ export interface LanguageSwitcherProps {
12
12
  /** Show as icon only (for small screens) */
13
13
  compact?: boolean;
14
14
  }
15
+ /**
16
+ * Accessible name of the switcher's trigger button.
17
+ *
18
+ * WCAG 2.5.3 (Label in Name; axe `label-content-name-mismatch`): when the
19
+ * trigger shows the current language code, its accessible name must contain
20
+ * that visible text, so a speech-input user who says "click EN" hits it. The
21
+ * name therefore leads with the code ("EN, Change language"). The compact
22
+ * trigger shows only a globe icon, so its name stays the plain action label.
23
+ */
24
+ export declare function languageTriggerLabel(currentCode: string | undefined, changeLabel: string, compact: boolean): string;
15
25
  /**
16
26
  * LanguageSwitcher Component
17
27
  *
@@ -3,46 +3,50 @@ import { useAssistantControl as t } from "../shared/assistant/appControls/useAss
3
3
  import { useEffect as n, useMemo as r, useRef as i, useState as a } from "react";
4
4
  import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
5
5
  //#region src/chrome/LanguageSwitcher.tsx
6
- var l = ({ languages: l, currentLanguage: u, onLanguageChange: d, compact: f = !1 }) => {
7
- let p = e(), [m, h] = a(!1), g = i(null), _ = l.find((e) => e.code === u) || l[0], v = i({
8
- languages: l,
9
- currentLanguage: u,
10
- onLanguageChange: d
6
+ function l(e, t, n) {
7
+ let r = e?.trim().toUpperCase();
8
+ return n || !r ? t : `${r}, ${t}`;
9
+ }
10
+ var u = ({ languages: u, currentLanguage: d, onLanguageChange: f, compact: p = !1 }) => {
11
+ let m = e(), [h, g] = a(!1), _ = i(null), v = u.find((e) => e.code === d) || u[0], y = i({
12
+ languages: u,
13
+ currentLanguage: d,
14
+ onLanguageChange: f
11
15
  });
12
- v.current = {
13
- languages: l,
14
- currentLanguage: u,
15
- onLanguageChange: d
16
+ y.current = {
17
+ languages: u,
18
+ currentLanguage: d,
19
+ onLanguageChange: f
16
20
  };
17
- let y = d !== void 0;
18
- t("language", r(() => y ? {
19
- languages: () => v.current.languages.map((e) => ({
21
+ let b = f !== void 0;
22
+ t("language", r(() => b ? {
23
+ languages: () => y.current.languages.map((e) => ({
20
24
  code: e.code,
21
25
  label: e.nativeName || e.name
22
26
  })),
23
- current: () => v.current.currentLanguage,
27
+ current: () => y.current.currentLanguage,
24
28
  set: (e) => {
25
- let { languages: t, currentLanguage: n, onLanguageChange: r } = v.current, i = t.find((t) => t.code.toLowerCase() === e.toLowerCase());
29
+ let { languages: t, currentLanguage: n, onLanguageChange: r } = y.current, i = t.find((t) => t.code.toLowerCase() === e.toLowerCase());
26
30
  return !i || !r ? { status: "failed" } : i.code === n ? { status: "already" } : (r(i), { status: "done" });
27
31
  }
28
- } : null, [y])), n(() => {
32
+ } : null, [b])), n(() => {
29
33
  let e = (e) => {
30
- g.current && !g.current.contains(e.target) && h(!1);
34
+ _.current && !_.current.contains(e.target) && g(!1);
31
35
  };
32
- if (m) return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
33
- }, [m]);
34
- let b = (e) => {
35
- d && d(e), h(!1);
36
+ if (h) return document.addEventListener("mousedown", e), () => document.removeEventListener("mousedown", e);
37
+ }, [h]);
38
+ let x = (e) => {
39
+ f && f(e), g(!1);
36
40
  };
37
41
  return /* @__PURE__ */ c("div", {
38
42
  className: "relative",
39
- ref: g,
43
+ ref: _,
40
44
  children: [/* @__PURE__ */ s("button", {
41
- onClick: () => h(!m),
42
- className: `flex items-center gap-2 px-3 py-2 rounded-button hover:bg-bg-sunken transition-colors ${m ? "bg-bg-sunken" : ""}`,
43
- "aria-label": p("languageSwitcher.change", "Change language"),
44
- "aria-expanded": m,
45
- children: f ? /* @__PURE__ */ s("svg", {
45
+ onClick: () => g(!h),
46
+ className: `flex items-center gap-2 px-3 py-2 rounded-button hover:bg-bg-sunken transition-colors ${h ? "bg-bg-sunken" : ""}`,
47
+ "aria-label": l(v?.code, m("languageSwitcher.change", "Change language"), p),
48
+ "aria-expanded": h,
49
+ children: p ? /* @__PURE__ */ s("svg", {
46
50
  className: "size-5 text-text-primary",
47
51
  fill: "none",
48
52
  stroke: "currentColor",
@@ -55,9 +59,9 @@ var l = ({ languages: l, currentLanguage: u, onLanguageChange: d, compact: f = !
55
59
  })
56
60
  }) : /* @__PURE__ */ c(o, { children: [/* @__PURE__ */ s("span", {
57
61
  className: "text-body-sm font-medium text-text-primary uppercase",
58
- children: _?.code
62
+ children: v?.code.toUpperCase()
59
63
  }), /* @__PURE__ */ s("svg", {
60
- className: `w-4 h-4 text-text-secondary transition-transform ${m ? "rotate-180" : ""}`,
64
+ className: `w-4 h-4 text-text-secondary transition-transform ${h ? "rotate-180" : ""}`,
61
65
  fill: "none",
62
66
  stroke: "currentColor",
63
67
  viewBox: "0 0 24 24",
@@ -68,19 +72,19 @@ var l = ({ languages: l, currentLanguage: u, onLanguageChange: d, compact: f = !
68
72
  d: "M19 9l-7 7-7-7"
69
73
  })
70
74
  })] })
71
- }), m && /* @__PURE__ */ c("div", {
75
+ }), h && /* @__PURE__ */ c("div", {
72
76
  className: "absolute right-0 mt-2 w-[calc(100vw-1.5rem)] sm:w-64 max-w-64 bg-bg-elevated border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] z-50 py-2 overflow-hidden animate-in fade-in-0 zoom-in-95 slide-in-from-top-2 duration-200",
73
77
  children: [/* @__PURE__ */ s("div", {
74
78
  className: "px-4 py-2 border-b border-border-subtle",
75
79
  children: /* @__PURE__ */ s("p", {
76
80
  className: "text-[11px] font-medium text-text-muted uppercase tracking-wider",
77
- children: p("languageSwitcher.title", "Select Language")
81
+ children: m("languageSwitcher.title", "Select Language")
78
82
  })
79
83
  }), /* @__PURE__ */ s("div", {
80
84
  className: "py-1 max-h-96 overflow-y-auto",
81
- children: l.map((e) => /* @__PURE__ */ c("button", {
82
- onClick: () => b(e),
83
- className: `w-full flex items-center gap-3 px-4 py-2.5 text-body-sm transition-colors ${e.code === u ? "bg-[var(--color-accent-soft)] text-text-primary font-medium" : "text-text-primary hover:bg-bg-sunken"}`,
85
+ children: u.map((e) => /* @__PURE__ */ c("button", {
86
+ onClick: () => x(e),
87
+ className: `w-full flex items-center gap-3 px-4 py-2.5 text-body-sm transition-colors ${e.code === d ? "bg-[var(--color-accent-soft)] text-text-primary font-medium" : "text-text-primary hover:bg-bg-sunken"}`,
84
88
  children: [
85
89
  e.flag && /* @__PURE__ */ s("span", {
86
90
  className: "text-xl flex-shrink-0",
@@ -96,7 +100,7 @@ var l = ({ languages: l, currentLanguage: u, onLanguageChange: d, compact: f = !
96
100
  children: e.name
97
101
  })]
98
102
  }),
99
- e.code === u && /* @__PURE__ */ s("svg", {
103
+ e.code === d && /* @__PURE__ */ s("svg", {
100
104
  className: "size-5 text-action-primary-bg flex-shrink-0",
101
105
  fill: "currentColor",
102
106
  viewBox: "0 0 20 20",
@@ -113,4 +117,4 @@ var l = ({ languages: l, currentLanguage: u, onLanguageChange: d, compact: f = !
113
117
  });
114
118
  };
115
119
  //#endregion
116
- export { l as LanguageSwitcher };
120
+ export { u as LanguageSwitcher };
@@ -163,6 +163,17 @@ export interface EnqueueGraphQLFetchOptions {
163
163
  /** Dispatch at once, outside any batch, with fetch `keepalive`. */
164
164
  keepalive?: boolean;
165
165
  }
166
+ /**
167
+ * True when the promise returned by `enqueueGraphQLFetch` was served from
168
+ * the in-memory response cache without a network request. The marker is
169
+ * attached to the promise itself, so the caller must hold the promise
170
+ * reference (not just the awaited value) to inspect it:
171
+ *
172
+ * const promise = enqueueGraphQLFetch(url, headers, body, signal);
173
+ * const result = await promise;
174
+ * if (!isGraphqlFetchCacheHit(promise)) markConnectionHealthy();
175
+ */
176
+ export declare function isGraphqlFetchCacheHit(promise: Promise<unknown> | unknown): boolean;
166
177
  /**
167
178
  * Drop the short-lived GraphQL read caches so the next read of any operation
168
179
  * hits the network.
@@ -55,11 +55,11 @@ function C(e) {
55
55
  return null;
56
56
  }
57
57
  }
58
- function ee(e) {
58
+ function w(e) {
59
59
  let t = C(e)?.exp;
60
60
  return typeof t == "number" ? t * 1e3 <= Date.now() + 3e4 : !1;
61
61
  }
62
- function te() {
62
+ function ee() {
63
63
  try {
64
64
  let e = sessionStorage.getItem("bf-active-profile");
65
65
  if (e) {
@@ -72,7 +72,7 @@ function te() {
72
72
  return null;
73
73
  }
74
74
  }
75
- function w() {
75
+ function T() {
76
76
  try {
77
77
  let e = sessionStorage.getItem("bf-active-profile");
78
78
  if (e) {
@@ -84,31 +84,31 @@ function w() {
84
84
  return "en";
85
85
  }
86
86
  }
87
- function T() {
87
+ function E() {
88
88
  return s("workspace");
89
89
  }
90
- function E() {
90
+ function D() {
91
91
  return s("organization");
92
92
  }
93
- function D() {
93
+ function O() {
94
94
  return s("tenant");
95
95
  }
96
- function O() {
96
+ function k() {
97
97
  return s("project");
98
98
  }
99
- function k() {
99
+ function A() {
100
100
  try {
101
101
  return typeof window > "u" ? null : new URLSearchParams(window.location.search).get("org");
102
102
  } catch {
103
103
  return null;
104
104
  }
105
105
  }
106
- function A(e) {
106
+ function te(e) {
107
107
  return {
108
- workspaceId: typeof e.workspaceId == "string" ? e.workspaceId : T() || void 0,
109
- organizationId: typeof e.organizationId == "string" ? e.organizationId : E() || k() || void 0,
110
- tenantId: typeof e.tenantId == "string" ? e.tenantId : D() || void 0,
111
- projectId: typeof e.projectId == "string" ? e.projectId : O() || void 0
108
+ workspaceId: typeof e.workspaceId == "string" ? e.workspaceId : E() || void 0,
109
+ organizationId: typeof e.organizationId == "string" ? e.organizationId : D() || A() || void 0,
110
+ tenantId: typeof e.tenantId == "string" ? e.tenantId : O() || void 0,
111
+ projectId: typeof e.projectId == "string" ? e.projectId : k() || void 0
112
112
  };
113
113
  }
114
114
  function j(e, t, n) {
@@ -129,9 +129,9 @@ function M(e, t, n) {
129
129
  async function N(e) {
130
130
  let { url: t, authToken: n, workspaceId: r, organizationId: i, projectId: a, locale: o, signal: s, currentQuery: c, issuanceGuard: l } = e;
131
131
  if (!n || c.includes("issueWorkspaceToken") || l && !l.isCurrent()) return null;
132
- let u = r || T();
132
+ let u = r || E();
133
133
  if (!u) return null;
134
- let d = b(), p = T(), m = i || E() || k();
134
+ let d = b(), p = E(), m = i || D() || A();
135
135
  if (d && p === u && f(d, {
136
136
  workspaceId: u,
137
137
  organizationId: m,
@@ -198,33 +198,37 @@ function B() {
198
198
  let e = L();
199
199
  return e.__graphqlFetchPendingResponses ||= /* @__PURE__ */ new Map(), e.__graphqlFetchPendingResponses;
200
200
  }
201
- function V() {
201
+ var V = Symbol.for("@burdenoff/fe-libs:graphql-fetch-cache-hit");
202
+ function H(e) {
203
+ return !!(e && typeof e == "object" && e[V] === !0);
204
+ }
205
+ function ne() {
202
206
  z().clear(), B().clear(), n();
203
207
  }
204
- var H = 10, ne = 600, U = 100, re = 1e4, W = 15e3, ie = new Set([
208
+ var re = 10, ie = 600, U = 100, ae = 1e4, W = 15e3, oe = new Set([
205
209
  "traceparent",
206
210
  "tracestate",
207
211
  "baggage"
208
212
  ]), G = "Apollo-Require-Preflight";
209
- function ae() {
213
+ function se() {
210
214
  if (typeof performance > "u") return U;
211
215
  let e = L();
212
- return e.__graphqlFetchBurstStartedAt ??= performance.now(), performance.now() - e.__graphqlFetchBurstStartedAt < re ? ne : U;
216
+ return e.__graphqlFetchBurstStartedAt ??= performance.now(), performance.now() - e.__graphqlFetchBurstStartedAt < ae ? ie : U;
213
217
  }
214
- function oe(e) {
218
+ function ce(e) {
215
219
  return typeof e.query == "string" ? /^\s*query\b/.test(e.query) : !1;
216
220
  }
217
- function se(e) {
221
+ function le(e) {
218
222
  return typeof e.operationName == "string" && e.operationName.length > 0 ? {
219
223
  operationName: e.operationName,
220
224
  variables: e.variables ?? null
221
225
  } : e;
222
226
  }
223
- function ce(e) {
227
+ function ue(e) {
224
228
  let t = /* @__PURE__ */ new Map();
225
229
  for (let [n, r] of Object.entries(e)) {
226
230
  let e = n.toLowerCase();
227
- ie.has(e) || t.set(e, r);
231
+ oe.has(e) || t.set(e, r);
228
232
  }
229
233
  return Object.fromEntries(Array.from(t.entries()).sort(([e], [t]) => e.localeCompare(t)));
230
234
  }
@@ -237,13 +241,13 @@ function K(t) {
237
241
  };
238
242
  }
239
243
  function q(e) {
240
- return le(e) && Array.isArray(e.errors);
244
+ return de(e) && Array.isArray(e.errors);
241
245
  }
242
246
  function J(e) {
243
247
  let t = e.status;
244
248
  return t === 401 ? "Invalid email or password." : t === 403 ? "You do not have permission to do that." : t === 404 ? "We could not find what you were looking for." : t === 408 || t === 504 ? "The server took too long to respond. Please try again." : t === 429 ? "Too many requests. Please wait a moment and try again." : t >= 500 && t < 600 ? "The server is having trouble right now. Please try again in a moment." : t >= 400 && t < 500 ? "The request could not be processed. Please refresh and try again." : "We could not reach the server. Please check your connection and try again.";
245
249
  }
246
- function le(e) {
250
+ function de(e) {
247
251
  return typeof e == "object" && !!e && !Array.isArray(e);
248
252
  }
249
253
  async function Y(e) {
@@ -302,12 +306,15 @@ async function X(e, t, n) {
302
306
  }
303
307
  }
304
308
  function Z(e, t, n, r, i) {
305
- let a = ce(t), o = i?.keepalive === !0, s = o ? 1 : Math.max(1, i?.batchMax ?? H), c = Math.max(0, i?.batchInterval ?? ae()), l = `${e}|${JSON.stringify(a)}`, u = oe(n) ? `${l}|${JSON.stringify(se(n))}` : void 0;
309
+ let a = ue(t), o = i?.keepalive === !0, s = o ? 1 : Math.max(1, i?.batchMax ?? re), c = Math.max(0, i?.batchInterval ?? se()), l = `${e}|${JSON.stringify(a)}`, u = ce(n) ? `${l}|${JSON.stringify(le(n))}` : void 0;
306
310
  if (r?.aborted) return Promise.reject(F(r));
307
311
  if (u) {
308
312
  let e = z(), t = e.get(u);
309
313
  if (t) {
310
- if (t.expiresAt > Date.now()) return Promise.resolve(t.value);
314
+ if (t.expiresAt > Date.now()) {
315
+ let e = Promise.resolve(t.value);
316
+ return e[V] = !0, e;
317
+ }
311
318
  e.delete(u);
312
319
  }
313
320
  let n = B().get(u);
@@ -357,9 +364,9 @@ function Q(e, t, n) {
357
364
  i && (r.delete(n), i.timer &&= (clearTimeout(i.timer), null), X(e, t, i.requests));
358
365
  }
359
366
  async function $(e) {
360
- let { gateway: n, query: r, variables: i, operationName: a, authToken: o, workspaceToken: s, workspaceId: c, organizationId: u, tenantId: h, projectId: _, locale: v, baseUrl: x, extraHeaders: C, signal: T, suppressGlobalErrorEvent: E, keepalive: O } = e;
361
- I(T);
362
- let k = d(P(n, x)), j = o && !ee(o) ? o : y();
367
+ let { gateway: n, query: r, variables: i, operationName: a, authToken: o, workspaceToken: s, workspaceId: c, organizationId: u, tenantId: h, projectId: _, locale: v, baseUrl: x, extraHeaders: C, signal: E, suppressGlobalErrorEvent: D, keepalive: k } = e;
368
+ I(E);
369
+ let A = d(P(n, x)), j = o && !w(o) ? o : y();
363
370
  if (!j && !e.skipAuthRetry && typeof window < "u" && !window.location.pathname.startsWith("/auth/") && S()) {
364
371
  let e = await import("../utils/authErrorHandling.js");
365
372
  await e.attemptTokenRefresh({
@@ -368,9 +375,9 @@ async function $(e) {
368
375
  }) ? j = y() : g({
369
376
  operationName: a,
370
377
  gateway: n
371
- }), I(T);
378
+ }), I(E);
372
379
  }
373
- let M = v ?? w(), F = A({
380
+ let M = v ?? T(), F = te({
374
381
  workspaceId: c,
375
382
  organizationId: u,
376
383
  tenantId: h,
@@ -380,19 +387,19 @@ async function $(e) {
380
387
  [G]: "true"
381
388
  };
382
389
  j && (L.Authorization = `Bearer ${j}`), M && (L["Accept-Language"] = M);
383
- let R = te();
390
+ let R = ee();
384
391
  if (R?.country && (L["X-Geo-Country"] = R.country), R?.currency && (L["X-Geo-Currency"] = R.currency), R?.timezone && (L["X-Geo-Timezone"] = R.timezone), n === "workspace" ? s !== !1 : s) {
385
392
  let e = typeof s == "string" ? s : b();
386
393
  e && !f(e, F) && (e = null), !e && (s === !0 || n === "workspace" && s === void 0 || typeof s == "string") && (e = await N({
387
- url: k,
394
+ url: A,
388
395
  authToken: j,
389
396
  workspaceId: F.workspaceId,
390
397
  organizationId: F.organizationId,
391
398
  projectId: F.projectId,
392
399
  locale: M,
393
- signal: T,
400
+ signal: E,
394
401
  currentQuery: r
395
- }), I(T)), e && (L["X-Workspace-Authorization"] = `Bearer ${e}`);
402
+ }), I(E)), e && (L["X-Workspace-Authorization"] = `Bearer ${e}`);
396
403
  }
397
404
  if (n === "workspace" || c) {
398
405
  let e = F.workspaceId;
@@ -403,7 +410,7 @@ async function $(e) {
403
410
  e && (L["x-org-id"] = e);
404
411
  }
405
412
  if (h) {
406
- let e = typeof h == "string" ? h : D();
413
+ let e = typeof h == "string" ? h : O();
407
414
  e && (L["x-tenant-id"] = e);
408
415
  }
409
416
  if (n === "workspace" || _) {
@@ -431,24 +438,24 @@ async function $(e) {
431
438
  }), null) : null;
432
439
  };
433
440
  try {
434
- let e = await Z(k, L, B, T, O ? { keepalive: !0 } : void 0);
435
- if (m(), e.errors?.length) {
436
- let t = await V(e.errors);
437
- if (t) return t;
438
- E || p({ errors: e.errors }, { operationName: a });
441
+ let e = Z(A, L, B, E, k ? { keepalive: !0 } : void 0), t = await e;
442
+ if (H(e) || m(), t.errors?.length) {
443
+ let e = await V(t.errors);
444
+ if (e) return e;
445
+ D || p({ errors: t.errors }, { operationName: a });
439
446
  }
440
- return e;
447
+ return t;
441
448
  } catch (e) {
442
449
  let t = e.errors;
443
450
  if (t?.length) {
444
451
  let e = await V(t);
445
452
  if (e) return e;
446
453
  }
447
- throw E || p(e, {
454
+ throw D || p(e, {
448
455
  operationName: a,
449
456
  fallbackMessage: "We could not reach the server. Please check your connection and try again."
450
457
  }), e;
451
458
  }
452
459
  }
453
460
  //#endregion
454
- export { h as SESSION_EXPIRED_EVENT, V as clearGraphqlFetchResponseCache, Z as enqueueGraphQLFetch, N as ensureWorkspaceToken, y as getStoredAuthToken, w as getStoredLocale, E as getStoredOrganizationId, O as getStoredProjectId, D as getStoredTenantId, T as getStoredWorkspaceId, b as getStoredWorkspaceToken, $ as graphqlFetch, P as resolveGatewayUrl };
461
+ export { h as SESSION_EXPIRED_EVENT, ne as clearGraphqlFetchResponseCache, Z as enqueueGraphQLFetch, N as ensureWorkspaceToken, y as getStoredAuthToken, T as getStoredLocale, D as getStoredOrganizationId, k as getStoredProjectId, O as getStoredTenantId, E as getStoredWorkspaceId, b as getStoredWorkspaceToken, $ as graphqlFetch, P as resolveGatewayUrl };
@@ -83,7 +83,7 @@ function g({ children: n, defaultMode: r = "system", defaultBrand: l = "default"
83
83
  let n = document.documentElement;
84
84
  if (n.setAttribute("data-theme", P), n.setAttribute("data-brand", t(x)), n.setAttribute("data-density", L), n.setAttribute("data-typography", R), n.setAttribute("data-glass", z), n.toggleAttribute("data-reduce-motion", B), I === "dark" ? n.classList.add("dark") : n.classList.remove("dark"), N) {
85
85
  let e = N.accent;
86
- n.style.setProperty("--color-action-primary-bg", e), n.style.setProperty("--color-action-primary-bgHover", `color-mix(in oklab, ${e} 84%, black)`), n.style.setProperty("--color-action-primary-bgActive", `color-mix(in oklab, ${e} 70%, black)`), p(e) ? n.style.setProperty("--color-action-primary-text", "#0a0a0a") : n.style.removeProperty("--color-action-primary-text");
86
+ n.style.setProperty("--color-action-primary-bg", e), n.style.setProperty("--color-action-primary-bgHover", `color-mix(in oklab, ${e} 84%, black)`), n.style.setProperty("--color-action-primary-bgActive", `color-mix(in oklab, ${e} 70%, black)`), n.style.setProperty("--color-action-primary-text", p(e) ? "#0a0a0a" : "#ffffff");
87
87
  } else n.style.removeProperty("--color-action-primary-bg"), n.style.removeProperty("--color-action-primary-bgHover"), n.style.removeProperty("--color-action-primary-bgActive"), n.style.removeProperty("--color-action-primary-text");
88
88
  let r = () => {
89
89
  let t = getComputedStyle(n).getPropertyValue("--color-bg-canvas").trim();
@@ -51,8 +51,21 @@ export declare function isAbortError(input: unknown): boolean;
51
51
  export declare function isTransportError(input: unknown): boolean;
52
52
  /**
53
53
  * Signal that connectivity is healthy again. Called from the GraphQL success
54
- * paths; clears the connection indicator exactly once on recovery. No-op when
55
- * the connection was never marked degraded.
54
+ * paths; clears the connection indicator exactly once on recovery.
55
+ *
56
+ * Always dispatches the `online: true` event (not only when the latch was
57
+ * previously set). The connection-status toast in `AppShellProvider` can be
58
+ * shown by either an `online: false` event *or* a browser `offline` event
59
+ * that did not correspond to an actual transport error (e.g. a transient
60
+ * navigator.onLine blip during a Cloudflare Access redirect — see BOFF-8081).
61
+ * In that second case the latch was never tripped, so a previous version that
62
+ * returned early here left a stale "Connection lost. Trying to reconnect…"
63
+ * toast stuck forever even though every subsequent request succeeded. By
64
+ * always announcing recovery we keep the listener's toast state in sync with
65
+ * the actual connectivity.
66
+ *
67
+ * The listener treats `online: true` as a no-op when no banner is shown, so
68
+ * the extra dispatch on every successful request is cheap.
56
69
  */
57
70
  export declare function markConnectionHealthy(): void;
58
71
  export declare function extractBackendError(input: unknown, options?: ExtractBackendErrorOptions): BackendErrorDisplay | null;
@@ -111,7 +111,7 @@ function x(e, n) {
111
111
  } })));
112
112
  }
113
113
  function S() {
114
- typeof window > "u" || b && (b = !1, y = 0, window.dispatchEvent(new CustomEvent(t, { detail: {
114
+ typeof window > "u" || (b && (b = !1, y = 0), window.dispatchEvent(new CustomEvent(t, { detail: {
115
115
  online: !0,
116
116
  since: Date.now()
117
117
  } })));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/fe-libs",
3
- "version": "2026.1022.1",
3
+ "version": "2026.1022.3",
4
4
  "description": "Burdenoff frontend primitives and domain libraries",
5
5
  "type": "module",
6
6
  "bin": {
@@ -330,7 +330,7 @@
330
330
  "test:package-livekit-optional": "node scripts/validate-livekit-optional.mjs",
331
331
  "verify:package": "node scripts/verify-package-contents.mjs",
332
332
  "test:handsfree-browser": "node scripts/handsfree-browser-run.mjs",
333
- "test": "bun test scripts/graphql-schema-drift/__tests__ scripts/a11y-audit/__tests__ scripts/i18n/__tests__ src/credentials src/connections src/storage src/shared/assistant src/shared/feature-flags src/platform-pages src/admin-overview src/shared/components src/shared/graphql/links/sse-link.test.ts src/shared/graphql/fetch.test.ts src/shared/graphql/prepareWorkspaceRequest.test.ts src/shared/graphql/client.test.ts src/shared/utils/apollo.test.ts src/shared/utils/canvasTokens.test.ts src/shared/graphql/tourWorkspaceAuth.test.ts src/shared/config/gatewayUrls.test.ts src/shared/config/authBridgeUrls.test.ts src/shared/config/shellRoutes.test.ts src/shared/config/gatewayFallback.test.ts scripts/__tests__ src/shared/config/products.test.ts src/shared/config/docsUrls.test.ts src/shared/config/productSlug.test.ts src/shared/hooks/shell/useProducts.test.ts src/shared/hooks/shell/useGlobalSearch.test.ts src/shared/providers/shell/brandAttribute.test.ts src/chrome/ProductSwitcher.test.ts src/shared/utils/gatewayFetchRewrite.test.ts src/shared/utils/authCookie.test.ts src/shared/utils/redirectAllowlist.test.ts src/shared/providers/shell/ProfileStorageManager.test.ts src/shared/providers/AppShellProvider.test.ts src/shared/providers/shell/interpolate.test.ts src/shared/providers/shell/resolveTranslation.test.ts src/shared/providers/shell/I18nProvider.render.test.tsx src/shared/providers/shell/I18nProvider.client.test.tsx src/shared/providers/shell/translationStore.test.ts src/shared/providers/shell/translationLayers.test.ts src/shared/providers/shell/directionMode.test.ts src/shared/providers/shell/authProvider/ssoRelay.test.ts src/shared/providers/shell/authProvider/useAuthActions.signOut.test.tsx src/shared/providers/shell/personaSurfaceRemote.test.ts src/shared/providers/shell/workspaceSwitch.client.test.tsx src/shared/events/sinks/RybbitSink.test.ts src/shared/events/sinks/RybbitSink.client.test.tsx src/shell-native src/shared/native src/shared/navigation src/shared/tours src/chrome/__tests__ src/ui/__tests__",
333
+ "test": "bun test scripts/graphql-schema-drift/__tests__ scripts/a11y-audit/__tests__ scripts/i18n/__tests__ src/credentials src/connections src/storage src/shared/assistant src/shared/feature-flags src/platform-pages src/admin-overview src/shared/components src/shared/graphql/links/sse-link.test.ts src/shared/graphql/fetch.test.ts src/shared/graphql/prepareWorkspaceRequest.test.ts src/shared/graphql/client.test.ts src/shared/utils/apollo.test.ts src/shared/utils/canvasTokens.test.ts src/shared/utils/brandContrast.test.ts src/shared/graphql/tourWorkspaceAuth.test.ts src/shared/config/gatewayUrls.test.ts src/shared/config/authBridgeUrls.test.ts src/shared/config/shellRoutes.test.ts src/shared/config/gatewayFallback.test.ts scripts/__tests__ src/shared/config/products.test.ts src/shared/config/docsUrls.test.ts src/shared/config/productSlug.test.ts src/shared/hooks/shell/useProducts.test.ts src/shared/hooks/shell/useGlobalSearch.test.ts src/shared/providers/shell/brandAttribute.test.ts src/chrome/ProductSwitcher.test.ts src/shared/utils/backendErrors.test.ts src/shared/utils/gatewayFetchRewrite.test.ts src/shared/utils/authCookie.test.ts src/shared/utils/redirectAllowlist.test.ts src/shared/providers/shell/ProfileStorageManager.test.ts src/shared/providers/AppShellProvider.test.ts src/shared/providers/shell/interpolate.test.ts src/shared/providers/shell/resolveTranslation.test.ts src/shared/providers/shell/I18nProvider.render.test.tsx src/shared/providers/shell/I18nProvider.client.test.tsx src/shared/providers/shell/translationStore.test.ts src/shared/providers/shell/translationLayers.test.ts src/shared/providers/shell/directionMode.test.ts src/shared/providers/shell/authProvider/ssoRelay.test.ts src/shared/providers/shell/authProvider/useAuthActions.signOut.test.tsx src/shared/providers/shell/personaSurfaceRemote.test.ts src/shared/providers/shell/workspaceSwitch.client.test.tsx src/shared/events/sinks/RybbitSink.test.ts src/shared/events/sinks/RybbitSink.client.test.tsx src/shell-native src/shared/native src/shared/navigation src/shared/tours src/chrome/__tests__ src/ui/__tests__",
334
334
  "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run lint:sanity && bun run format && bun run type:check && bun run test && bun run build && bun run verify:package'",
335
335
  "switch:status": "bun scripts/deps-mode.ts status",
336
336
  "switch:remote": "bun scripts/deps-mode.ts remote",
@@ -1,6 +1,25 @@
1
- /* FluidGrids brand overrides - Auto-generated */
1
+ /* FluidGrids brand overrides
2
+ *
3
+ * WCAG 2.1 AA (1.4.3), parity G23. The accent is both the primary-button fill
4
+ * (white label) and, through --color-accent-primary, small accent text on the
5
+ * canvas, so one cyan cannot serve both themes:
6
+ * - light: cyan-700 #0E7490 carries a white label at 5.36:1. The former
7
+ * cyan-600 #0891B2 measured 3.68:1 and failed axe color-contrast on every
8
+ * primary button of the landing page.
9
+ * - dark: cyan-600 stays (5.2:1 as accent text on the #0A0E16 canvas), and
10
+ * the button label turns dark (5.7:1); hover/active step LIGHTER so the
11
+ * dark label keeps its contrast in every state.
12
+ */
2
13
  [data-brand="fluidgrids"] {
14
+ --color-action-primary-bg: #0E7490;
15
+ --color-action-primary-bgHover: #155E75;
16
+ --color-action-primary-bgActive: #164E63;
17
+ }
18
+
19
+ :root.dark[data-brand="fluidgrids"],
20
+ [data-brand="fluidgrids"].dark {
3
21
  --color-action-primary-bg: #0891B2;
4
- --color-action-primary-bgHover: #0E7490;
5
- --color-action-primary-bgActive: #155E75;
22
+ --color-action-primary-bgHover: #06B6D4;
23
+ --color-action-primary-bgActive: #22D3EE;
24
+ --color-action-primary-text: #0A0E16;
6
25
  }