@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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## 0.70.0 — 2026-10-06
2
+
3
+ The top bar and left rail match Specs/Left-Navigation and Reference/UI-Platform.
4
+
5
+ ### Changed
6
+
7
+ - **No Build notes control.** The top bar has exactly the five UI-Platform elements: search, Dev notes, Credits, Notifications, Help. `<PolitogyAppFrame>` no longer passes `buildNotes`; `NavTopBar` renders Build notes only when a caller passes the prop. (#101)
8
+ - **The top bar fits a 390px phone.** Search shrinks below `md` (fixed 320px from `md` up), the `/` hint hides below `md`, and the header gap narrows. (#104)
9
+ - **No hamburger on phone.** The top bar draws no hamburger. Below `md` the collapsed rail's logo tile is the expand control ("Expand navigation"); the drawer's close button is "Collapse navigation". The drawer itself is unchanged (Left-Navigation §11.6). The expand choice is remembered per user (`politogy_rail` cookie on `.politogyvrm.com` plus localStorage, via `readRailPreference` / `writeRailPreference` in `nav-preference`) and read back on the next load below `md`; a nav-link click closes the overlay without changing it. New `NavSidebar` props `onOpen` and `onNavigate`. `NavTopBar` still renders a hamburger for direct callers that pass `onOpenNav`. (#106, #108, #110)
10
+ - **Lucide icons.** `standardNavIcons` maps every slot to Lucide (`react-icons/lu`); the mode switcher uses contact-round, megaphone and file-signature. New slots: `layoutDashboard`, `usersRound`, `mousePointerClick`, `clipboardList`, `circleUser`, `crosshair`, `landmark`, `chartNoAxes`, `doorOpen`, `signpost`. (#105, #106)
11
+ - **Nav trees follow Left-Navigation §9.** Relationship: Relationship (Dashboard, Contacts, Email broadcast, SMS/MMS blast) · Collect (Calls to action, Forms, Surveys) · Account. Polls and CTAs rows removed. Campaign: Race data (Battleground, District data) · Research (Polling) · GOTV (Field canvassing, Phone banking, A2P texting, Sign mapping) · Account. Dashboard, Voter Contact and Tribes rows removed; Battleground is the landing row. A section left empty by availability flags is dropped. The Petition tree is unchanged. (#106)
12
+
13
+ ### Fixed
14
+
15
+ - react-router 6.30.6 clears the publish audit gate (#102); lockfile regenerated with npm 10 so CI `npm ci` succeeds (#103).
16
+
1
17
  ## 0.69.0 — 2026-10-04
2
18
 
3
19
  The left rail collapses instead of hiding, `<main>` is fluid, and there is one page tab strip. Implements Specs/Left-Navigation §11.6 and §5, the Brand guidelines "Content width is fluid", "Content body padding", "Tabs are an underlined strip" and "Logo standard", and the two "Known shell defect" notes in Reference/UI-Platform (Brand v135).
@@ -7,6 +7,7 @@ var fi = require('react-icons/fi');
7
7
  var reactDom = require('react-dom');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
9
  var devnotes = require('@the-portland-company/devnotes');
10
+ var lu = require('react-icons/lu');
10
11
 
11
12
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
13
 
@@ -88,13 +89,111 @@ function normalize(s) {
88
89
  return s.normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().trim();
89
90
  }
90
91
 
92
+ // src/nav-preference.ts
93
+ var NAV_DISPLAY_STORAGE_KEY = "politogy_nav_display";
94
+ var NAV_DISPLAY_COOKIE = "politogy_nav_display";
95
+ var LEGACY_NAV_DISPLAY_KEY = "vrm.navDisplay";
96
+ var NAV_DISPLAY_DEFAULT = "both";
97
+ var VALID = ["menu", "tabs", "both"];
98
+ function isNavDisplayPreference(v) {
99
+ return typeof v === "string" && VALID.includes(v);
100
+ }
101
+ function readNavDisplayCookie() {
102
+ if (typeof document === "undefined") return null;
103
+ try {
104
+ const m = document.cookie.match(
105
+ new RegExp(`(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`)
106
+ );
107
+ const v = m ? decodeURIComponent(m[1]) : null;
108
+ return isNavDisplayPreference(v) ? v : null;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ function writeNavDisplayCookie(pref) {
114
+ if (typeof document === "undefined" || typeof window === "undefined") return;
115
+ const host = window.location.hostname;
116
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
117
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
118
+ document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(
119
+ pref
120
+ )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
121
+ }
122
+ function readNavDisplayLocal() {
123
+ if (typeof localStorage === "undefined") return null;
124
+ try {
125
+ const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY);
126
+ if (isNavDisplayPreference(current)) return current;
127
+ const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY);
128
+ return isNavDisplayPreference(legacy) ? legacy : null;
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ function writeNavDisplayLocal(pref) {
134
+ if (typeof localStorage === "undefined") return;
135
+ try {
136
+ localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref);
137
+ } catch {
138
+ }
139
+ }
140
+ function resolveNavDisplay() {
141
+ return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT;
142
+ }
143
+ `(function(){try{
144
+ var KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};
145
+ var VALID=["menu","tabs","both"];
146
+ function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
147
+ var cookie=fromCookie();
148
+ if(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
149
+ if(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}
150
+ }catch(e){}})();`;
151
+ var RAIL_STORAGE_KEY = "politogy_rail";
152
+ var RAIL_COOKIE = "politogy_rail";
153
+ var RAIL_DEFAULT = "collapsed";
154
+ function isRailPreference(v) {
155
+ return v === "expanded" || v === "collapsed";
156
+ }
157
+ function readRailPreference() {
158
+ if (typeof document !== "undefined") {
159
+ try {
160
+ const m = document.cookie.match(new RegExp(`(?:^|;\\s*)${RAIL_COOKIE}=([^;]+)`));
161
+ const v = m ? decodeURIComponent(m[1]) : null;
162
+ if (isRailPreference(v)) return v;
163
+ } catch {
164
+ }
165
+ }
166
+ if (typeof localStorage !== "undefined") {
167
+ try {
168
+ const v = localStorage.getItem(RAIL_STORAGE_KEY);
169
+ if (isRailPreference(v)) return v;
170
+ } catch {
171
+ }
172
+ }
173
+ return RAIL_DEFAULT;
174
+ }
175
+ function writeRailPreference(pref) {
176
+ if (typeof document === "undefined" || typeof window === "undefined") return;
177
+ const host = window.location.hostname;
178
+ const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
179
+ const secure = window.location.protocol === "https:" ? "; Secure" : "";
180
+ try {
181
+ document.cookie = `${RAIL_COOKIE}=${pref}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
182
+ } catch {
183
+ }
184
+ try {
185
+ localStorage.setItem(RAIL_STORAGE_KEY, pref);
186
+ } catch {
187
+ }
188
+ }
189
+
91
190
  // src/theme-preference.ts
92
191
  var THEME_STORAGE_KEY = "politogy_theme";
93
192
  var THEME_COOKIE = "politogy_theme";
94
193
  var THEME_PENDING_COOKIE = "politogy_theme_pending";
95
- var VALID = ["light", "dark", "system"];
194
+ var VALID2 = ["light", "dark", "system"];
96
195
  function isThemePreference(v) {
97
- return typeof v === "string" && VALID.includes(v);
196
+ return typeof v === "string" && VALID2.includes(v);
98
197
  }
99
198
  function readThemeCookie() {
100
199
  if (typeof document === "undefined") return null;
@@ -1847,7 +1946,6 @@ var PHONEBANK_ORIGIN = "https://phonebank.politogyvrm.com/";
1847
1946
  var CANVASS_ORIGIN = "https://canvass.politogyvrm.com/";
1848
1947
  var SIGNS_ORIGIN = "https://signs.politogyvrm.com/";
1849
1948
  var POLLS_ORIGIN = "https://polls.politogyvrm.com/";
1850
- var CONTACTS_TRIBES = "https://contacts.politogyvrm.com/tribes";
1851
1949
  var PETITION_MODE_ORIGIN = "https://petition-mode.politogyvrm.com/";
1852
1950
  function applyHiddenItems(items, hidden) {
1853
1951
  if (!hidden || hidden.length === 0) return items;
@@ -1917,10 +2015,6 @@ var APP_POLLS_CHILDREN = [
1917
2015
  { label: "Command Center", icon: "grid", href: "https://polls.politogyvrm.com/" },
1918
2016
  { label: "New Poll", icon: "edit", href: "https://polls.politogyvrm.com/polls/new" }
1919
2017
  ];
1920
- var APP_CTAS_CHILDREN = [
1921
- { label: "CTAs", icon: "list", href: "https://ctas.politogyvrm.com/" },
1922
- { label: "Canvas", icon: "edit", href: "https://ctas.politogyvrm.com/?tab=canvas" }
1923
- ];
1924
2018
  var APP_A2P_CHILDREN = [
1925
2019
  { label: "Dashboard", icon: "grid", path: "dashboard" },
1926
2020
  { label: "Compose", icon: "edit", path: "compose" },
@@ -1968,16 +2062,27 @@ function standardPolitogyNav(opts) {
1968
2062
  return { label: c.label, icon: icon(c.icon), href: withOrg(withMode) };
1969
2063
  });
1970
2064
  };
2065
+ const accountRows = () => [
2066
+ { kind: "section", label: "Account" },
2067
+ // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
2068
+ { label: "My profile", icon: icon("circleUser"), href: withOrg(abs("/my-profile")) },
2069
+ {
2070
+ label: "Organization settings",
2071
+ icon: icon("settings"),
2072
+ href: withOrg(abs("/organization-settings")),
2073
+ children: branches(ORG_SETTINGS_CHILDREN)
2074
+ }
2075
+ ];
1971
2076
  const buildRelationship = () => {
1972
2077
  const contacts = opts.contactsItem ? {
1973
2078
  label: "Contacts",
1974
- icon: icon("users"),
2079
+ icon: icon("usersRound"),
1975
2080
  href: opts.contactsItem.href,
1976
2081
  children: opts.contactsItem.children,
1977
2082
  badge: opts.contactsItem.count ?? void 0
1978
2083
  } : {
1979
2084
  label: "Contacts",
1980
- icon: icon("users"),
2085
+ icon: icon("usersRound"),
1981
2086
  href: withOrg(`${CONTACTS_APP}/`),
1982
2087
  children: APP_CONTACTS_CHILDREN.map((c) => ({
1983
2088
  label: c.label,
@@ -1987,7 +2092,7 @@ function standardPolitogyNav(opts) {
1987
2092
  };
1988
2093
  return [
1989
2094
  { kind: "section", label: "Relationship" },
1990
- { label: "Dashboard", icon: icon("home"), href: withOrg(abs("/relationship/dashboard")) },
2095
+ { label: "Dashboard", icon: icon("layoutDashboard"), href: withOrg(abs("/relationship/dashboard")) },
1991
2096
  contacts,
1992
2097
  {
1993
2098
  label: "Email broadcast",
@@ -2010,40 +2115,20 @@ function standardPolitogyNav(opts) {
2010
2115
  },
2011
2116
  { kind: "section", label: "Collect" },
2012
2117
  // Carries no top-level href in VRM core (parent row only toggles).
2013
- { label: "Calls to action", icon: icon("layout"), children: leaves(CTA_CHILDREN) },
2118
+ { label: "Calls to action", icon: icon("mousePointerClick"), children: leaves(CTA_CHILDREN) },
2014
2119
  {
2015
2120
  label: "Forms",
2016
- icon: icon("activity"),
2121
+ icon: icon("fileText"),
2017
2122
  href: withOrg("https://forms.politogyvrm.com/"),
2018
2123
  children: APP_FORMS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2019
2124
  },
2020
2125
  {
2021
2126
  label: "Surveys",
2022
- icon: icon("fileText"),
2127
+ icon: icon("clipboardList"),
2023
2128
  href: withOrg("https://surveys.politogyvrm.com/surveys"),
2024
2129
  children: APP_SURVEYS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2025
2130
  },
2026
- {
2027
- label: "Polls",
2028
- icon: icon("barChart"),
2029
- href: withOrg("https://polls.politogyvrm.com/"),
2030
- children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2031
- },
2032
- {
2033
- label: "CTAs",
2034
- icon: icon("layout"),
2035
- href: withOrg("https://ctas.politogyvrm.com/"),
2036
- children: APP_CTAS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) }))
2037
- },
2038
- { kind: "section", label: "Account" },
2039
- // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).
2040
- { label: "My profile", icon: icon("user"), href: withOrg(abs("/my-profile")) },
2041
- {
2042
- label: "Organization settings",
2043
- icon: icon("settings"),
2044
- href: withOrg(abs("/organization-settings")),
2045
- children: branches(ORG_SETTINGS_CHILDREN)
2046
- }
2131
+ ...accountRows()
2047
2132
  ];
2048
2133
  };
2049
2134
  const buildCampaign = () => {
@@ -2052,40 +2137,57 @@ function standardPolitogyNav(opts) {
2052
2137
  ...opts.campaignAvailability
2053
2138
  };
2054
2139
  const rows = [
2055
- { label: "Dashboard", icon: icon("home"), href: withOrg(abs("/campaign/dashboard")) },
2056
- { label: "Voter Contact", icon: icon("target"), href: withOrg(abs("/campaign/campaigns")) },
2140
+ // Left-Navigation §9.2: Race data -> Research -> GOTV -> Account.
2141
+ // Battleground is the Campaign landing page; there is no Campaign
2142
+ // Dashboard row, and Voter Contact and Tribes have no row.
2143
+ { kind: "section", label: "Race data" },
2144
+ { label: "Battleground", icon: icon("crosshair"), href: withOrg(abs("/campaign/battleground")) },
2145
+ { label: "District data", icon: icon("landmark"), href: withOrg(abs("/campaign/districts")) },
2146
+ { kind: "section", label: "Research" },
2057
2147
  {
2058
- label: "A2P",
2059
- icon: icon("messageSquare"),
2060
- href: withOrg(A2P_ORIGIN),
2061
- children: appLeaves(A2P_ORIGIN, APP_A2P_CHILDREN),
2062
- flag: "a2p"
2148
+ label: "Polling",
2149
+ icon: icon("chartNoAxes"),
2150
+ href: withOrg(POLLS_ORIGIN),
2151
+ children: APP_POLLS_CHILDREN.map((c) => ({ label: c.label, icon: icon(c.icon), href: withOrg(c.href) })),
2152
+ flag: "polling"
2153
+ },
2154
+ { kind: "section", label: "GOTV" },
2155
+ {
2156
+ label: "Field canvassing",
2157
+ icon: icon("doorOpen"),
2158
+ href: withOrg(CANVASS_ORIGIN),
2159
+ children: appLeaves(CANVASS_ORIGIN, APP_CANVASS_CHILDREN),
2160
+ flag: "canvass"
2063
2161
  },
2064
2162
  {
2065
- label: "Phone Banking",
2163
+ label: "Phone banking",
2066
2164
  icon: icon("phone"),
2067
2165
  href: withOrg(PHONEBANK_ORIGIN),
2068
2166
  children: appLeaves(PHONEBANK_ORIGIN, APP_PHONEBANK_CHILDREN),
2069
2167
  flag: "phonebank"
2070
2168
  },
2071
2169
  {
2072
- label: "Field Canvassing",
2073
- icon: icon("map"),
2074
- href: withOrg(CANVASS_ORIGIN),
2075
- children: appLeaves(CANVASS_ORIGIN, APP_CANVASS_CHILDREN),
2076
- flag: "canvass"
2170
+ label: "A2P texting",
2171
+ icon: icon("messageSquare"),
2172
+ href: withOrg(A2P_ORIGIN),
2173
+ children: appLeaves(A2P_ORIGIN, APP_A2P_CHILDREN),
2174
+ flag: "a2p"
2077
2175
  },
2078
2176
  {
2079
- label: "Sign Mapping",
2080
- icon: icon("mapPin"),
2177
+ label: "Sign mapping",
2178
+ icon: icon("signpost"),
2081
2179
  href: withOrg(SIGNS_ORIGIN),
2082
2180
  children: appLeaves(SIGNS_ORIGIN, APP_SIGNS_CHILDREN),
2083
2181
  flag: "signs"
2084
2182
  },
2085
- { label: "Tribes", icon: icon("users"), href: withOrg(CONTACTS_TRIBES), flag: "tribes" },
2086
- { label: "Polling Engine", icon: icon("barChart"), href: withOrg(POLLS_ORIGIN), flag: "polling" }
2183
+ ...accountRows()
2087
2184
  ];
2088
- return rows.filter((r) => !r.flag || avail[r.flag]).map(({ flag: _flag, ...item }) => item);
2185
+ const kept = rows.filter((r) => !r.flag || avail[r.flag]).map(({ flag: _flag, ...item }) => item);
2186
+ return kept.filter((item, idx) => {
2187
+ if (item.kind !== "section") return true;
2188
+ const next = kept[idx + 1];
2189
+ return !!next && next.kind !== "section";
2190
+ });
2089
2191
  };
2090
2192
  const buildPetition = () => [
2091
2193
  opts.petitionsItem ? {
@@ -2192,7 +2294,9 @@ function NavSidebar({
2192
2294
  footer,
2193
2295
  linkComponent,
2194
2296
  open,
2195
- onClose
2297
+ onClose,
2298
+ onOpen,
2299
+ onNavigate
2196
2300
  }) {
2197
2301
  const Link = linkComponent ?? DefaultLink;
2198
2302
  const collapsible = !open;
@@ -2289,7 +2393,7 @@ function NavSidebar({
2289
2393
  style: { fontFamily: 'var(--font-family, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif)' },
2290
2394
  onKeyDown: trapTab,
2291
2395
  onClick: (e) => {
2292
- if (open && e.target.closest("a[href]")) onClose?.();
2396
+ if (open && e.target.closest("a[href]")) (onNavigate ?? onClose)?.();
2293
2397
  },
2294
2398
  children: [
2295
2399
  open ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -2298,18 +2402,30 @@ function NavSidebar({
2298
2402
  type: "button",
2299
2403
  ref: closeButtonRef,
2300
2404
  onClick: onClose,
2301
- "aria-label": "Close menu",
2405
+ "aria-label": "Collapse navigation",
2302
2406
  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",
2303
2407
  children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
2304
2408
  }
2305
2409
  ) : null,
2306
2410
  /* @__PURE__ */ jsxRuntime.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: [
2411
+ onOpen ? /* @__PURE__ */ jsxRuntime.jsx(
2412
+ "button",
2413
+ {
2414
+ type: "button",
2415
+ onClick: onOpen,
2416
+ "aria-label": "Expand navigation",
2417
+ "aria-expanded": open ?? false,
2418
+ "data-rail-toggle": "expand",
2419
+ className: `hidden h-8 w-8 items-center justify-center rounded-lg ${rc("max-md:flex")}`,
2420
+ children: brand.logoSquareSrc ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: brand.logoSquareSrc, alt: "", draggable: false, className: "h-8 w-8 object-contain" }) : brand.logoPending ? /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, className: "block h-8 w-8" }) : /* @__PURE__ */ jsxRuntime.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 })
2421
+ }
2422
+ ) : null,
2307
2423
  /* @__PURE__ */ jsxRuntime.jsxs(
2308
2424
  Link,
2309
2425
  {
2310
2426
  href: brand.homeHref ?? "/",
2311
2427
  "aria-label": brand.logoAlt ?? brand.title,
2312
- className: `flex items-center gap-2.5 ${rc("max-md:justify-center")}`,
2428
+ className: `flex items-center gap-2.5 ${rc("max-md:justify-center")} ${onOpen ? rc("max-md:hidden") : ""}`,
2313
2429
  children: [
2314
2430
  brand.logoSquareSrc ? /* @__PURE__ */ jsxRuntime.jsx(
2315
2431
  "img",
@@ -3083,7 +3199,7 @@ function NavTopBar({
3083
3199
  const initials = (hasRealName ? rawName : user?.email ?? "P").slice(0, 2).toUpperCase();
3084
3200
  const secondary = hasRealName ? user?.email || [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member" : [user?.tier, user?.role].filter(Boolean).join(" \xB7 ") || "member";
3085
3201
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "sticky top-0 z-20", children: [
3086
- /* @__PURE__ */ jsxRuntime.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: [
3202
+ /* @__PURE__ */ jsxRuntime.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: [
3087
3203
  onOpenNav ? /* @__PURE__ */ jsxRuntime.jsx(
3088
3204
  "button",
3089
3205
  {
@@ -3096,7 +3212,7 @@ function NavTopBar({
3096
3212
  children: /* @__PURE__ */ jsxRuntime.jsx(HamburgerIcon, {})
3097
3213
  }
3098
3214
  ) : null,
3099
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "relative flex h-9 w-[320px] max-w-full shrink-0 items-center", children: [
3215
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "relative flex h-9 w-[320px] min-w-0 max-w-full shrink items-center md:shrink-0", children: [
3100
3216
  /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, { className: "pointer-events-none absolute left-[10px] h-[15px] w-[15px] text-[color:var(--muted)]" }),
3101
3217
  /* @__PURE__ */ jsxRuntime.jsx(
3102
3218
  "input",
@@ -3116,13 +3232,13 @@ function NavTopBar({
3116
3232
  "kbd",
3117
3233
  {
3118
3234
  "aria-hidden": true,
3119
- 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)]",
3235
+ 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)]",
3120
3236
  children: "/"
3121
3237
  }
3122
3238
  )
3123
3239
  ] }),
3124
3240
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative ml-auto flex shrink-0 items-center gap-[var(--topbar-icon-gap,8px)]", children: [
3125
- /* @__PURE__ */ jsxRuntime.jsxs(
3241
+ buildNotes ? /* @__PURE__ */ jsxRuntime.jsxs(
3126
3242
  "button",
3127
3243
  {
3128
3244
  type: "button",
@@ -3133,7 +3249,7 @@ function NavTopBar({
3133
3249
  buildNotes?.label ?? "Build notes"
3134
3250
  ]
3135
3251
  }
3136
- ),
3252
+ ) : null,
3137
3253
  actionsList && actionsList.length > 0 ? actionsList.map((action) => /* @__PURE__ */ jsxRuntime.jsx(NavTopBarActionButton, { action }, action.key)) : null,
3138
3254
  actions,
3139
3255
  themeToggle,
@@ -3624,9 +3740,22 @@ function NavAppShell({
3624
3740
  );
3625
3741
  }
3626
3742
  const [navOpen, setNavOpen] = react.useState(false);
3743
+ const expandRail = () => {
3744
+ writeRailPreference("expanded");
3745
+ setNavOpen(true);
3746
+ };
3747
+ const collapseRail = () => {
3748
+ writeRailPreference("collapsed");
3749
+ setNavOpen(false);
3750
+ };
3627
3751
  react.useEffect(() => {
3628
3752
  setNavOpen(false);
3629
3753
  }, [sidebar.pathname]);
3754
+ react.useEffect(() => {
3755
+ if (readRailPreference() !== "expanded") return;
3756
+ const narrow = typeof window !== "undefined" && typeof window.matchMedia === "function" ? window.matchMedia("(max-width: 767.98px)").matches : false;
3757
+ if (narrow) setNavOpen(true);
3758
+ }, []);
3630
3759
  react.useEffect(() => {
3631
3760
  if (!navOpen) return;
3632
3761
  const previous = document.body.style.overflow;
@@ -3647,15 +3776,23 @@ function NavAppShell({
3647
3776
  "data-politogy-mode": modeId,
3648
3777
  className: "min-h-screen bg-[color:var(--background)] text-[color:var(--foreground)]",
3649
3778
  children: [
3650
- /* @__PURE__ */ jsxRuntime.jsx(NavSidebar, { ...sidebar, footer: sidebarFooter, open: navOpen, onClose: () => setNavOpen(false) }),
3779
+ /* @__PURE__ */ jsxRuntime.jsx(
3780
+ NavSidebar,
3781
+ {
3782
+ ...sidebar,
3783
+ footer: sidebarFooter,
3784
+ open: navOpen,
3785
+ onClose: collapseRail,
3786
+ onOpen: expandRail,
3787
+ onNavigate: () => setNavOpen(false)
3788
+ }
3789
+ ),
3651
3790
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pl-[var(--sidebar-collapsed-width,64px)] md:pl-[var(--sidebar-width,236px)]", children: [
3652
3791
  /* @__PURE__ */ jsxRuntime.jsx(
3653
3792
  NavTopBar,
3654
3793
  {
3655
3794
  ...topBar,
3656
- accountMenu: topBar.accountMenu ?? (accountMenuPlacement === "sidebar" ? "sidebar" : "topbar"),
3657
- onOpenNav: () => setNavOpen(true),
3658
- navOpen
3795
+ accountMenu: topBar.accountMenu ?? (accountMenuPlacement === "sidebar" ? "sidebar" : "topbar")
3659
3796
  }
3660
3797
  ),
3661
3798
  betaBanner ? /* @__PURE__ */ jsxRuntime.jsx(NavBetaBanner, { ...betaBanner }) : null,
@@ -3730,66 +3867,6 @@ function resolveActiveOrg(search) {
3730
3867
  return readActiveOrgCookie();
3731
3868
  }
3732
3869
 
3733
- // src/nav-preference.ts
3734
- var NAV_DISPLAY_STORAGE_KEY = "politogy_nav_display";
3735
- var NAV_DISPLAY_COOKIE = "politogy_nav_display";
3736
- var LEGACY_NAV_DISPLAY_KEY = "vrm.navDisplay";
3737
- var NAV_DISPLAY_DEFAULT = "both";
3738
- var VALID2 = ["menu", "tabs", "both"];
3739
- function isNavDisplayPreference(v) {
3740
- return typeof v === "string" && VALID2.includes(v);
3741
- }
3742
- function readNavDisplayCookie() {
3743
- if (typeof document === "undefined") return null;
3744
- try {
3745
- const m = document.cookie.match(
3746
- new RegExp(`(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)`)
3747
- );
3748
- const v = m ? decodeURIComponent(m[1]) : null;
3749
- return isNavDisplayPreference(v) ? v : null;
3750
- } catch {
3751
- return null;
3752
- }
3753
- }
3754
- function writeNavDisplayCookie(pref) {
3755
- if (typeof document === "undefined" || typeof window === "undefined") return;
3756
- const host = window.location.hostname;
3757
- const domainAttr = /(^|\.)politogyvrm\.com$/i.test(host) ? "; Domain=.politogyvrm.com" : "";
3758
- const secure = window.location.protocol === "https:" ? "; Secure" : "";
3759
- document.cookie = `${NAV_DISPLAY_COOKIE}=${encodeURIComponent(
3760
- pref
3761
- )}; Path=/; Max-Age=31536000; SameSite=Lax${domainAttr}${secure}`;
3762
- }
3763
- function readNavDisplayLocal() {
3764
- if (typeof localStorage === "undefined") return null;
3765
- try {
3766
- const current = localStorage.getItem(NAV_DISPLAY_STORAGE_KEY);
3767
- if (isNavDisplayPreference(current)) return current;
3768
- const legacy = localStorage.getItem(LEGACY_NAV_DISPLAY_KEY);
3769
- return isNavDisplayPreference(legacy) ? legacy : null;
3770
- } catch {
3771
- return null;
3772
- }
3773
- }
3774
- function writeNavDisplayLocal(pref) {
3775
- if (typeof localStorage === "undefined") return;
3776
- try {
3777
- localStorage.setItem(NAV_DISPLAY_STORAGE_KEY, pref);
3778
- } catch {
3779
- }
3780
- }
3781
- function resolveNavDisplay() {
3782
- return readNavDisplayCookie() ?? readNavDisplayLocal() ?? NAV_DISPLAY_DEFAULT;
3783
- }
3784
- `(function(){try{
3785
- var KEY=${JSON.stringify(NAV_DISPLAY_STORAGE_KEY)};
3786
- var VALID=["menu","tabs","both"];
3787
- function fromCookie(){try{var m=document.cookie.match(/(?:^|;\\s*)${NAV_DISPLAY_COOKIE}=([^;]+)/);return m?decodeURIComponent(m[1]):null;}catch(e){return null;}}
3788
- var cookie=fromCookie();
3789
- if(cookie&&VALID.indexOf(cookie)!==-1){var ls=null;try{ls=localStorage.getItem(KEY);}catch(e){}
3790
- if(cookie!==ls){try{localStorage.setItem(KEY,cookie);}catch(e){}}}
3791
- }catch(e){}})();`;
3792
-
3793
3870
  // src/lib/breadcrumbs.ts
3794
3871
  function deriveBreadcrumbs(items, pathname, opts) {
3795
3872
  const search = opts?.search ?? (typeof window !== "undefined" ? window.location.search : "");
@@ -4698,10 +4775,7 @@ function FrameBody({
4698
4775
  // search yet, so this stays decorative (`NavTopBarProps.search` — "an
4699
4776
  // app that has not wired search yet still gets the correct chrome").
4700
4777
  search: { placeholder: data?.searchPlaceholder ?? DEFAULT_SEARCH_PLACEHOLDER[app] ?? "Search" },
4701
- // "Build notes" — a quiet text button that starts the right-hand
4702
- // group. The spec names the element and its position but not its
4703
- // destination content; `onOpen` is left unset until that is defined.
4704
- buildNotes: {},
4778
+ // No Build notes control (Reference/UI-Platform, decided 2026-10-03).
4705
4779
  // The action cluster is IDENTICAL everywhere — core included. Page
4706
4780
  // actions sit BEFORE it, never inside it, so the Alerts bell keeps its
4707
4781
  // required position: farthest right, immediately before My Account.
@@ -4716,33 +4790,43 @@ function FrameBody({
4716
4790
  ] });
4717
4791
  }
4718
4792
  var standardNavIcons = {
4719
- home: fi.FiHome,
4720
- users: fi.FiUsers,
4721
- mail: fi.FiMail,
4722
- messageSquare: fi.FiMessageSquare,
4723
- layout: fi.FiLayout,
4724
- activity: fi.FiActivity,
4725
- lifeBuoy: fi.FiLifeBuoy,
4726
- grid: fi.FiGrid,
4727
- list: fi.FiList,
4728
- upload: fi.FiUpload,
4729
- edit: fi.FiEdit3,
4730
- fileText: fi.FiFileText,
4731
- send: fi.FiSend,
4732
- clock: fi.FiClock,
4733
- user: fi.FiUser,
4734
- barChart: fi.FiBarChart2,
4735
- briefcase: fi.FiBriefcase,
4736
- settings: fi.FiSettings,
4737
- creditCard: fi.FiCreditCard,
4738
- dollarSign: fi.FiDollarSign,
4739
- share: fi.FiShare2,
4793
+ home: lu.LuHouse,
4794
+ users: lu.LuUsers,
4795
+ mail: lu.LuMail,
4796
+ messageSquare: lu.LuMessageSquare,
4797
+ layout: lu.LuLayoutDashboard,
4798
+ activity: lu.LuActivity,
4799
+ lifeBuoy: lu.LuLifeBuoy,
4800
+ grid: lu.LuLayoutGrid,
4801
+ list: lu.LuList,
4802
+ upload: lu.LuUpload,
4803
+ edit: lu.LuPencil,
4804
+ fileText: lu.LuFileText,
4805
+ send: lu.LuSend,
4806
+ clock: lu.LuClock,
4807
+ user: lu.LuUser,
4808
+ barChart: lu.LuChartColumn,
4809
+ briefcase: lu.LuBriefcase,
4810
+ settings: lu.LuSettings,
4811
+ creditCard: lu.LuCreditCard,
4812
+ dollarSign: lu.LuDollarSign,
4813
+ share: lu.LuShare2,
4740
4814
  // Campaign / Petition mode slots.
4741
- phone: fi.FiPhone,
4742
- map: fi.FiMap,
4743
- mapPin: fi.FiMapPin,
4744
- target: fi.FiTarget,
4745
- flag: fi.FiFlag
4815
+ phone: lu.LuPhone,
4816
+ map: lu.LuMap,
4817
+ mapPin: lu.LuMapPin,
4818
+ target: lu.LuTarget,
4819
+ flag: lu.LuFlag,
4820
+ layoutDashboard: lu.LuLayoutDashboard,
4821
+ usersRound: lu.LuUsersRound,
4822
+ mousePointerClick: lu.LuMousePointerClick,
4823
+ clipboardList: lu.LuClipboardList,
4824
+ circleUser: lu.LuCircleUser,
4825
+ crosshair: lu.LuCrosshair,
4826
+ landmark: lu.LuLandmark,
4827
+ chartNoAxes: lu.LuChartNoAxesColumn,
4828
+ doorOpen: lu.LuDoorOpen,
4829
+ signpost: lu.LuSignpost
4746
4830
  };
4747
4831
  var ORG_STORAGE_KEY = "vrm.activeOrg";
4748
4832
  var MODE_STORAGE_KEY = "vrm.activeMode";
@@ -5169,14 +5253,14 @@ var MODE_DEFS = [
5169
5253
  {
5170
5254
  id: "relationship",
5171
5255
  label: "Relationship Mode",
5172
- icon: fi.FiUsers,
5256
+ icon: lu.LuContactRound,
5173
5257
  href: `${CORE_BASE_URL}/relationship/dashboard`,
5174
5258
  orgQuery: true
5175
5259
  },
5176
5260
  {
5177
5261
  id: "campaign",
5178
5262
  label: "Campaign Mode",
5179
- icon: fi.FiBarChart2,
5263
+ icon: lu.LuMegaphone,
5180
5264
  href: `${CORE_BASE_URL}/campaign/dashboard`,
5181
5265
  orgQuery: true
5182
5266
  },
@@ -5185,7 +5269,7 @@ var MODE_DEFS = [
5185
5269
  // petitions repo deploys production to petition-mode.politogyvrm.com.
5186
5270
  id: "petition",
5187
5271
  label: "Petition Mode",
5188
- icon: fi.FiFlag,
5272
+ icon: lu.LuFilePenLine,
5189
5273
  href: "https://petition-mode.politogyvrm.com/",
5190
5274
  orgQuery: true
5191
5275
  }