@the-portland-company/shell 0.65.0 → 0.66.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/native.cjs CHANGED
@@ -1368,11 +1368,14 @@ function useLocationHref() {
1368
1368
  return href;
1369
1369
  }
1370
1370
  function specOverlayFetchJson(path) {
1371
- return fetch(`${SPECS_ORIGIN}${path}`, { credentials: "include" }).then((res) => {
1371
+ return fetch(`${SPECS_ORIGIN}${path}`, { credentials: "include", signal: AbortSignal.timeout(1e4) }).then((res) => {
1372
1372
  if (!res.ok) throw new Error(`spec overlay ${path}: ${res.status}`);
1373
1373
  return res.json();
1374
1374
  });
1375
1375
  }
1376
+ function isAutomatedCaptureSession() {
1377
+ return typeof navigator !== "undefined" && navigator.webdriver === true;
1378
+ }
1376
1379
  function currentChromeTheme() {
1377
1380
  const root = document.documentElement;
1378
1381
  if (root.classList.contains("dark") || root.getAttribute("data-theme") === "dark") return "dark";
@@ -1424,7 +1427,8 @@ async function captureMyView(status, orgId, orgName) {
1424
1427
  method: "POST",
1425
1428
  credentials: "include",
1426
1429
  headers: { "X-Spec-Capture": "1" },
1427
- body: form
1430
+ body: form,
1431
+ signal: AbortSignal.timeout(1e4)
1428
1432
  });
1429
1433
  if (!res.ok) throw new Error(`capture ${res.status}: ${(await res.text()).slice(0, 160)}`);
1430
1434
  }
@@ -1457,8 +1461,9 @@ function SpecOverlayAction({ orgId, orgName } = {}) {
1457
1461
  const [frameKey, setFrameKey] = react.useState(0);
1458
1462
  const [copied, setCopied] = react.useState("idle");
1459
1463
  const href = useLocationHref();
1464
+ const automated = isAutomatedCaptureSession();
1460
1465
  react.useEffect(() => {
1461
- if (access) return;
1466
+ if (access || automated) return;
1462
1467
  let cancelled = false;
1463
1468
  specOverlayFetchJson("/api/overlay/access").then((data) => {
1464
1469
  if (cancelled) return;
@@ -1476,7 +1481,7 @@ function SpecOverlayAction({ orgId, orgName } = {}) {
1476
1481
  };
1477
1482
  }, [access]);
1478
1483
  react.useEffect(() => {
1479
- if (!access?.allowed || typeof window === "undefined") return;
1484
+ if (automated || !access?.allowed || typeof window === "undefined") return;
1480
1485
  const timer = window.setTimeout(() => {
1481
1486
  specOverlayFetchJson(specOverlayStatusPath()).then((data) => setStatus(data)).catch(() => setStatus(null));
1482
1487
  }, 300);
@@ -1507,13 +1512,13 @@ function SpecOverlayAction({ orgId, orgName } = {}) {
1507
1512
  }
1508
1513
  };
1509
1514
  react.useEffect(() => {
1510
- if (!open || !status || !status.matched) return;
1515
+ if (automated || !open || !status || !status.matched) return;
1511
1516
  void runCapture(status);
1512
1517
  return () => {
1513
1518
  captureRun.current++;
1514
1519
  };
1515
1520
  }, [open, status && status.matched ? status.target_id : null]);
1516
- if (!access?.allowed) return null;
1521
+ if (automated || !access?.allowed) return null;
1517
1522
  const badgeLabel = status && status.matched ? `${Math.round(status.mismatch_pct)}%` : status ? "\u2014" : void 0;
1518
1523
  const badgeTone = status && status.matched ? status.passed ? "default" : "danger" : "default";
1519
1524
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -2209,8 +2214,8 @@ function NavSidebar({
2209
2214
  };
2210
2215
  const activeFn = nav.isActive ?? isNavItemActive;
2211
2216
  const ModeIcon = mode?.icon;
2212
- const orgBg = org?.brandColor || ORG_PILL_DEFAULT;
2213
2217
  const orgLogoSrc = resolveOrgLogoUrl(org?.logoUrl);
2218
+ const orgGradient = org?.brandColor ? `linear-gradient(to right, ${org.brandColor}, ${org.brandColorSecondary || org.brandColor})` : "linear-gradient(to right, #007BFF, #A54B7A, #E63946)";
2214
2219
  const activeGroupKey = react.useMemo(() => {
2215
2220
  for (let i = 0; i < nav.items.length; i++) {
2216
2221
  const item = nav.items[i];
@@ -2306,29 +2311,33 @@ function NavSidebar({
2306
2311
  ),
2307
2312
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Loading organization" })
2308
2313
  ] }) : org.organizations && org.organizations.length > 0 && org.onSwitch ? /* @__PURE__ */ jsxRuntime.jsxs("details", { "data-nav-details": true, className: "group relative", children: [
2309
- /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex w-full cursor-pointer list-none items-center justify-between gap-2 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)] [&::-webkit-details-marker]:hidden", children: [
2310
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
2311
- /* @__PURE__ */ jsxRuntime.jsx(
2312
- "span",
2314
+ /* @__PURE__ */ jsxRuntime.jsxs("summary", { className: "flex w-full cursor-pointer list-none flex-col overflow-hidden rounded-[5px] border border-[color:var(--line)] bg-[color:var(--panel-elevated)] text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)] [&::-webkit-details-marker]:hidden", children: [
2315
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-[70px] w-full items-center justify-center bg-white px-3", children: [
2316
+ orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "max-h-[70px] max-w-full object-contain" }) : org.canUploadLogo ? /* @__PURE__ */ jsxRuntime.jsx(
2317
+ "a",
2313
2318
  {
2314
- style: { backgroundColor: orgBg },
2315
- className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
2316
- children: orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
2319
+ href: org.uploadLogoHref ?? "#",
2320
+ onClick: (e) => e.stopPropagation(),
2321
+ className: "text-xs font-semibold text-[color:var(--color-accent)] underline",
2322
+ children: "Upload logo"
2317
2323
  }
2318
- ),
2324
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-neutral-800", children: org.name ?? "Organization" }),
2325
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "absolute inset-x-0 bottom-0 h-[3px]", style: { backgroundImage: orgGradient } })
2326
+ ] }),
2327
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex w-full min-w-0 items-center justify-between gap-2 px-2.5 py-2", children: [
2319
2328
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
2320
2329
  /* @__PURE__ */ jsxRuntime.jsx(
2321
2330
  "span",
2322
2331
  {
2323
2332
  title: org.name ?? void 0,
2324
- className: "truncate text-sm font-semibold",
2333
+ className: "truncate text-[13px] font-semibold",
2325
2334
  children: org.name ?? "No organizations"
2326
2335
  }
2327
2336
  ),
2328
2337
  org.id && !org.loading ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
2329
- ] })
2330
- ] }),
2331
- /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
2338
+ ] }),
2339
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
2340
+ ] })
2332
2341
  ] }),
2333
2342
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute left-0 right-0 z-40 mt-1 max-h-[min(60vh,520px)] overflow-y-auto rounded-xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] py-1 shadow-[var(--shadow-soft)]", children: [
2334
2343
  shouldShowOrgFilter(org.organizations.length) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "sticky top-0 z-10 bg-[color:var(--panel-elevated)] px-2 pb-1 pt-1", children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2398,60 +2407,76 @@ function NavSidebar({
2398
2407
  {
2399
2408
  type: "button",
2400
2409
  onClick: org.onClick,
2401
- className: "flex w-full items-center justify-between gap-2 rounded-md border border-[color:var(--line)] bg-[color:var(--panel-elevated)] px-2.5 py-2 text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)]",
2410
+ className: "flex w-full flex-col overflow-hidden rounded-[5px] border border-[color:var(--line)] bg-[color:var(--panel-elevated)] text-left text-[color:var(--foreground)] shadow-sm transition hover:bg-[color:var(--panel)]",
2402
2411
  children: [
2403
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 items-center gap-2.5", children: [
2404
- /* @__PURE__ */ jsxRuntime.jsx(
2405
- "span",
2412
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "relative flex h-[70px] w-full items-center justify-center bg-white px-3", children: [
2413
+ orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "max-h-[70px] max-w-full object-contain" }) : org.canUploadLogo ? /* @__PURE__ */ jsxRuntime.jsx(
2414
+ "a",
2406
2415
  {
2407
- style: { backgroundColor: orgBg },
2408
- className: "flex h-7 w-7 shrink-0 items-center justify-center overflow-hidden rounded-full text-[11px] font-bold text-white",
2409
- children: orgLogoSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: orgLogoSrc, alt: "", className: "h-full w-full object-cover" }) : (org.name ?? "Org").slice(0, 2).toUpperCase()
2416
+ href: org.uploadLogoHref ?? "#",
2417
+ onClick: (e) => e.stopPropagation(),
2418
+ className: "text-xs font-semibold text-[color:var(--color-accent)] underline",
2419
+ children: "Upload logo"
2410
2420
  }
2411
- ),
2421
+ ) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-sm font-semibold text-neutral-800", children: org.name ?? "Organization" }),
2422
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "absolute inset-x-0 bottom-0 h-[3px]", style: { backgroundImage: orgGradient } })
2423
+ ] }),
2424
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex w-full min-w-0 items-center justify-between gap-2 px-2.5 py-2", children: [
2412
2425
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
2413
2426
  /* @__PURE__ */ jsxRuntime.jsx(
2414
2427
  "span",
2415
2428
  {
2416
2429
  title: org.name ?? void 0,
2417
- className: "truncate text-sm font-semibold",
2430
+ className: "truncate text-[13px] font-semibold",
2418
2431
  children: org.name ?? "No organizations"
2419
2432
  }
2420
2433
  ),
2421
2434
  org.id && !org.loading ? /* @__PURE__ */ jsxRuntime.jsx(OrgIdCopyButton, { id: org.id }) : null
2422
- ] })
2423
- ] }),
2424
- /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
2435
+ ] }),
2436
+ /* @__PURE__ */ jsxRuntime.jsx(ChevronDown, {})
2437
+ ] })
2425
2438
  ]
2426
2439
  }
2427
2440
  )
2428
2441
  ] }) : null,
2429
2442
  mode ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
2430
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center gap-2 rounded-md bg-[color:var(--panel-elevated)] p-1", children: (mode.options && mode.options.length > 0 ? mode.options : [{ id: mode.label, label: mode.label, icon: ModeIcon }]).map(
2431
- (opt) => {
2432
- const selected = opt.label === mode.label;
2433
- const OptIcon = opt.icon;
2434
- return /* @__PURE__ */ jsxRuntime.jsx(
2435
- "button",
2436
- {
2437
- type: "button",
2438
- title: opt.label,
2439
- "aria-label": opt.label,
2440
- "aria-current": selected ? "true" : void 0,
2441
- onClick: () => {
2442
- if (!selected) mode.onSelect?.(opt.id);
2443
- },
2444
- disabled: !mode.onSelect && !selected,
2445
- className: [
2446
- "flex h-9 flex-1 items-center justify-center rounded transition",
2447
- selected ? "bg-[color:var(--mode-accent,var(--color-accent))] text-[color:var(--mode-on-accent,white)] shadow-sm" : "text-[color:var(--sidebar-text-muted,var(--muted))] hover:bg-[color:var(--panel)] hover:text-[color:var(--sidebar-text,var(--foreground))]"
2448
- ].join(" "),
2449
- children: OptIcon ? /* @__PURE__ */ jsxRuntime.jsx(OptIcon, { size: 16, className: "shrink-0" }) : null
2450
- },
2451
- opt.id
2452
- );
2443
+ /* @__PURE__ */ jsxRuntime.jsx(
2444
+ "div",
2445
+ {
2446
+ role: "radiogroup",
2447
+ "aria-label": "Mode",
2448
+ className: "flex items-center justify-start gap-0.5 rounded-lg border border-[color:var(--border-inverse,var(--line))] bg-[color:var(--mode-track-bg,var(--panel-elevated))] p-[3px]",
2449
+ children: (mode.options && mode.options.length > 0 ? mode.options : [{ id: mode.label, label: mode.label, icon: ModeIcon }]).map(
2450
+ (opt) => {
2451
+ const selected = opt.label === mode.label;
2452
+ const OptIcon = opt.icon;
2453
+ const available = opt.available ?? true;
2454
+ const title = !available ? opt.unavailableTooltip ?? `${opt.label} isn't active for this organization.` : opt.label;
2455
+ return /* @__PURE__ */ jsxRuntime.jsx(
2456
+ "button",
2457
+ {
2458
+ type: "button",
2459
+ role: "radio",
2460
+ title,
2461
+ "aria-label": title,
2462
+ "aria-checked": selected,
2463
+ "aria-current": selected ? "true" : void 0,
2464
+ onClick: () => {
2465
+ if (available && !selected) mode.onSelect?.(opt.id);
2466
+ },
2467
+ disabled: !available,
2468
+ className: [
2469
+ "flex h-9 items-center justify-center rounded px-[9px] py-[6px] transition",
2470
+ !available ? "cursor-not-allowed text-[color:var(--sidebar-text-muted,var(--muted))] opacity-35" : selected ? "bg-[color:var(--mode-accent,var(--color-accent))] text-[color:var(--mode-on-accent,white)] shadow-sm" : "text-[color:var(--sidebar-text-muted,var(--muted))] hover:bg-white/5 hover:text-[color:var(--sidebar-text,var(--foreground))]"
2471
+ ].join(" "),
2472
+ children: OptIcon ? /* @__PURE__ */ jsxRuntime.jsx(OptIcon, { size: 15, className: "shrink-0" }) : null
2473
+ },
2474
+ opt.id
2475
+ );
2476
+ }
2477
+ )
2453
2478
  }
2454
- ) }),
2479
+ ),
2455
2480
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1.5 text-center text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--sidebar-text-muted,var(--muted))]", children: mode.label })
2456
2481
  ] }) : null,
2457
2482
  /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-col gap-1", children: [