@the-portland-company/shell 0.69.0 → 0.70.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.
@@ -57,7 +57,45 @@ var cookie=fromCookie();
57
57
  if(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
58
58
  if(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}
59
59
  }catch(e){}})();`;
60
+ var RAIL_STORAGE_KEY = "politogy_rail";
61
+ var RAIL_COOKIE = "politogy_rail";
62
+ var RAIL_DEFAULT = "collapsed";
63
+ function isRailPreference(v) {
64
+ return v === "expanded" || v === "collapsed";
65
+ }
66
+ function readRailPreference() {
67
+ if (typeof document !== "undefined") {
68
+ try {
69
+ const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${RAIL_COOKIE}=([^;]+)`));
70
+ const v = m ? decodeURIComponent(m[1]) : null;
71
+ if (isRailPreference(v)) return v;
72
+ } catch {
73
+ }
74
+ }
75
+ if (typeof localStorage !== "undefined") {
76
+ try {
77
+ const v = localStorage.getItem(RAIL_STORAGE_KEY);
78
+ if (isRailPreference(v)) return v;
79
+ } catch {
80
+ }
81
+ }
82
+ return RAIL_DEFAULT;
83
+ }
84
+ function writeRailPreference(pref) {
85
+ if (typeof document === "undefined" || typeof window === "undefined") return;
86
+ const host = window.location.hostname;
87
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
88
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
89
+ try {
90
+ document.cookie = `${RAIL_COOKIE}=${pref}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
91
+ } catch {
92
+ }
93
+ try {
94
+ localStorage.setItem(RAIL_STORAGE_KEY, pref);
95
+ } catch {
96
+ }
97
+ }
60
98
 
61
- export { LEGACY_NAV_DISPLAY_KEY, NAV_DISPLAY_COOKIE, NAV_DISPLAY_DEFAULT, NAV_DISPLAY_PREPAINT_SCRIPT, NAV_DISPLAY_STORAGE_KEY, isNavDisplayPreference, readNavDisplayCookie, readNavDisplayLocal, resolveNavDisplay, writeNavDisplayCookie, writeNavDisplayLocal };
62
- //# sourceMappingURL=chunk-6UHJV2FH.js.map
63
- //# sourceMappingURL=chunk-6UHJV2FH.js.map
99
+ export { LEGACY_NAV_DISPLAY_KEY, NAV_DISPLAY_COOKIE, NAV_DISPLAY_DEFAULT, NAV_DISPLAY_PREPAINT_SCRIPT, NAV_DISPLAY_STORAGE_KEY, RAIL_COOKIE, RAIL_DEFAULT, RAIL_STORAGE_KEY, isNavDisplayPreference, isRailPreference, readNavDisplayCookie, readNavDisplayLocal, readRailPreference, resolveNavDisplay, writeNavDisplayCookie, writeNavDisplayLocal, writeRailPreference };
100
+ //# sourceMappingURL=chunk-7YVJ7HQG.js.map
101
+ //# sourceMappingURL=chunk-7YVJ7HQG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/nav-preference.ts"],"names":[],"mappings":"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","file":"chunk-7YVJ7HQG.js","sourcesContent":["// Universal navigation-display preference — the same cross-app contract as the\n// theme (see ./theme-preference, which this deliberately mirrors).\n//\n// The user's choice of how navigation renders (Menu / Tabs / Both) follows the\n// authenticated user across every Politogy app + subdomain. Three layers:\n// 1. VRM Core `profiles.nav_display_preference` — source of truth, persisted\n// via the chrome API (`PATCH me/preferences { navDisplay }`) and read back\n// from `GET me` (`navDisplay`).\n// 2. `.politogyvrm.com` cookie `politogy_nav_display` — the cross-subdomain\n// vehicle (app session cookies are host-only, and so is localStorage).\n// 3. localStorage — same-app fast reads and cross-tab `storage` events.\n//\n// Before this existed the preference lived ONLY in localStorage under\n// `vrm.navDisplay`, which is per-ORIGIN: every app subdomain kept its own copy\n// and a freshly opened app booted the default, so the setting appeared not to\n// hold when moving between apps. The legacy key is still read as a fallback so\n// an existing local choice survives the upgrade.\n//\n// Server-safe (no 'use client'): layouts may import NAV_DISPLAY_PREPAINT_SCRIPT.\n\nexport type NavDisplayPreference = 'menu' | 'tabs' | 'both'\n\n// One key for BOTH the localStorage store and the cross-subdomain cookie, so a\n// choice in one app is the same key in the next.\nexport const NAV_DISPLAY_STORAGE_KEY = 'politogy_nav_display'\nexport const NAV_DISPLAY_COOKIE = 'politogy_nav_display'\n\n/** Pre-0.37 host-only key. Read for migration; never written. */\nexport const LEGACY_NAV_DISPLAY_KEY = 'vrm.navDisplay'\n\nexport const NAV_DISPLAY_DEFAULT: NavDisplayPreference = 'both'\n\nconst VALID: readonly NavDisplayPreference[] = ['menu', 'tabs', 'both']\n\nexport function isNavDisplayPreference(v: unknown): v is NavDisplayPreference {\n return typeof v === 'string' && (VALID as readonly string[]).includes(v)\n}\n\n/** Read the shared cookie (browser only; null on the server or when unset). */\nexport function readNavDisplayCookie(): NavDisplayPreference | null {\n if (typeof document === 'undefined') return null\n try {\n const m = document.cookie.match(\n new RegExp(`(?:^|;\\\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`),\n )\n const v = m ? decodeURIComponent(m[1]) : null\n return isNavDisplayPreference(v) ? v : null\n } catch {\n return null\n }\n}\n\n// Same host-aware scoping as the theme cookie: parent domain on\n// *.politogyvrm.com so every app subdomain reads it, host-only elsewhere\n// (localhost, *.pages.dev preview) because browsers silently drop a mismatched\n// Domain attribute. 1-year, SameSite=Lax, Secure on https.\nexport function writeNavDisplayCookie(pref: NavDisplayPreference): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n const host = window.location.hostname\n const domainAttr = /(^|\\.)politogyvrm\\.com$/i.test(host) ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(\n pref,\n )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n}\n\n/** Read the local store, falling back to the legacy host-only key. */\nexport function readNavDisplayLocal(): NavDisplayPreference | null {\n if (typeof localStorage === 'undefined') return null\n try {\n const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY)\n if (isNavDisplayPreference(current)) return current\n const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY)\n return isNavDisplayPreference(legacy) ? legacy : null\n } catch {\n return null\n }\n}\n\nexport function writeNavDisplayLocal(pref: NavDisplayPreference): void {\n if (typeof localStorage === 'undefined') return\n try {\n localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref)\n } catch {\n /* storage unavailable */\n }\n}\n\n/** Boot precedence: cookie → localStorage (incl. legacy) → default. */\nexport function resolveNavDisplay(): NavDisplayPreference {\n return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT\n}\n\n// Inline pre-paint script — optional, and the mirror of THEME_PREPAINT_SCRIPT.\n// Nav display has no paint-blocking visual (the frame is client-rendered), but\n// seeding localStorage from the cookie before hydration keeps the first render\n// on the synced value instead of the default. Self-contained and silent.\nexport const NAV_DISPLAY_PREPAINT_SCRIPT = `(function(){try{\nvar KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};\nvar VALID=[\"menu\",\"tabs\",\"both\"];\nfunction fromCookie(){try{var m=document.cookie.match(/(?:^|;\\\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}\nvar cookie=fromCookie();\nif(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}\nif(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}\n}catch(e){}})();`\n\n// ---------------------------------------------------------------------------\n// Rail expand preference — Left-Navigation §11.6: below `md` the rail\n// collapses to the 64px icon rail; \"the user can also collapse and expand it;\n// the choice is remembered per user\". Same cookie + localStorage layers as the\n// nav-display preference above (no server column yet), so the choice follows\n// the user across every app subdomain and survives reloads. Only an explicit\n// Expand / Collapse writes it; a route change that closes the overlay does not.\n// ---------------------------------------------------------------------------\n\nexport type RailPreference = 'expanded' | 'collapsed'\n\nexport const RAIL_STORAGE_KEY = 'politogy_rail'\nexport const RAIL_COOKIE = 'politogy_rail'\nexport const RAIL_DEFAULT: RailPreference = 'collapsed'\n\nexport function isRailPreference(v: unknown): v is RailPreference {\n return v === 'expanded' || v === 'collapsed'\n}\n\nexport function readRailPreference(): RailPreference {\n if (typeof document !== 'undefined') {\n try {\n const m = document.cookie.match(new RegExp(`(?:^|;\\\\s*)${RAIL_COOKIE}=([^;]+)`))\n const v = m ? decodeURIComponent(m[1]) : null\n if (isRailPreference(v)) return v\n } catch {\n /* ignore */\n }\n }\n if (typeof localStorage !== 'undefined') {\n try {\n const v = localStorage.getItem(RAIL_STORAGE_KEY)\n if (isRailPreference(v)) return v\n } catch {\n /* ignore */\n }\n }\n return RAIL_DEFAULT\n}\n\nexport function writeRailPreference(pref: RailPreference): void {\n if (typeof document === 'undefined' || typeof window === 'undefined') return\n const host = window.location.hostname\n const domainAttr = /(^|\\.)politogyvrm\\.com$/i.test(host) ? '; Domain=.politogyvrm.com' : ''\n const secure = window.location.protocol === 'https:' ? '; Secure' : ''\n try {\n document.cookie = `${RAIL_COOKIE}=${pref}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`\n } catch {\n /* ignore */\n }\n try {\n localStorage.setItem(RAIL_STORAGE_KEY, pref)\n } catch {\n /* storage unavailable */\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { warnDeprecated } from './chunk-LPSBSAF3.js';
2
2
  import { readThemeCookie, THEME_STORAGE_KEY, isThemePreference, applyThemePreference, readPendingThemeChoice, writeThemeCookie, clearPendingThemeChoice, markThemeChoicePending } from './chunk-NHBNM5P3.js';
3
+ import { readRailPreference, writeRailPreference } from './chunk-7YVJ7HQG.js';
3
4
  import { createContext, useRef, useState, useEffect, useContext, useCallback, useMemo, useSyncExternalStore } from 'react';
4
5
  import clsx from 'clsx';
5
6
  import { FiUser, FiSettings, FiMessageSquare, FiMail, FiInfo, FiSun, FiMoon, FiHelpCircle, FiLifeBuoy, FiAlertCircle, FiAlertTriangle, FiBell, FiGrid, FiZap, FiBatteryCharging, FiBattery } from 'react-icons/fi';
@@ -1763,7 +1764,6 @@ var PHONEBANK_ORIGIN = "https://phonebank.politogyvrm.com/";
1763
1764
  var CANVASS_ORIGIN = "https://canvass.politogyvrm.com/";
1764
1765
  var SIGNS_ORIGIN = "https://signs.politogyvrm.com/";
1765
1766
  var POLLS_ORIGIN = "https://polls.politogyvrm.com/";
1766
- var CONTACTS_TRIBES = "https://contacts.politogyvrm.com/tribes";
1767
1767
  var PETITION_MODE_ORIGIN = "https://petition-mode.politogyvrm.com/";
1768
1768
  function applyHiddenItems(items, hidden) {
1769
1769
  if (!hidden || hidden.length === 0) return items;
@@ -1833,10 +1833,6 @@ var APP_POLLS_CHILDREN = [
1833
1833
  { label: "Command Center", icon: "grid", href: "https://polls.politogyvrm.com/" },
1834
1834
  { label: "New Poll", icon: "edit", href: "https://polls.politogyvrm.com/polls/new" }
1835
1835
  ];
1836
- var APP_CTAS_CHILDREN = [
1837
- { label: "CTAs", icon: "list", href: "https://ctas.politogyvrm.com/" },
1838
- { label: "Canvas", icon: "edit", href: "https://ctas.politogyvrm.com/?tab=canvas" }
1839
- ];
1840
1836
  var APP_A2P_CHILDREN = [
1841
1837
  { label: "Dashboard", icon: "grid", path: "dashboard" },
1842
1838
  { label: "Compose", icon: "edit", path: "compose" },
@@ -1884,16 +1880,27 @@ function standardPolitogyNav(opts) {
1884
1880
  return { label: c.label, icon: icon(c.icon), href: withOrg(withMode) };
1885
1881
  });
1886
1882
  };
1883
+ const accountRows = () => [
1884
+ { kind: "section", label: "Account" },
1885
+ // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
1886
+ { label: "My profile", icon: icon("circleUser"), href: withOrg(abs("/my-profile")) },
1887
+ {
1888
+ label: "Organization settings",
1889
+ icon: icon("settings"),
1890
+ href: withOrg(abs("/organization-settings")),
1891
+ children: branches(ORG_SETTINGS_CHILDREN)
1892
+ }
1893
+ ];
1887
1894
  const buildRelationship = () => {
1888
1895
  const contacts = opts.contactsItem ? {
1889
1896
  label: "Contacts",
1890
- icon: icon("users"),
1897
+ icon: icon("usersRound"),
1891
1898
  href: opts.contactsItem.href,
1892
1899
  children: opts.contactsItem.children,
1893
1900
  badge: opts.contactsItem.count ?? void 0
1894
1901
  } : {
1895
1902
  label: "Contacts",
1896
- icon: icon("users"),
1903
+ icon: icon("usersRound"),
1897
1904
  href: withOrg(`${CONTACTS_APP}/`),
1898
1905
  children: APP_CONTACTS_CHILDREN.map((c) => ({
1899
1906
  label: c.label,
@@ -1903,7 +1910,7 @@ function standardPolitogyNav(opts) {
1903
1910
  };
1904
1911
  return [
1905
1912
  { kind: "section", label: "Relationship" },
1906
- { label: "Dashboard", icon: icon("home"), href: withOrg(abs("/relationship/dashboard")) },
1913
+ { label: "Dashboard", icon: icon("layoutDashboard"), href: withOrg(abs("/relationship/dashboard")) },
1907
1914
  contacts,
1908
1915
  {
1909
1916
  label: "Email broadcast",
@@ -1926,40 +1933,20 @@ function standardPolitogyNav(opts) {
1926
1933
  },
1927
1934
  { kind: "section", label: "Collect" },
1928
1935
  // Carries no top-level href in VRM core (parent row only toggles).
1929
- { label: "Calls to action", icon: icon("layout"), children: leaves(CTA_CHILDREN) },
1936
+ { label: "Calls to action", icon: icon("mousePointerClick"), children: leaves(CTA_CHILDREN) },
1930
1937
  {
1931
1938
  label: "Forms",
1932
- icon: icon("activity"),
1939
+ icon: icon("fileText"),
1933
1940
  href: withOrg("https://forms.politogyvrm.com/"),
1934
1941
  children: APP_FORMS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
1935
1942
  },
1936
1943
  {
1937
1944
  label: "Surveys",
1938
- icon: icon("fileText"),
1945
+ icon: icon("clipboardList"),
1939
1946
  href: withOrg("https://surveys.politogyvrm.com/surveys"),
1940
1947
  children: APP_SURVEYS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
1941
1948
  },
1942
- {
1943
- label: "Polls",
1944
- icon: icon("barChart"),
1945
- href: withOrg("https://polls.politogyvrm.com/"),
1946
- children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
1947
- },
1948
- {
1949
- label: "CTAs",
1950
- icon: icon("layout"),
1951
- href: withOrg("https://ctas.politogyvrm.com/"),
1952
- children: APP_CTAS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
1953
- },
1954
- { kind: "section", label: "Account" },
1955
- // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
1956
- { label: "My profile", icon: icon("user"), href: withOrg(abs("/my-profile")) },
1957
- {
1958
- label: "Organization settings",
1959
- icon: icon("settings"),
1960
- href: withOrg(abs("/organization-settings")),
1961
- children: branches(ORG_SETTINGS_CHILDREN)
1962
- }
1949
+ ...accountRows()
1963
1950
  ];
1964
1951
  };
1965
1952
  const buildCampaign = () => {
@@ -1968,40 +1955,57 @@ function standardPolitogyNav(opts) {
1968
1955
  ...opts.campaignAvailability
1969
1956
  };
1970
1957
  const rows = [
1971
- { label: "Dashboard", icon: icon("home"), href: withOrg(abs("/campaign/dashboard")) },
1972
- { label: "Voter Contact", icon: icon("target"), href: withOrg(abs("/campaign/campaigns")) },
1958
+ // Left-Navigation §9.2: Race data -> Research -> GOTV -> Account.
1959
+ // Battleground is the Campaign landing page; there is no Campaign
1960
+ // Dashboard row, and Voter Contact and Tribes have no row.
1961
+ { kind: "section", label: "Race data" },
1962
+ { label: "Battleground", icon: icon("crosshair"), href: withOrg(abs("/campaign/battleground")) },
1963
+ { label: "District data", icon: icon("landmark"), href: withOrg(abs("/campaign/districts")) },
1964
+ { kind: "section", label: "Research" },
1973
1965
  {
1974
- label: "A2P",
1975
- icon: icon("messageSquare"),
1976
- href: withOrg(A2P_ORIGIN),
1977
- children: appLeaves(A2P_ORIGIN, APP_A2P_CHILDREN),
1978
- flag: "a2p"
1966
+ label: "Polling",
1967
+ icon: icon("chartNoAxes"),
1968
+ href: withOrg(POLLS_ORIGIN),
1969
+ children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) })),
1970
+ flag: "polling"
1979
1971
  },
1972
+ { kind: "section", label: "GOTV" },
1980
1973
  {
1981
- label: "Phone Banking",
1974
+ label: "Field canvassing",
1975
+ icon: icon("doorOpen"),
1976
+ href: withOrg(CANVASS_ORIGIN),
1977
+ children: appLeaves(CANVASS_ORIGIN, APP_CANVASS_CHILDREN),
1978
+ flag: "canvass"
1979
+ },
1980
+ {
1981
+ label: "Phone banking",
1982
1982
  icon: icon("phone"),
1983
1983
  href: withOrg(PHONEBANK_ORIGIN),
1984
1984
  children: appLeaves(PHONEBANK_ORIGIN, APP_PHONEBANK_CHILDREN),
1985
1985
  flag: "phonebank"
1986
1986
  },
1987
1987
  {
1988
- label: "Field Canvassing",
1989
- icon: icon("map"),
1990
- href: withOrg(CANVASS_ORIGIN),
1991
- children: appLeaves(CANVASS_ORIGIN, APP_CANVASS_CHILDREN),
1992
- flag: "canvass"
1988
+ label: "A2P texting",
1989
+ icon: icon("messageSquare"),
1990
+ href: withOrg(A2P_ORIGIN),
1991
+ children: appLeaves(A2P_ORIGIN, APP_A2P_CHILDREN),
1992
+ flag: "a2p"
1993
1993
  },
1994
1994
  {
1995
- label: "Sign Mapping",
1996
- icon: icon("mapPin"),
1995
+ label: "Sign mapping",
1996
+ icon: icon("signpost"),
1997
1997
  href: withOrg(SIGNS_ORIGIN),
1998
1998
  children: appLeaves(SIGNS_ORIGIN, APP_SIGNS_CHILDREN),
1999
1999
  flag: "signs"
2000
2000
  },
2001
- { label: "Tribes", icon: icon("users"), href: withOrg(CONTACTS_TRIBES), flag: "tribes" },
2002
- { label: "Polling Engine", icon: icon("barChart"), href: withOrg(POLLS_ORIGIN), flag: "polling" }
2001
+ ...accountRows()
2003
2002
  ];
2004
- return rows.filter((r) => !r.flag || avail[r.flag]).map(({ flag: _flag, ...item }) => item);
2003
+ const kept = rows.filter((r) => !r.flag || avail[r.flag]).map(({ flag: _flag, ...item }) => item);
2004
+ return kept.filter((item, idx) => {
2005
+ if (item.kind !== "section") return true;
2006
+ const next = kept[idx + 1];
2007
+ return !!next && next.kind !== "section";
2008
+ });
2005
2009
  };
2006
2010
  const buildPetition = () => [
2007
2011
  opts.petitionsItem ? {
@@ -2108,7 +2112,9 @@ function NavSidebar({
2108
2112
  footer,
2109
2113
  linkComponent,
2110
2114
  open,
2111
- onClose
2115
+ onClose,
2116
+ onOpen,
2117
+ onNavigate
2112
2118
  }) {
2113
2119
  const Link = linkComponent ?? DefaultLink;
2114
2120
  const collapsible = !open;
@@ -2205,7 +2211,7 @@ function NavSidebar({
2205
2211
  style: { fontFamily: 'var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif)' },
2206
2212
  onKeyDown: trapTab,
2207
2213
  onClick: (e) => {
2208
- if (open && e.target.closest("a[href]")) onClose?.();
2214
+ if (open && e.target.closest("a[href]")) (onNavigate ?? onClose)?.();
2209
2215
  },
2210
2216
  children: [
2211
2217
  open ? /* @__PURE__ */ jsx(
@@ -2214,18 +2220,30 @@ function NavSidebar({
2214
2220
  type: "button",
2215
2221
  ref: closeButtonRef,
2216
2222
  onClick: onClose,
2217
- "aria-label": "Close menu",
2223
+ "aria-label": "Collapse navigation",
2218
2224
  className: "absolute right-3 top-3 flex h-8 w-8 items-center justify-center rounded-full text-[color:var(--muted)] transition hover:bg-[color:var(--panel-elevated)] hover:text-[color:var(--foreground)] md:hidden",
2219
2225
  children: /* @__PURE__ */ jsx(CloseIcon, {})
2220
2226
  }
2221
2227
  ) : null,
2222
2228
  /* @__PURE__ */ jsxs("div", { className: `flex flex-shrink-0 flex-col gap-[14px] border-b border-[color:var(--sidebar-line,var(--line))] px-[18px] py-4 ${rc("max-md:items-center max-md:px-[10px]")}`, children: [
2229
+ onOpen ? /* @__PURE__ */ jsx(
2230
+ "button",
2231
+ {
2232
+ type: "button",
2233
+ onClick: onOpen,
2234
+ "aria-label": "Expand navigation",
2235
+ "aria-expanded": open ?? false,
2236
+ "data-rail-toggle": "expand",
2237
+ className: `hidden h-8 w-8 items-center justify-center rounded-lg ${rc("max-md:flex")}`,
2238
+ children: brand.logoSquareSrc ? /* @__PURE__ */ jsx("img", { src: brand.logoSquareSrc, alt: "", draggable: false, className: "h-8 w-8 object-contain" }) : brand.logoPending ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "block h-8 w-8" }) : /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 items-center justify-center rounded-xl bg-[color:var(--color-accent)] text-sm font-black text-[#FFFFFF]", children: brand.badgeLabel })
2239
+ }
2240
+ ) : null,
2223
2241
  /* @__PURE__ */ jsxs(
2224
2242
  Link,
2225
2243
  {
2226
2244
  href: brand.homeHref ?? "/",
2227
2245
  "aria-label": brand.logoAlt ?? brand.title,
2228
- className: `flex items-center gap-2.5 ${rc("max-md:justify-center")}`,
2246
+ className: `flex items-center gap-2.5 ${rc("max-md:justify-center")} ${onOpen ? rc("max-md:hidden") : ""}`,
2229
2247
  children: [
2230
2248
  brand.logoSquareSrc ? /* @__PURE__ */ jsx(
2231
2249
  "img",
@@ -2999,7 +3017,7 @@ function NavTopBar({
2999
3017
  const initials = (hasRealName ? rawName : user?.email ?? "P").slice(0, 2).toUpperCase();
3000
3018
  const secondary = hasRealName ? user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member" : [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
3001
3019
  return /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-20", children: [
3002
- /* @__PURE__ */ jsxs("header", { className: "flex h-[var(--topbar-height,60px)] items-center justify-between gap-[14px] border-b border-[color:var(--line)] bg-[color:var(--panel)] px-[var(--topbar-padding-x,20px)]", children: [
3020
+ /* @__PURE__ */ jsxs("header", { className: "flex min-w-0 h-[var(--topbar-height,60px)] items-center justify-between gap-[8px] md:gap-[14px] border-b border-[color:var(--line)] bg-[color:var(--panel)] px-[var(--topbar-padding-x,20px)]", children: [
3003
3021
  onOpenNav ? /* @__PURE__ */ jsx(
3004
3022
  "button",
3005
3023
  {
@@ -3012,7 +3030,7 @@ function NavTopBar({
3012
3030
  children: /* @__PURE__ */ jsx(HamburgerIcon, {})
3013
3031
  }
3014
3032
  ) : null,
3015
- /* @__PURE__ */ jsxs("label", { className: "relative flex h-9 w-[320px] max-w-full shrink-0 items-center", children: [
3033
+ /* @__PURE__ */ jsxs("label", { className: "relative flex h-9 w-[320px] min-w-0 max-w-full shrink items-center md:shrink-0", children: [
3016
3034
  /* @__PURE__ */ jsx(SearchIcon, { className: "pointer-events-none absolute left-[10px] h-[15px] w-[15px] text-[color:var(--muted)]" }),
3017
3035
  /* @__PURE__ */ jsx(
3018
3036
  "input",
@@ -3032,13 +3050,13 @@ function NavTopBar({
3032
3050
  "kbd",
3033
3051
  {
3034
3052
  "aria-hidden": true,
3035
- className: "pointer-events-none absolute right-[10px] rounded-[3px] border border-[color:var(--line)] px-[5px] py-px font-[family-name:var(--font-plex-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--muted)]",
3053
+ className: "pointer-events-none absolute right-[10px] hidden md:block rounded-[3px] border border-[color:var(--line)] px-[5px] py-px font-[family-name:var(--font-plex-mono,ui-monospace,monospace)] text-[12px] leading-[1.2] text-[color:var(--muted)]",
3036
3054
  children: "/"
3037
3055
  }
3038
3056
  )
3039
3057
  ] }),
3040
3058
  /* @__PURE__ */ jsxs("div", { className: "relative ml-auto flex shrink-0 items-center gap-[var(--topbar-icon-gap,8px)]", children: [
3041
- /* @__PURE__ */ jsxs(
3059
+ buildNotes ? /* @__PURE__ */ jsxs(
3042
3060
  "button",
3043
3061
  {
3044
3062
  type: "button",
@@ -3049,7 +3067,7 @@ function NavTopBar({
3049
3067
  buildNotes?.label ?? "Build notes"
3050
3068
  ]
3051
3069
  }
3052
- ),
3070
+ ) : null,
3053
3071
  actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsx(NavTopBarActionButton, { action }, action.key)) : null,
3054
3072
  actions,
3055
3073
  themeToggle,
@@ -3624,9 +3642,22 @@ function NavAppShell({
3624
3642
  );
3625
3643
  }
3626
3644
  const [navOpen, setNavOpen] = useState(false);
3645
+ const expandRail = () => {
3646
+ writeRailPreference("expanded");
3647
+ setNavOpen(true);
3648
+ };
3649
+ const collapseRail = () => {
3650
+ writeRailPreference("collapsed");
3651
+ setNavOpen(false);
3652
+ };
3627
3653
  useEffect(() => {
3628
3654
  setNavOpen(false);
3629
3655
  }, [sidebar.pathname]);
3656
+ useEffect(() => {
3657
+ if (readRailPreference() !== "expanded") return;
3658
+ const narrow = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 767.98px)").matches : false;
3659
+ if (narrow) setNavOpen(true);
3660
+ }, []);
3630
3661
  useEffect(() => {
3631
3662
  if (!navOpen) return;
3632
3663
  const previous = document.body.style.overflow;
@@ -3647,15 +3678,23 @@ function NavAppShell({
3647
3678
  "data-politogy-mode": modeId,
3648
3679
  className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]",
3649
3680
  children: [
3650
- /* @__PURE__ */ jsx(NavSidebar, { ...sidebar, footer: sidebarFooter, open: navOpen, onClose: () => setNavOpen(false) }),
3681
+ /* @__PURE__ */ jsx(
3682
+ NavSidebar,
3683
+ {
3684
+ ...sidebar,
3685
+ footer: sidebarFooter,
3686
+ open: navOpen,
3687
+ onClose: collapseRail,
3688
+ onOpen: expandRail,
3689
+ onNavigate: () => setNavOpen(false)
3690
+ }
3691
+ ),
3651
3692
  /* @__PURE__ */ jsxs("div", { className: "pl-[var(--sidebar-collapsed-width,64px)] md:pl-[var(--sidebar-width,236px)]", children: [
3652
3693
  /* @__PURE__ */ jsx(
3653
3694
  NavTopBar,
3654
3695
  {
3655
3696
  ...topBar,
3656
- accountMenu: topBar.accountMenu ?? (accountMenuPlacement === "sidebar" ? "sidebar" : "topbar"),
3657
- onOpenNav: () => setNavOpen(true),
3658
- navOpen
3697
+ accountMenu: topBar.accountMenu ?? (accountMenuPlacement === "sidebar" ? "sidebar" : "topbar")
3659
3698
  }
3660
3699
  ),
3661
3700
  betaBanner ? /* @__PURE__ */ jsx(NavBetaBanner, { ...betaBanner }) : null,
@@ -3684,5 +3723,5 @@ function NavAppShell({
3684
3723
  }
3685
3724
 
3686
3725
  export { BETA_BANNER_DISMISS_KEY, CAMPAIGN_FEATURE_AVAILABILITY, CORE_APP_ORIGIN, CORE_BILLING_PATH, FuelAction, NavAppShell, NavBetaBanner, NavChromeProvider, NavDisplayAction, NavSidebar, NavTabStrip, NavThemeControl, NavTopBar, NotificationsAction, SPECS_ORIGIN, SpecOverlayAction, SupportAction, TopBarCountBadge, TopBarPanelArrow, TopBarPopover, TopBarTooltip, TopBarWidgetSlot, clearBetaBannerDismissal, envChipClasses, isNavItemActive, resolveOrgLogoUrl, standardPolitogyNav, topBarIconButtonClass, useChromeMe, useChromeOrgs, useNavChromeFetcher, useThemePreference };
3687
- //# sourceMappingURL=chunk-WFUWFMCA.js.map
3688
- //# sourceMappingURL=chunk-WFUWFMCA.js.map
3726
+ //# sourceMappingURL=chunk-CZI66JVG.js.map
3727
+ //# sourceMappingURL=chunk-CZI66JVG.js.map