@the-portland-company/shell 0.96.2 → 0.97.1

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.
@@ -4,9 +4,9 @@
4
4
  var react = require('react');
5
5
  var clsx = require('clsx');
6
6
  var fi = require('react-icons/fi');
7
- var reactDom = require('react-dom');
8
- var jsxRuntime = require('react/jsx-runtime');
7
+ require('react-dom');
9
8
  var lu = require('react-icons/lu');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
10
  var devnotes = require('@the-portland-company/devnotes');
11
11
 
12
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -575,7 +575,6 @@ var SETTINGS_APP_ORIGIN = "https://settings.politogyvrm.com";
575
575
  var CORE_BILLING_PATH = "/organization-settings/subscription-billing";
576
576
  var CORE_BILLING_URL = `${SETTINGS_APP_ORIGIN}${CORE_BILLING_PATH}`;
577
577
  var QUARANTINED_CONTACTS_URL = `https://contacts.politogyvrm.com/duplicates`;
578
- var SPECS_ORIGIN = "https://specs.politogyvrm.com";
579
578
  function withOrgParam(url, orgId) {
580
579
  if (!orgId) return url;
581
580
  return `${url}${url.includes("?") ? "&" : "?"}org=${orgId}`;
@@ -623,19 +622,26 @@ function TopBarCountBadge({ count }) {
623
622
  }
624
623
  function TopBarTooltip({
625
624
  title,
626
- description
625
+ description,
626
+ align = "center"
627
627
  }) {
628
+ const bodyPosition = align === "end" ? "right-0" : "left-1/2 -translate-x-1/2";
629
+ const arrowPosition = align === "end" ? "" : "left-1/2 -translate-x-1/2";
630
+ const arrowStyle = align === "end" ? { right: "calc(var(--topbar-icon-button-size, 34px) / 2 - 5px)" } : void 0;
628
631
  return /* @__PURE__ */ jsxRuntime.jsxs(
629
632
  "span",
630
633
  {
631
634
  role: "tooltip",
632
- className: "pointer-events-none absolute left-1/2 top-full z-50 mt-2 hidden w-max max-w-56 -translate-x-1/2 flex-col rounded-lg border border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)] px-3 py-2 text-left shadow-[var(--shadow-soft)] group-hover/tbtip:flex group-focus-visible/tbtip:flex",
635
+ "data-tooltip-align": align,
636
+ className: `pointer-events-none absolute ${bodyPosition} top-full z-50 mt-2 hidden w-max max-w-56 flex-col rounded-lg border border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)] px-3 py-2 text-left shadow-[var(--shadow-soft)] group-hover/tbtip:flex group-focus-visible/tbtip:flex`,
633
637
  children: [
634
638
  /* @__PURE__ */ jsxRuntime.jsx(
635
639
  "span",
636
640
  {
637
641
  "aria-hidden": true,
638
- className: "absolute -top-[5px] left-1/2 h-2.5 w-2.5 -translate-x-1/2 rotate-45 border-l border-t border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)]"
642
+ "data-tooltip-arrow": true,
643
+ style: arrowStyle,
644
+ className: `absolute -top-[5px] ${arrowPosition} h-2.5 w-2.5 rotate-45 border-l border-t border-[color:var(--app-btn-tooltip-bg,#2F3136)] bg-[color:var(--app-btn-tooltip-bg,#2F3136)]`
639
645
  }
640
646
  ),
641
647
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--app-btn-tooltip-fg,#FFFFFF)]", children: title }),
@@ -653,16 +659,6 @@ function TopBarPanelArrow() {
653
659
  }
654
660
  );
655
661
  }
656
- function TopBarWidgetSlot({
657
- title,
658
- description,
659
- children
660
- }) {
661
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "topbar-widget-slot group/tbtip relative inline-flex", children: [
662
- children,
663
- /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title, description })
664
- ] });
665
- }
666
662
  var THEME_OPTIONS = [
667
663
  { value: "light", label: "Light" }
668
664
  ];
@@ -946,23 +942,6 @@ function BellOffGlyph({ size = 14, className }) {
946
942
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "1", y1: "1", x2: "23", y2: "23" })
947
943
  ] });
948
944
  }
949
- function LifeBuoyGlyph({ size = TOPBAR_GLYPH_SIZE, className }) {
950
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
951
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "10" }),
952
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "4" }),
953
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "4.93", x2: "9.17", y2: "9.17" }),
954
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "14.83", x2: "19.07", y2: "19.07" }),
955
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "14.83", y1: "9.17", x2: "19.07", y2: "4.93" }),
956
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "4.93", y1: "19.07", x2: "9.17", y2: "14.83" })
957
- ] });
958
- }
959
- function LayoutGlyph({ size = TOPBAR_GLYPH_SIZE, className }) {
960
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
961
- /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
962
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "3", y1: "9", x2: "21", y2: "9" }),
963
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "9", y1: "21", x2: "9", y2: "9" })
964
- ] });
965
- }
966
945
  function PlusGlyph({ size = 14, className }) {
967
946
  return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
968
947
  /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }),
@@ -1596,586 +1575,39 @@ function NotificationsAction({ orgId }) {
1596
1575
  ] })
1597
1576
  );
1598
1577
  }
1599
- function composeSupportMessage(input) {
1600
- const ctx = [`Page: ${input.pageTitle || "Unknown"}`, `URL: ${input.url}`];
1601
- if (input.orgName) ctx.push(`Org: ${input.orgName}`);
1602
- const reply = [input.replyEmail?.trim(), input.replyPhone?.trim()].filter(Boolean).join(" / ");
1603
- if (reply) ctx.push(`Reply-to: ${reply}`);
1604
- return `${input.note.trim()}
1605
-
1606
- ---
1607
- ${ctx.join("\n")}`;
1608
- }
1609
- function SupportAction({
1610
- orgId,
1611
- orgs: knownOrgs
1612
- } = {}) {
1613
- const { fetchJson } = useNavChrome("SupportAction");
1614
- const { me, loaded } = useChromeMe();
1615
- const { orgs: fetchedOrgs } = useChromeOrgs({ enabled: !knownOrgs });
1616
- const orgs = knownOrgs ?? fetchedOrgs;
1617
- const [open, setOpen] = react.useState(false);
1618
- const [message, setMessage] = react.useState("");
1619
- const [email, setEmail] = react.useState("");
1620
- const [phone, setPhone] = react.useState("");
1621
- const [showEmailError, setShowEmailError] = react.useState(false);
1622
- const [state, setState] = react.useState("idle");
1623
- const meEmail = me?.email ?? null;
1624
- const mePhone = me?.phone ?? null;
1625
- const missingEmail = loaded && !meEmail;
1626
- const missingPhone = loaded && !mePhone;
1627
- const activeOrg = (orgId ? orgs?.find((o) => o.id === orgId) : null) ?? (orgs && orgs.length === 1 ? orgs[0] : null);
1628
- const orgName = activeOrg?.name ?? null;
1629
- const close = react.useCallback(() => {
1630
- setOpen(false);
1631
- setState("idle");
1632
- setMessage("");
1633
- setShowEmailError(false);
1634
- }, []);
1635
- react.useEffect(() => {
1636
- if (!open) return;
1637
- setEmail(meEmail ?? "");
1638
- setPhone(mePhone ?? "");
1639
- setShowEmailError(false);
1640
- }, [open, meEmail, mePhone]);
1641
- react.useEffect(() => {
1642
- if (!open) return;
1643
- const onKey = (e) => {
1644
- if (e.key === "Escape") close();
1645
- };
1646
- document.addEventListener("keydown", onKey);
1647
- return () => document.removeEventListener("keydown", onKey);
1648
- }, [open, close]);
1649
- const submit = async () => {
1650
- if (!message.trim() || state === "sending") return;
1651
- const resolvedEmail = (missingEmail ? email : meEmail ?? "").trim();
1652
- const resolvedPhone = (missingPhone ? phone : mePhone ?? "").trim();
1653
- if (missingEmail && !resolvedEmail) {
1654
- setShowEmailError(true);
1655
- return;
1656
- }
1657
- setShowEmailError(false);
1658
- const collectedContact = missingEmail || missingPhone;
1659
- const fullMessage = composeSupportMessage({
1660
- note: message,
1661
- pageTitle: typeof document !== "undefined" ? document.title : "",
1662
- url: typeof window !== "undefined" ? window.location.href : "",
1663
- orgName,
1664
- replyEmail: collectedContact ? resolvedEmail : void 0,
1665
- replyPhone: collectedContact ? resolvedPhone : void 0
1666
- });
1667
- setState("sending");
1668
- try {
1669
- await fetchJson("support", {
1670
- method: "POST",
1671
- headers: { "Content-Type": "application/json" },
1672
- body: JSON.stringify({ message: fullMessage })
1673
- });
1674
- setState("sent");
1675
- } catch {
1676
- setState("error");
1677
- }
1678
- };
1679
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1680
- /* @__PURE__ */ jsxRuntime.jsxs(
1681
- "button",
1682
- {
1683
- type: "button",
1684
- "aria-label": "Support",
1685
- className: topBarIconButtonClass(),
1686
- onClick: () => setOpen(true),
1687
- children: [
1688
- /* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot: "support", fallback: LifeBuoyGlyph }),
1689
- /* @__PURE__ */ jsxRuntime.jsx(
1690
- TopBarTooltip,
1691
- {
1692
- title: "Support",
1693
- description: "Send a support request to the Politogy team."
1694
- }
1695
- )
1696
- ]
1697
- }
1698
- ),
1699
- open && typeof document !== "undefined" ? reactDom.createPortal(
1700
- /* @__PURE__ */ jsxRuntime.jsx(
1701
- "div",
1702
- {
1703
- className: "fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4",
1704
- role: "dialog",
1705
- "aria-modal": "true",
1706
- "aria-label": "Support request",
1707
- onMouseDown: (e) => {
1708
- if (e.target === e.currentTarget) close();
1709
- },
1710
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full max-w-md rounded-2xl border border-[color:var(--line)] bg-[color:var(--panel-elevated)] p-6 shadow-[var(--shadow-soft)]", children: [
1711
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: "Contact Support" }),
1712
- state === "sent" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1713
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-3 text-sm text-[color:var(--muted)]", children: "Your request was sent. The team will reply by email." }),
1714
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-5 flex justify-end", children: /* @__PURE__ */ jsxRuntime.jsx(
1715
- "button",
1716
- {
1717
- type: "button",
1718
- onClick: close,
1719
- className: "rounded-full bg-[color:var(--color-accent-bg-safe,var(--color-accent))] px-4 py-2 text-sm font-semibold text-[#FFFFFF] hover:opacity-90",
1720
- children: "Done"
1721
- }
1722
- ) })
1723
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1724
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-[color:var(--muted)]", children: "Describe the problem or question. Your account details ride along automatically." }),
1725
- missingEmail ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-4 block", children: [
1726
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block text-xs font-semibold text-[color:var(--foreground)]", children: "Your email" }),
1727
- /* @__PURE__ */ jsxRuntime.jsx(
1728
- "input",
1729
- {
1730
- type: "email",
1731
- value: email,
1732
- onChange: (e) => setEmail(e.target.value),
1733
- placeholder: "you@example.com",
1734
- className: "w-full rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-accent)]"
1735
- }
1736
- ),
1737
- showEmailError ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-1 block text-xs text-[color:var(--chrome-danger)]", children: "Enter an email so support can reply." }) : null
1738
- ] }) : null,
1739
- missingPhone ? /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "mt-3 block", children: [
1740
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mb-1 block text-xs font-semibold text-[color:var(--foreground)]", children: "Your phone \u2014 optional" }),
1741
- /* @__PURE__ */ jsxRuntime.jsx(
1742
- "input",
1743
- {
1744
- type: "tel",
1745
- value: phone,
1746
- onChange: (e) => setPhone(e.target.value),
1747
- placeholder: "(503) 555-1212",
1748
- className: "w-full rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-accent)]"
1749
- }
1750
- )
1751
- ] }) : null,
1752
- /* @__PURE__ */ jsxRuntime.jsx(
1753
- "textarea",
1754
- {
1755
- value: message,
1756
- onChange: (e) => setMessage(e.target.value),
1757
- rows: 5,
1758
- placeholder: "How can we help?",
1759
- className: "mt-4 w-full resize-y rounded-lg border border-[color:var(--line)] bg-[color:var(--panel)] px-3 py-2 text-sm text-[color:var(--foreground)] placeholder:text-[color:var(--muted)] focus:outline-none focus:ring-2 focus:ring-[color:var(--color-accent)]"
1760
- }
1761
- ),
1762
- state === "error" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-[color:var(--chrome-danger)]", children: "Sending failed \u2014 try again." }) : null,
1763
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
1764
- /* @__PURE__ */ jsxRuntime.jsx(
1765
- "button",
1766
- {
1767
- type: "button",
1768
- onClick: close,
1769
- className: "rounded-full border border-[color:var(--line)] px-4 py-2 text-sm font-semibold text-[color:var(--foreground)] hover:bg-[color:var(--panel)]",
1770
- children: "Cancel"
1771
- }
1772
- ),
1773
- /* @__PURE__ */ jsxRuntime.jsx(
1774
- "button",
1775
- {
1776
- type: "button",
1777
- onClick: submit,
1778
- disabled: !message.trim() || state === "sending",
1779
- className: "rounded-full bg-[color:var(--color-accent-bg-safe,var(--color-accent))] px-4 py-2 text-sm font-semibold text-[#FFFFFF] transition hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
1780
- children: state === "sending" ? "Sending\u2026" : "Send request"
1781
- }
1782
- )
1783
- ] })
1784
- ] })
1785
- ] })
1786
- }
1787
- ),
1788
- document.body
1789
- ) : null
1578
+ var NAV_DISPLAY_OPTIONS = [
1579
+ { value: "menu", label: "Sub Menu" },
1580
+ { value: "tabs", label: "Tabs Only" },
1581
+ { value: "both", label: "Both" }
1582
+ ];
1583
+ function TopBarLinkAction({ label, description, href, icon: Icon }) {
1584
+ return /* @__PURE__ */ jsxRuntime.jsxs("a", { href, "aria-label": label, className: topBarIconButtonClass(), children: [
1585
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { size: TOPBAR_GLYPH_SIZE, className: "text-current" }),
1586
+ /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: label, description, align: "end" })
1790
1587
  ] });
1791
1588
  }
1792
- var SPEC_OVERLAY_ACCESS_CACHE_KEY = "politogy_spec_overlay_access";
1793
- function readSpecOverlayAccessCache() {
1794
- if (typeof window === "undefined") return null;
1795
- try {
1796
- const raw = window.sessionStorage.getItem(SPEC_OVERLAY_ACCESS_CACHE_KEY);
1797
- if (!raw) return null;
1798
- const parsed = JSON.parse(raw);
1799
- if (typeof parsed?.allowed === "boolean") return parsed;
1800
- } catch {
1801
- }
1802
- return null;
1803
- }
1804
- function writeSpecOverlayAccessCache(access) {
1805
- if (typeof window === "undefined") return;
1806
- try {
1807
- window.sessionStorage.setItem(SPEC_OVERLAY_ACCESS_CACHE_KEY, JSON.stringify(access));
1808
- } catch {
1809
- }
1810
- }
1811
- var locationChangeListeners = /* @__PURE__ */ new Set();
1812
- var historyPatched = false;
1813
- function ensureHistoryPatched() {
1814
- if (historyPatched || typeof window === "undefined") return;
1815
- historyPatched = true;
1816
- const notify = () => locationChangeListeners.forEach((fn) => fn());
1817
- const originalPushState = window.history.pushState.bind(window.history);
1818
- const originalReplaceState = window.history.replaceState.bind(window.history);
1819
- window.history.pushState = (...args) => {
1820
- originalPushState(...args);
1821
- notify();
1822
- };
1823
- window.history.replaceState = (...args) => {
1824
- originalReplaceState(...args);
1825
- notify();
1826
- };
1827
- window.addEventListener("popstate", notify);
1828
- }
1829
- function useLocationHref() {
1830
- const [href, setHref] = react.useState(
1831
- () => typeof window === "undefined" ? "" : window.location.href
1832
- );
1833
- react.useEffect(() => {
1834
- ensureHistoryPatched();
1835
- const onChange = () => setHref(window.location.href);
1836
- locationChangeListeners.add(onChange);
1837
- return () => {
1838
- locationChangeListeners.delete(onChange);
1839
- };
1840
- }, []);
1841
- return href;
1842
- }
1843
- function specOverlayFetchJson(path) {
1844
- return fetch(`${SPECS_ORIGIN}${path}`, { credentials: "include", signal: AbortSignal.timeout(1e4) }).then((res) => {
1845
- if (!res.ok) throw new Error(`spec overlay ${path}: ${res.status}`);
1846
- return res.json();
1847
- });
1848
- }
1849
- function isAutomatedCaptureSession() {
1850
- return typeof navigator !== "undefined" && navigator.webdriver === true;
1851
- }
1852
- function currentChromeTheme() {
1853
- const root = document.documentElement;
1854
- if (root.classList.contains("dark") || root.getAttribute("data-theme") === "dark") return "dark";
1855
- if (root.classList.contains("light") || root.getAttribute("data-theme") === "light") return "light";
1856
- return "light";
1857
- }
1858
- var SPEC_OVERLAY_ROOT_ATTR = "data-spec-overlay-root";
1859
- async function captureMyView(status, orgId, orgName) {
1860
- const { domToCanvas } = await import('modern-screenshot');
1861
- const w = window.innerWidth;
1862
- const h = window.innerHeight;
1863
- const theme = currentChromeTheme();
1864
- const bg = getComputedStyle(document.body).backgroundColor || (theme === "dark" ? "#0b0b0c" : "#ffffff");
1865
- const full = await domToCanvas(document.body, {
1866
- width: w,
1867
- height: h,
1868
- scale: 1,
1869
- backgroundColor: bg,
1870
- // Cross-origin images that refuse CORS render blank instead of failing.
1871
- fetch: { requestInit: { mode: "cors", credentials: "omit" }, placeholderImage: "" },
1872
- // Drop the Spec drawer and everything fixed (sidebar, toasts, popovers):
1873
- // the design is the page body only, so chrome would only add noise.
1874
- filter: (node) => !(node instanceof Element) || !node.hasAttribute(SPEC_OVERLAY_ROOT_ATTR) && getComputedStyle(node).position !== "fixed",
1875
- style: { transform: `translate(${-window.scrollX}px, ${-window.scrollY}px)` }
1876
- });
1877
- const el = (status.live_selector ? document.querySelector(status.live_selector) : null) ?? document.querySelector("[data-shell-content]");
1878
- let region = { x: 0, y: 0, width: w, height: h };
1879
- if (el) {
1880
- const r = el.getBoundingClientRect();
1881
- const x = Math.max(0, Math.round(r.left));
1882
- const y = Math.max(0, Math.round(r.top));
1883
- region = { x, y, width: Math.min(w - x, Math.round(r.width)), height: Math.min(h - y, Math.round(r.height)) };
1884
- }
1885
- const out = document.createElement("canvas");
1886
- out.width = region.width;
1887
- out.height = region.height;
1888
- const ctx = out.getContext("2d");
1889
- if (!ctx) throw new Error("page raster failed");
1890
- ctx.drawImage(full, region.x, region.y, region.width, region.height, 0, 0, region.width, region.height);
1891
- const blob = await new Promise((resolve) => out.toBlob(resolve, "image/png"));
1892
- if (!blob) throw new Error("page raster failed");
1893
- const form = new FormData();
1894
- form.append("image", blob, "live.png");
1895
- form.append(
1896
- "meta",
1897
- JSON.stringify({ url: window.location.href, theme, org_id: orgId ?? null, org_name: orgName ?? null, crop: null })
1589
+ function OrganizationSettingsAction({ href }) {
1590
+ return /* @__PURE__ */ jsxRuntime.jsx(
1591
+ TopBarLinkAction,
1592
+ {
1593
+ label: "Organization settings",
1594
+ description: "Users, Brand Kit, modes and billing for this organization.",
1595
+ href,
1596
+ icon: lu.LuSettings
1597
+ }
1898
1598
  );
1899
- const res = await fetch(status.capture_url ?? `${SPECS_ORIGIN}/api/overlay/capture`, {
1900
- method: "POST",
1901
- credentials: "include",
1902
- headers: { "X-Spec-Capture": "1" },
1903
- body: form,
1904
- signal: AbortSignal.timeout(1e4)
1905
- });
1906
- if (!res.ok) throw new Error(`capture ${res.status}: ${(await res.text()).slice(0, 160)}`);
1907
1599
  }
1908
- function SpecCaptureLoading() {
1909
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-spec-capture-loading": "", role: "status", "aria-live": "polite", className: "flex-1 overflow-hidden px-4 py-4", children: [
1910
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-center gap-3 text-sm text-[color:var(--foreground)]", children: [
1911
- /* @__PURE__ */ jsxRuntime.jsx(
1912
- "span",
1913
- {
1914
- "aria-hidden": "true",
1915
- className: "inline-block h-5 w-5 animate-spin rounded-full border-2 border-[color:var(--line)] border-t-[color:var(--color-accent)] motion-reduce:animate-none"
1916
- }
1917
- ),
1918
- "Capturing your view and comparing\u2026"
1919
- ] }),
1920
- ["Live \u2014 your view", "Diff"].map((label) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
1921
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-1 text-xs font-semibold uppercase tracking-wide text-[color:var(--muted)]", children: label }),
1922
- /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", className: "h-40 w-full animate-pulse rounded border border-[color:var(--line)] bg-[color:var(--panel)] motion-reduce:animate-none" })
1923
- ] }, label))
1924
- ] });
1925
- }
1926
- function specOverlayStatusPath() {
1927
- return `/api/overlay/status?url=${encodeURIComponent(window.location.href)}&theme=${currentChromeTheme()}`;
1928
- }
1929
- function SpecOverlayAction({ orgId, orgName } = {}) {
1930
- const [access, setAccess] = react.useState(() => readSpecOverlayAccessCache());
1931
- const [status, setStatus] = react.useState(null);
1932
- const [open, setOpen] = react.useState(false);
1933
- const [capture, setCapture] = react.useState({ state: "idle" });
1934
- const [frameKey, setFrameKey] = react.useState(0);
1935
- const [copied, setCopied] = react.useState("idle");
1936
- const href = useLocationHref();
1937
- const automated = isAutomatedCaptureSession();
1938
- react.useEffect(() => {
1939
- if (access || automated) return;
1940
- let cancelled = false;
1941
- specOverlayFetchJson("/api/overlay/access").then((data) => {
1942
- if (cancelled) return;
1943
- const next = data && typeof data.allowed === "boolean" ? data : { allowed: false };
1944
- setAccess(next);
1945
- writeSpecOverlayAccessCache(next);
1946
- }).catch(() => {
1947
- if (cancelled) return;
1948
- const next = { allowed: false };
1949
- setAccess(next);
1950
- writeSpecOverlayAccessCache(next);
1951
- });
1952
- return () => {
1953
- cancelled = true;
1954
- };
1955
- }, [access]);
1956
- react.useEffect(() => {
1957
- if (automated || !access?.allowed || typeof window === "undefined") return;
1958
- const timer = window.setTimeout(() => {
1959
- specOverlayFetchJson(specOverlayStatusPath()).then((data) => setStatus(data)).catch(() => setStatus(null));
1960
- }, 300);
1961
- return () => window.clearTimeout(timer);
1962
- }, [access?.allowed, href, orgId]);
1963
- const captureRun = react.useRef(0);
1964
- const accessRef = react.useRef(access);
1965
- accessRef.current = access;
1966
- const runCapture = async (s) => {
1967
- const run = ++captureRun.current;
1968
- const before = s.live_source === "viewer" ? s.finished_at : null;
1969
- setCapture({ state: "busy" });
1970
- try {
1971
- await new Promise((r) => setTimeout(r, 150));
1972
- await captureMyView(s, orgId, orgName);
1973
- for (let i = 0; i < 45; i++) {
1974
- await new Promise((r) => setTimeout(r, 3e3));
1975
- if (run !== captureRun.current) return;
1976
- if (!accessRef.current?.allowed) {
1977
- setCapture({ state: "idle" });
1978
- return;
1979
- }
1980
- const next = await specOverlayFetchJson(specOverlayStatusPath()).catch(() => null);
1981
- if (next && next.matched && next.live_source === "viewer" && next.finished_at !== before) {
1982
- setStatus(next);
1983
- setFrameKey((k) => k + 1);
1984
- setCapture({ state: "idle" });
1985
- return;
1986
- }
1987
- }
1988
- setCapture({ state: "error", message: "Compare did not finish in 2 min." });
1989
- } catch (e) {
1990
- if (run === captureRun.current) setCapture({ state: "error", message: e instanceof Error ? e.message : String(e) });
1600
+ function MyProfileAction({ href }) {
1601
+ return /* @__PURE__ */ jsxRuntime.jsx(
1602
+ TopBarLinkAction,
1603
+ {
1604
+ label: "My profile",
1605
+ description: "Your photo, organizations, security and notifications.",
1606
+ href,
1607
+ icon: lu.LuCircleUser
1991
1608
  }
1992
- };
1993
- react.useEffect(() => {
1994
- if (automated || !open || !status || !status.matched) return;
1995
- void runCapture(status);
1996
- return () => {
1997
- captureRun.current++;
1998
- };
1999
- }, [open, status && status.matched ? status.target_id : null]);
2000
- if (automated || !access?.allowed) return null;
2001
- const hasScore = status && status.matched && typeof status.mismatch_pct === "number";
2002
- const badgeLabel = hasScore ? `${Math.round(status.mismatch_pct)}%` : status ? "\u2014" : void 0;
2003
- const badgeTone = hasScore ? status && status.matched && status.passed ? "default" : "danger" : "default";
2004
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2005
- /* @__PURE__ */ jsxRuntime.jsxs(
2006
- "button",
2007
- {
2008
- type: "button",
2009
- "aria-label": "Spec",
2010
- "aria-expanded": open,
2011
- className: topBarIconButtonClass(badgeTone),
2012
- onClick: () => setOpen(true),
2013
- children: [
2014
- /* @__PURE__ */ jsxRuntime.jsx(LayoutGlyph, { size: TOPBAR_GLYPH_SIZE, className: "text-current" }),
2015
- badgeLabel ? /* @__PURE__ */ jsxRuntime.jsx(
2016
- "span",
2017
- {
2018
- className: [
2019
- "absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-normal leading-none text-[#FFFFFF]",
2020
- hasScore ? status && status.matched && status.passed ? "bg-[color:var(--chrome-success,#2f855a)]" : "bg-[color:var(--chrome-danger)]" : "bg-[color:var(--muted)]"
2021
- ].join(" "),
2022
- children: badgeLabel
2023
- }
2024
- ) : null,
2025
- !open ? /* @__PURE__ */ jsxRuntime.jsx(
2026
- TopBarTooltip,
2027
- {
2028
- title: "Spec",
2029
- description: "Spec design + visual regression for this page."
2030
- }
2031
- ) : null
2032
- ]
2033
- }
2034
- ),
2035
- open && typeof document !== "undefined" ? reactDom.createPortal(
2036
- /* @__PURE__ */ jsxRuntime.jsx(
2037
- "div",
2038
- {
2039
- className: "fixed inset-0 z-50 flex justify-end bg-black/40",
2040
- ...{ [SPEC_OVERLAY_ROOT_ATTR]: "" },
2041
- role: "dialog",
2042
- "aria-modal": "true",
2043
- "aria-label": "Spec overlay",
2044
- onMouseDown: (e) => {
2045
- if (e.target === e.currentTarget) setOpen(false);
2046
- },
2047
- onKeyDown: (e) => {
2048
- if (e.key === "Escape") setOpen(false);
2049
- },
2050
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full w-full max-w-[480px] flex-col border-l border-[color:var(--line)] bg-[color:var(--panel-elevated)] shadow-[var(--shadow-soft)]", children: [
2051
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-[color:var(--line)] px-4 py-3", children: [
2052
- /* @__PURE__ */ jsxRuntime.jsxs("h2", { className: "truncate text-sm font-bold tracking-[-0.01em] text-[color:var(--foreground)]", children: [
2053
- "Spec",
2054
- status && status.matched ? `: ${status.spec_title}` : ""
2055
- ] }),
2056
- /* @__PURE__ */ jsxRuntime.jsx(
2057
- "button",
2058
- {
2059
- type: "button",
2060
- "aria-label": "Close",
2061
- onClick: () => setOpen(false),
2062
- className: "rounded-full p-1 text-[color:var(--muted)] hover:bg-[color:var(--panel)] hover:text-[color:var(--foreground)]",
2063
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseGlyph, { size: 16 })
2064
- }
2065
- )
2066
- ] }),
2067
- status && status.matched ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2068
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 border-b border-[color:var(--line)] px-4 py-2 text-xs font-semibold", children: [
2069
- /* @__PURE__ */ jsxRuntime.jsx(
2070
- "a",
2071
- {
2072
- href: status.spec_url,
2073
- target: "_blank",
2074
- rel: "noreferrer",
2075
- className: "text-[color:var(--color-accent-fg,var(--color-accent))] hover:underline",
2076
- children: "Open spec \u2197"
2077
- }
2078
- ),
2079
- /* @__PURE__ */ jsxRuntime.jsx(
2080
- "a",
2081
- {
2082
- href: status.compare_url,
2083
- target: "_blank",
2084
- rel: "noreferrer",
2085
- className: "text-[color:var(--color-accent-fg,var(--color-accent))] hover:underline",
2086
- children: "Compare tab \u2197"
2087
- }
2088
- ),
2089
- /* @__PURE__ */ jsxRuntime.jsx(
2090
- "button",
2091
- {
2092
- type: "button",
2093
- disabled: capture.state === "busy",
2094
- onClick: () => void runCapture(status),
2095
- className: "ml-auto rounded-md border border-[color:var(--line)] px-2 py-1 text-[color:var(--foreground)] hover:bg-[color:var(--panel)] disabled:opacity-60",
2096
- title: "Your view is captured when the drawer opens. Recapture after changing the page.",
2097
- children: capture.state === "busy" ? "Capturing\u2026" : "Recapture"
2098
- }
2099
- ),
2100
- /* @__PURE__ */ jsxRuntime.jsx(
2101
- "button",
2102
- {
2103
- type: "button",
2104
- "data-spec-copy-prompt": "",
2105
- disabled: capture.state === "busy" || !status.prompt,
2106
- onClick: () => {
2107
- const text = status.prompt;
2108
- if (!text) return;
2109
- navigator.clipboard.writeText(text).then(() => setCopied("copied")).catch(() => setCopied("failed")).finally(() => window.setTimeout(() => setCopied("idle"), 2e3));
2110
- },
2111
- className: "rounded-md border border-[color:var(--line)] px-2 py-1 text-[color:var(--foreground)] hover:bg-[color:var(--panel)] disabled:opacity-60",
2112
- title: "Copy a repair prompt for an AI agent: spec, score, image links, top mismatched regions and instructions.",
2113
- children: copied === "copied" ? "Copied \u2713" : copied === "failed" ? "Copy failed" : "Copy prompt"
2114
- }
2115
- )
2116
- ] }),
2117
- status.live_size && status.design_size && capture.state !== "busy" ? /* @__PURE__ */ jsxRuntime.jsxs(
2118
- "p",
2119
- {
2120
- "data-spec-sizes": "",
2121
- className: [
2122
- "border-b border-[color:var(--line)] px-4 py-1.5 text-xs",
2123
- status.live_size.width === status.design_size.width && status.live_size.height === status.design_size.height ? "text-[color:var(--muted)]" : "font-semibold text-[color:var(--chrome-danger)]"
2124
- ].join(" "),
2125
- children: [
2126
- "Live ",
2127
- status.live_size.width,
2128
- "\xD7",
2129
- status.live_size.height,
2130
- " \xB7 Design ",
2131
- status.design_size.width,
2132
- "\xD7",
2133
- status.design_size.height,
2134
- status.live_size.width === status.design_size.width && status.live_size.height === status.design_size.height ? " \xB7 sizes match" : " \xB7 SIZE MISMATCH"
2135
- ]
2136
- }
2137
- ) : null,
2138
- capture.state === "error" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "border-b border-[color:var(--line)] px-4 py-2 text-xs text-[color:var(--chrome-danger)]", children: capture.message }) : null,
2139
- capture.state === "busy" ? /* @__PURE__ */ jsxRuntime.jsx(SpecCaptureLoading, {}) : /* @__PURE__ */ jsxRuntime.jsx(
2140
- "iframe",
2141
- {
2142
- src: status.overlay_url,
2143
- title: "Spec overlay",
2144
- className: "h-full w-full flex-1 border-0"
2145
- },
2146
- frameKey
2147
- )
2148
- ] }) : status && !status.matched ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-sm text-[color:var(--muted)]", children: [
2149
- /* @__PURE__ */ jsxRuntime.jsx("p", { children: "No compare target covers this page." }),
2150
- /* @__PURE__ */ jsxRuntime.jsx(
2151
- "a",
2152
- {
2153
- href: status.search_url,
2154
- target: "_blank",
2155
- rel: "noreferrer",
2156
- className: "mt-2 inline-block font-semibold text-[color:var(--color-accent-fg,var(--color-accent))] hover:underline",
2157
- children: "Search specs \u2197"
2158
- }
2159
- )
2160
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4", children: /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-4 w-full" }) })
2161
- ] })
2162
- }
2163
- ),
2164
- document.body
2165
- ) : null
2166
- ] });
2167
- }
2168
- function CloseGlyph({ size = 16, className }) {
2169
- return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: [
2170
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
2171
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
2172
- ] });
1609
+ );
2173
1610
  }
2174
- var NAV_DISPLAY_OPTIONS = [
2175
- { value: "menu", label: "Sub Menu" },
2176
- { value: "tabs", label: "Tabs Only" },
2177
- { value: "both", label: "Both" }
2178
- ];
2179
1611
  function parseCssUrl(value) {
2180
1612
  if (!value) return null;
2181
1613
  const m = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(value.trim());
@@ -2455,13 +1887,14 @@ function standardPolitogyNav(opts) {
2455
1887
  });
2456
1888
  };
2457
1889
  const accountRows = () => [
2458
- { kind: "section", label: "Account" },
1890
+ { kind: "section", label: "Account", railHidden: true },
2459
1891
  // My Profile moved to the Politogy ID account app (auth.politogyvrm.com)
2460
1892
  // with the retirement of app.politogyvrm.com.
2461
- { label: "My profile", icon: icon("circleUser"), href: withOrg(`${AUTH_ORIGIN}my-profile`) },
1893
+ { label: "My profile", icon: icon("circleUser"), href: withOrg(`${AUTH_ORIGIN}my-profile`), railHidden: true },
2462
1894
  {
2463
1895
  label: "Organization settings",
2464
1896
  icon: icon("settings"),
1897
+ railHidden: true,
2465
1898
  // Organization settings moved to the standalone Settings app
2466
1899
  // (settings.politogyvrm.com, Specs/Organization-Settings v12).
2467
1900
  href: withOrg(`${SETTINGS_ORIGIN}organization-settings`),
@@ -2697,6 +2130,33 @@ function shortEnvLabel(label) {
2697
2130
  if (l === "development") return "dev";
2698
2131
  return l;
2699
2132
  }
2133
+ function buildLineParts(meta) {
2134
+ const version = meta.version ? `v${meta.version.replace(/^v/, "")}` : null;
2135
+ const parsed = meta.commitDate ? new Date(meta.commitDate) : null;
2136
+ const date = parsed && !Number.isNaN(parsed.getTime()) ? parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" }) : null;
2137
+ const line = [version, date].filter(Boolean).join(" \xB7 ");
2138
+ const detail = [
2139
+ meta.envLabel ? shortEnvLabel(meta.envLabel) : null,
2140
+ meta.commit ? meta.commit.slice(0, 12) : null,
2141
+ version,
2142
+ date,
2143
+ meta.commitAuthor ? meta.commitAuthor.split(/\s+/)[0] : null
2144
+ ].filter(Boolean).join(" | ");
2145
+ return { line, detail };
2146
+ }
2147
+ function ShellBuildLine({ footer, hidden }) {
2148
+ const { line, detail } = buildLineParts(footer);
2149
+ if (!line && !detail) return null;
2150
+ return /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "div",
2152
+ {
2153
+ "data-shell-meta": true,
2154
+ title: detail,
2155
+ className: `mt-1.5 truncate text-center font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[10px] leading-[1.2] text-[color:var(--sidebar-row-text,rgba(255,255,255,0.66))] ${hidden}`,
2156
+ children: line || detail
2157
+ }
2158
+ );
2159
+ }
2700
2160
  var ORG_PILL_DEFAULT = "var(--color-accent-bg-safe,var(--color-accent))";
2701
2161
  var RailCollapsibleContext = react.createContext(false);
2702
2162
  function useRailCollapse() {
@@ -3108,7 +2568,7 @@ function NavSidebar({
3108
2568
  ] }),
3109
2569
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-1 flex-col overflow-y-auto px-[10px] py-3", children: /* @__PURE__ */ jsxRuntime.jsxs("nav", { className: "flex flex-col gap-0.5", children: [
3110
2570
  nav.groupLabel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: `${NAV_EYEBROW_CLASS} px-2 pb-1.5 pt-1 ${rc("max-md:hidden")}`, children: nav.groupLabel }) : null,
3111
- nav.items.map((item, idx) => {
2571
+ nav.items.filter((item) => !item.railHidden).map((item, idx) => {
3112
2572
  if (item.kind === "section") {
3113
2573
  return /* @__PURE__ */ jsxRuntime.jsx(
3114
2574
  NavSectionRow,
@@ -3189,31 +2649,7 @@ function NavSidebar({
3189
2649
  }
3190
2650
  )
3191
2651
  ] }, l.label)) }) : null,
3192
- footer.envLabel || footer.commit || footer.version ? /* @__PURE__ */ jsxRuntime.jsxs(
3193
- "div",
3194
- {
3195
- "data-shell-meta": true,
3196
- className: `mt-1.5 flex flex-col items-center gap-0.5 font-[family-name:var(--font-mono,ui-monospace,monospace)] text-[11px] text-[color:var(--sidebar-meta-text,rgba(255,255,255,0.72))] ${rc("max-md:hidden")}`,
3197
- children: [
3198
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1.5", children: [
3199
- footer.envLabel ? shortEnvLabel(footer.envLabel) : null,
3200
- footer.commit ? footer.commit.slice(-4) : null,
3201
- footer.version ? `v${footer.version.replace(/^v/, "")}` : null
3202
- ].filter((v) => Boolean(v)).map((v, i) => /* @__PURE__ */ jsxRuntime.jsxs(react.Fragment, { children: [
3203
- i > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "|" }) : null,
3204
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: v })
3205
- ] }, i)) }),
3206
- footer.commitDate || footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
3207
- footer.commitDate ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: new Date(footer.commitDate).toLocaleDateString(void 0, {
3208
- month: "short",
3209
- day: "numeric"
3210
- }) }) : null,
3211
- footer.commitDate && footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: "|" }) : null,
3212
- footer.commitAuthor ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: footer.commitAuthor.split(/\s+/)[0] }) : null
3213
- ] }) : null
3214
- ]
3215
- }
3216
- ) : null
2652
+ footer.envLabel || footer.commit || footer.version ? /* @__PURE__ */ jsxRuntime.jsx(ShellBuildLine, { footer, hidden: rc("max-md:hidden") }) : null
3217
2653
  ] }) : null
3218
2654
  ]
3219
2655
  }
@@ -5651,26 +5087,15 @@ function ChromeTopBarActions() {
5651
5087
  const state = useChromeState();
5652
5088
  if (!state) return null;
5653
5089
  const settledOrgId = state.orgsLoading ? null : state.orgId;
5090
+ const tail = orgIdTail(state.orgId);
5091
+ const orgQuery = tail ? `?${ORG_ID_QUERY_PARAM}=${tail}` : "";
5654
5092
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5655
- /* @__PURE__ */ jsxRuntime.jsx(
5656
- TopBarWidgetSlot,
5657
- {
5658
- title: "DevNotes",
5659
- description: "Capture bugs and notes, filed straight to Forge.",
5660
- children: /* @__PURE__ */ jsxRuntime.jsx(ChromeDevNotesMenu, {})
5661
- }
5662
- ),
5663
5093
  /* @__PURE__ */ jsxRuntime.jsx(FuelAction, { orgId: settledOrgId }),
5664
- /* @__PURE__ */ jsxRuntime.jsx(SpecOverlayAction, { orgId: state.orgId, orgName: state.orgs.find((o) => o.id === state.orgId)?.name ?? null }),
5665
5094
  /* @__PURE__ */ jsxRuntime.jsx(NotificationsAction, { orgId: settledOrgId }),
5666
- /* @__PURE__ */ jsxRuntime.jsx(SupportAction, { orgId: state.orgId, orgs: state.orgs.length ? state.orgs : void 0 })
5095
+ /* @__PURE__ */ jsxRuntime.jsx(OrganizationSettingsAction, { href: `${settingsBaseUrl()}/organization-settings${orgQuery}` }),
5096
+ /* @__PURE__ */ jsxRuntime.jsx(MyProfileAction, { href: `${authBaseUrl()}/my-profile${orgQuery}` })
5667
5097
  ] });
5668
5098
  }
5669
- function ChromeDevNotesMenu() {
5670
- const { ready, openTasks, activate } = react.useContext(DevNotesTasksContext);
5671
- if (!ready) return null;
5672
- return /* @__PURE__ */ jsxRuntime.jsx("span", { style: { display: "contents" }, onPointerDownCapture: activate, onFocusCapture: activate, onKeyDownCapture: activate, children: /* @__PURE__ */ jsxRuntime.jsx(devnotes.DevNotesMenu, { position: "top-right", icon: lu.LuNotebookPen, onViewTasks: openTasks }) });
5673
- }
5674
5099
  function useIsSuperAdmin() {
5675
5100
  const state = useChromeState();
5676
5101
  return (state?.orgs ?? []).some((o) => /super/i.test(o.rawRole ?? ""));
@@ -5720,11 +5145,13 @@ function useChromeUserMenu({
5720
5145
  organizationSettingsHref: `${settings}/organization-settings${orgSuffix}`,
5721
5146
  roles: (state?.orgs ?? []).filter((o) => o.rawRole || o.role).map((o) => ({ role: o.rawRole ?? o.role, orgName: o.name })),
5722
5147
  accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],
5723
- addAccountHref: `${auth}/my-profile#accounts`,
5148
+ addAccountHref: `${auth}/my-profile#accounts`
5724
5149
  // No theme control: the product is light only (Reference/UI-Platform
5725
5150
  // "Theme — light only"; shell 0.73.0 stopped applying dark). The account
5726
5151
  // menu rendered an "Appearance" radio with one option until 0.77.0.
5727
- navDisplay: state ? { value: state.navDisplay, onChange: state.setNavDisplay } : void 0
5152
+ // Ben 2026-11-04: no Navigation display option in the account menu. The
5153
+ // preference itself (state.navDisplay, its storage and the frame's use of
5154
+ // it) is unchanged; only the menu control is gone.
5728
5155
  };
5729
5156
  }
5730
5157
  var MODE_DEFS = [