@the-portland-company/shell 0.36.2 → 0.36.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-chrome.cjs +10 -8
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +8 -1
- package/dist/app-chrome.d.ts +8 -1
- package/dist/app-chrome.js +10 -8
- package/dist/app-chrome.js.map +1 -1
- package/package.json +1 -1
package/dist/app-chrome.d.cts
CHANGED
|
@@ -134,7 +134,7 @@ type PolitogyBuildMeta = {
|
|
|
134
134
|
commitDate?: string;
|
|
135
135
|
commitAuthor?: string;
|
|
136
136
|
};
|
|
137
|
-
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, buildMeta, data, children, }: {
|
|
137
|
+
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, children, }: {
|
|
138
138
|
app: PolitogyAppKey;
|
|
139
139
|
/** Current path from the app's router (Next: usePathname()). */
|
|
140
140
|
pathname: string;
|
|
@@ -144,6 +144,13 @@ declare function PolitogyAppFrame({ app, pathname, search, linkComponent, sessio
|
|
|
144
144
|
linkComponent: NavLinkComponent;
|
|
145
145
|
session: PolitogyAppSession;
|
|
146
146
|
onSignOut: () => void;
|
|
147
|
+
/**
|
|
148
|
+
* The app's sign-out ROUTE — a GET endpoint that clears the session cookie
|
|
149
|
+
* and redirects to RP-initiated logout at the IdP. Defaults to the Template A
|
|
150
|
+
* convention; core overrides it. This, not `onSignOut`, is what actually
|
|
151
|
+
* signs the user out (see handleSignOut).
|
|
152
|
+
*/
|
|
153
|
+
signOutUrl?: string;
|
|
147
154
|
buildMeta?: PolitogyBuildMeta;
|
|
148
155
|
/** Optional DATA plumbing (never styling) — see PolitogyFrameData. */
|
|
149
156
|
data?: PolitogyFrameData;
|
package/dist/app-chrome.d.ts
CHANGED
|
@@ -134,7 +134,7 @@ type PolitogyBuildMeta = {
|
|
|
134
134
|
commitDate?: string;
|
|
135
135
|
commitAuthor?: string;
|
|
136
136
|
};
|
|
137
|
-
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, buildMeta, data, children, }: {
|
|
137
|
+
declare function PolitogyAppFrame({ app, pathname, search, linkComponent, session, onSignOut, signOutUrl, buildMeta, data, children, }: {
|
|
138
138
|
app: PolitogyAppKey;
|
|
139
139
|
/** Current path from the app's router (Next: usePathname()). */
|
|
140
140
|
pathname: string;
|
|
@@ -144,6 +144,13 @@ declare function PolitogyAppFrame({ app, pathname, search, linkComponent, sessio
|
|
|
144
144
|
linkComponent: NavLinkComponent;
|
|
145
145
|
session: PolitogyAppSession;
|
|
146
146
|
onSignOut: () => void;
|
|
147
|
+
/**
|
|
148
|
+
* The app's sign-out ROUTE — a GET endpoint that clears the session cookie
|
|
149
|
+
* and redirects to RP-initiated logout at the IdP. Defaults to the Template A
|
|
150
|
+
* convention; core overrides it. This, not `onSignOut`, is what actually
|
|
151
|
+
* signs the user out (see handleSignOut).
|
|
152
|
+
*/
|
|
153
|
+
signOutUrl?: string;
|
|
147
154
|
buildMeta?: PolitogyBuildMeta;
|
|
148
155
|
/** Optional DATA plumbing (never styling) — see PolitogyFrameData. */
|
|
149
156
|
data?: PolitogyFrameData;
|
package/dist/app-chrome.js
CHANGED
|
@@ -78,6 +78,7 @@ function resolveIdentity(input) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
var CORE_APP = "vrm";
|
|
81
|
+
var DEFAULT_SIGN_OUT_URL = "/auth/signout";
|
|
81
82
|
var POLITOGY_APPS = {
|
|
82
83
|
vrm: { title: "Politogy VRM", origin: "https://app.politogyvrm.com", storagePrefix: "vrm" },
|
|
83
84
|
contacts: { title: "Contacts", origin: "https://contacts.politogyvrm.com", storagePrefix: "contacts" },
|
|
@@ -105,6 +106,7 @@ function PolitogyAppFrame({
|
|
|
105
106
|
linkComponent,
|
|
106
107
|
session,
|
|
107
108
|
onSignOut,
|
|
109
|
+
signOutUrl = DEFAULT_SIGN_OUT_URL,
|
|
108
110
|
buildMeta,
|
|
109
111
|
data,
|
|
110
112
|
children
|
|
@@ -132,6 +134,7 @@ function PolitogyAppFrame({
|
|
|
132
134
|
linkComponent,
|
|
133
135
|
session,
|
|
134
136
|
onSignOut,
|
|
137
|
+
signOutUrl,
|
|
135
138
|
buildMeta,
|
|
136
139
|
data,
|
|
137
140
|
children
|
|
@@ -147,6 +150,7 @@ function FrameBody({
|
|
|
147
150
|
linkComponent,
|
|
148
151
|
session,
|
|
149
152
|
onSignOut,
|
|
153
|
+
signOutUrl,
|
|
150
154
|
buildMeta,
|
|
151
155
|
data,
|
|
152
156
|
children
|
|
@@ -160,14 +164,12 @@ function FrameBody({
|
|
|
160
164
|
signingOutRef.current = true;
|
|
161
165
|
broadcastLogout();
|
|
162
166
|
signOutRef.current();
|
|
163
|
-
window.
|
|
164
|
-
|
|
165
|
-
}, 2e3);
|
|
166
|
-
}, []);
|
|
167
|
+
window.location.assign(signOutUrl);
|
|
168
|
+
}, [signOutUrl]);
|
|
167
169
|
useEffect(
|
|
168
170
|
() => (
|
|
169
|
-
// A tab that learns of a sign-out elsewhere
|
|
170
|
-
//
|
|
171
|
+
// A tab that learns of a sign-out elsewhere ends its own session for
|
|
172
|
+
// real, by navigating to the same sign-out route.
|
|
171
173
|
//
|
|
172
174
|
// Measured against production before this landed: calling the app's
|
|
173
175
|
// handler did nothing at all. Apps wire it as
|
|
@@ -184,10 +186,10 @@ function FrameBody({
|
|
|
184
186
|
// already on record for every tab.
|
|
185
187
|
watchLogout(() => {
|
|
186
188
|
if (signingOutRef.current) return;
|
|
187
|
-
window.location.
|
|
189
|
+
window.location.assign(signOutUrl);
|
|
188
190
|
})
|
|
189
191
|
),
|
|
190
|
-
[]
|
|
192
|
+
[signOutUrl]
|
|
191
193
|
);
|
|
192
194
|
const core = app === CORE_APP ? data?.core : void 0;
|
|
193
195
|
const identity = useChromeIdentity({ userName: session.userName, email: session.email });
|
package/dist/app-chrome.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/breadcrumbs.ts","../src/lib/identity.ts","../src/app-chrome/frame.tsx","../src/app-chrome/index.tsx"],"names":["trail","useEffect","useCallback","useMemo","jsx","jsxs","Fragment"],"mappings":";;;;;;;;;;AAUO,SAAS,iBAAA,CACd,KAAA,EACA,QAAA,EACA,IAAA,EACiB;AACjB,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,KAAW,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS,EAAA,CAAA;AACzF,EAAA,MAAM,MAAA,GAAS,MAAM,MAAA,KAAW,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS,EAAA,CAAA;AAGzF,EAAA,MAAM,OAAA,GAAU,CAAC,IAAA,KAA2B;AAC1C,IAAA,IAAI,CAAC,MAAM,OAAO,KAAA;AAClB,IAAA,IAAI,IAAA,GAAO,IAAA;AACX,IAAA,IAAI,KAAA,GAAQ,EAAA;AACZ,IAAA,IAAI,eAAA,CAAgB,IAAA,CAAK,IAAI,CAAA,EAAG;AAC9B,MAAA,IAAI;AACF,QAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,IAAI,CAAA;AACtB,QAAA,IAAI,MAAA,IAAU,CAAA,CAAE,MAAA,KAAW,MAAA,EAAQ,OAAO,KAAA;AAC1C,QAAA,IAAA,GAAO,CAAA,CAAE,QAAA;AACT,QAAA,KAAA,GAAQ,CAAA,CAAE,MAAA;AAAA,MACZ,CAAA,CAAA,MAAQ;AACN,QAAA,OAAO,KAAA;AAAA,MACT;AAAA,IACF,CAAA,MAAO;AACL,MAAA,MAAM,EAAA,GAAK,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAC3B,MAAA,IAAI,MAAM,CAAA,EAAG;AACX,QAAA,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA,IAAK,GAAA;AAC5B,QAAA,KAAA,GAAQ,IAAA,CAAK,MAAM,EAAE,CAAA;AAAA,MACvB;AAAA,IACF;AACA,IAAA,IAAI,IAAA,KAAS,UAAU,OAAO,KAAA;AAC9B,IAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,KAAK,CAAA;AACtC,IAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,MAAM,CAAA;AACvC,IAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,CAAA,IAAK,IAAA,EAAM,IAAI,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,EAAG,OAAO,KAAA;AACzD,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAGA,EAAA,MAAM,IAAA,GAAO,CAAC,KAAA,EAAkBA,MAAAA,KAAuC;AACrE,IAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,MAAA,MAAM,IAAA,GAAO,CAAC,GAAGA,MAAAA,EAAO,IAAI,CAAA;AAC5B,MAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAQ;AACzB,QAAA,MAAM,MAAA,GAAS,IAAA,CAAK,IAAA,CAAK,QAAA,EAAU,IAAI,CAAA;AACvC,QAAA,IAAI,QAAQ,OAAO,MAAA;AAAA,MACrB;AACA,MAAA,IAAI,OAAA,CAAQ,IAAA,CAAK,IAAI,CAAA,EAAG,OAAO,IAAA;AAAA,IACjC;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,EAAO,EAAE,CAAA;AAG5B,EAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,WAAW,CAAA;AAC3D,EAAA,MAAM,MAAA,GAA0B,CAAC,EAAE,KAAA,EAAO,aAAa,IAAA,EAAM,SAAA,EAAW,MAAM,CAAA;AAC9E,EAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AACnB,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,IAAI,IAAA,CAAK,UAAU,WAAA,EAAa;AAChC,IAAA,MAAA,CAAO,IAAA,CAAK,EAAE,KAAA,EAAO,IAAA,CAAK,OAAO,IAAA,EAAM,IAAA,CAAK,MAAM,CAAA;AAAA,EACpD;AACA,EAAA,MAAM,IAAA,GAAO,MAAA,CAAO,MAAA,CAAO,MAAA,GAAS,CAAC,CAAA;AACrC,EAAA,IAAI,IAAA,SAAc,IAAA,CAA2B,IAAA;AAC7C,EAAA,OAAO,MAAA;AACT;;;ACzDO,SAAS,eAAe,KAAA,EAA2C;AACxE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,6BAA6B,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA;AACpF;AAEO,SAAS,gBAAgB,KAAA,EAMX;AACnB,EAAA,MAAM,UAAU,cAAA,CAAe,KAAA,CAAM,OAAO,CAAA,GAAI,IAAA,GAAO,MAAM,OAAA,IAAW,IAAA;AACxE,EAAA,MAAM,eAAe,cAAA,CAAe,KAAA,CAAM,YAAY,CAAA,GAAI,IAAA,GAAO,MAAM,YAAA,IAAgB,IAAA;AACvF,EAAA,OAAO;AAAA,IACL,UAAU,OAAA,IAAW,YAAA;AAAA,IACrB,KAAA,EAAO,MAAM,KAAA,IAAS,IAAA;AAAA,IACtB,SAAA,EAAW,MAAM,SAAA,IAAa,IAAA;AAAA,IAC9B,MAAA,EAAQ,MAAM,MAAA,IAAU;AAAA,GAC1B;AACF;AC4BA,IAAM,QAAA,GAA2B,KAAA;AA+B1B,IAAM,aAAA,GAGT;AAAA,EACF,KAAK,EAAE,KAAA,EAAO,gBAAgB,MAAA,EAAQ,6BAAA,EAA+B,eAAe,KAAA,EAAM;AAAA,EAC1F,UAAU,EAAE,KAAA,EAAO,YAAY,MAAA,EAAQ,kCAAA,EAAoC,eAAe,UAAA,EAAW;AAAA,EACrG,OAAO,EAAE,KAAA,EAAO,SAAS,MAAA,EAAQ,+BAAA,EAAiC,eAAe,OAAA,EAAQ;AAAA,EACzF,SAAS,EAAE,KAAA,EAAO,WAAW,MAAA,EAAQ,iCAAA,EAAmC,eAAe,SAAA,EAAU;AAAA,EACjG,OAAO,EAAE,KAAA,EAAO,SAAS,MAAA,EAAQ,+BAAA,EAAiC,eAAe,OAAA,EAAQ;AAAA,EACzF,SAAA,EAAW;AAAA,IACT,KAAA,EAAO,WAAA;AAAA,IACP,MAAA,EAAQ,uCAAA;AAAA,IACR,aAAA,EAAe,WAAA;AAAA,IACf,IAAA,EAAM;AAAA,GACR;AAAA;AAAA;AAAA;AAAA,EAIA,GAAA,EAAK,EAAE,KAAA,EAAO,eAAA,EAAiB,QAAQ,6BAAA,EAA+B,aAAA,EAAe,KAAA,EAAO,IAAA,EAAM,UAAA,EAAW;AAAA,EAC7G,SAAA,EAAW,EAAE,KAAA,EAAO,eAAA,EAAiB,QAAQ,mCAAA,EAAqC,aAAA,EAAe,WAAA,EAAa,IAAA,EAAM,UAAA,EAAW;AAAA,EAC/H,OAAA,EAAS,EAAE,KAAA,EAAO,kBAAA,EAAoB,QAAQ,iCAAA,EAAmC,aAAA,EAAe,SAAA,EAAW,IAAA,EAAM,UAAA,EAAW;AAAA,EAC5H,KAAA,EAAO,EAAE,KAAA,EAAO,cAAA,EAAgB,QAAQ,+BAAA,EAAiC,aAAA,EAAe,OAAA,EAAS,IAAA,EAAM,UAAA;AACzG;AAuFO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,GAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAcG;AACD,EAAA,MAAM,IAAA,GAAO,cAAc,GAAG,CAAA;AAC9B,EAAA,uBACE,GAAA;AAAA,IAAC,iBAAA;AAAA,IAAA;AAAA,MACC,eAAe,IAAA,CAAK,aAAA;AAAA,MACpB,WAAW,IAAA,EAAM,SAAA;AAAA,MACjB,UAAU,IAAA,EAAM,QAAA;AAAA,MAChB,KAAK,IAAA,EAAM,GAAA;AAAA,MACX,QAAA,EAAU;AAAA,QACR,QAAQ,OAAA,CAAQ,MAAA;AAAA,QAChB,QAAA,EAAU,QAAQ,QAAA,IAAY,IAAA;AAAA,QAC9B,OAAO,OAAA,CAAQ,KAAA;AAAA,QACf,MAAM,OAAA,CAAQ;AAAA,OAChB;AAAA,MAEA,QAAA,kBAAA,GAAA;AAAA,QAAC,SAAA;AAAA,QAAA;AAAA,UACC,GAAA;AAAA,UACA,QAAA;AAAA,UACA,MAAA;AAAA,UACA,aAAA;AAAA,UACA,OAAA;AAAA,UACA,SAAA;AAAA,UACA,SAAA;AAAA,UACA,IAAA;AAAA,UAEC;AAAA;AAAA;AACH;AAAA,GACF;AAEJ;AAEA,SAAS,SAAA,CAAU;AAAA,EACjB,GAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,aAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA;AAAA,EACA,IAAA;AAAA,EACA;AACF,CAAA,EAUG;AACD,EAAA,MAAM,IAAA,GAAO,cAAc,GAAG,CAAA;AAC9B,EAAA,MAAM,SAAS,cAAA,EAAe;AAW9B,EAAA,MAAM,UAAA,GAAa,OAAO,SAAS,CAAA;AACnC,EAAA,UAAA,CAAW,OAAA,GAAU,SAAA;AAGrB,EAAA,MAAM,aAAA,GAAgB,OAAO,KAAK,CAAA;AAElC,EAAA,MAAM,aAAA,GAAgB,YAAY,MAAM;AACtC,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AACxB,IAAA,eAAA,EAAgB;AAChB,IAAA,UAAA,CAAW,OAAA,EAAQ;AAKnB,IAAA,MAAA,CAAO,WAAW,MAAM;AACtB,MAAA,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA;AAAA,IAC9C,GAAG,GAAI,CAAA;AAAA,EACT,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,SAAA;AAAA,IACE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAiBE,YAAY,MAAM;AAChB,QAAA,IAAI,cAAc,OAAA,EAAS;AAC3B,QAAA,MAAA,CAAO,QAAA,CAAS,OAAA,CAAQ,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA;AAAA,MAC9C,CAAC;AAAA,KAAA;AAAA,IACH;AAAC,GACH;AAIA,EAAA,MAAM,IAAA,GAAO,GAAA,KAAQ,QAAA,GAAW,IAAA,EAAM,IAAA,GAAO,MAAA;AAI7C,EAAA,MAAM,QAAA,GAAW,kBAAkB,EAAE,QAAA,EAAU,QAAQ,QAAA,EAAU,KAAA,EAAO,OAAA,CAAQ,KAAA,EAAO,CAAA;AACvF,EAAA,MAAM,kBAAkB,iBAAA,CAAkB;AAAA,IACxC,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,UAAU,QAAA,CAAS,QAAA;AAAA,IACnB,OAAO,QAAA,CAAS;AAAA,GACjB,CAAA;AACD,EAAA,MAAM,QAAA,GAAW,MAAM,QAAA,IAAY,eAAA;AACnC,EAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,SAAA,IAAa,EAAE,CAAA;AAI9C,EAAA,MAAM,UAAU,IAAA,CAAK,IAAA;AACrB,EAAA,MAAM,cAAc,aAAA,CAAc,OAAA,GAAU,EAAE,YAAA,EAAc,OAAA,KAAY,MAAS,CAAA;AAEjF,EAAA,MAAM,IAAA,GAAO,MAAM,IAAA,IAAQ,WAAA;AAE3B,EAAA,MAAM,QAAA,GAAW,MAAA,EAAQ,IAAA,IAAQ,EAAC;AAClC,EAAA,MAAM,aAAA,GAAgB,SAAS,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,MAAA,EAAQ,KAAK,CAAA,IAAK,IAAA;AACtE,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,MAAA;AAMzC,EAAA,MAAM,UAA2B,OAAA,IAAW,cAAA;AAC5C,EAAA,MAAM,WAAA,GAAc,QAAQ,KAAA,IAAS,IAAA;AACrC,EAAA,MAAM,eAAA,GAAkB,OAAA;AAAA,IACtB,MACE,mBAAA,CAAoB;AAAA,MAClB,WAAA,EAAa,cAAc,GAAA,CAAI,MAAA;AAAA,MAC/B,KAAA,EAAO,gBAAA;AAAA;AAAA;AAAA,MAGP,cAAc,IAAA,EAAM,YAAA;AAAA,MACpB,IAAA,EAAM,OAAA;AAAA,MACN;AAAA,KACD,CAAA;AAAA,IACH,CAAC,IAAA,EAAM,YAAA,EAAc,OAAA,EAAS,WAAW;AAAA,GAC3C;AAGA,EAAA,MAAM,QAAA,GAAW,MAAM,GAAA,IAAO,eAAA;AAK9B,EAAA,MAAM,kBAAA,GAAqB,OAAA;AAAA,IACzB,MAAM,kBAAkB,QAAA,EAAU,QAAA,EAAU,EAAE,MAAA,EAAQ,MAAA,EAAQ,IAAA,CAAK,MAAA,EAAQ,CAAA;AAAA,IAC3E,CAAC,QAAA,EAAU,QAAA,EAAU,MAAA,EAAQ,KAAK,MAAM;AAAA,GAC1C;AACA,EAAA,MAAM,WAAA,GAAc,IAAA,GAAO,IAAA,CAAK,IAAA,EAAM,WAAA,GAAc,kBAAA;AAEpD,EAAA,uBACE,GAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAS;AAAA,QACP,QAAA;AAAA,QACA,KAAA,EAAO;AAAA,UACL,OAAA,EAAS,MAAM,YAAA,IAAgB,yCAAA;AAAA,UAC/B,OAAA,EAAS,UAAA;AAAA,UACT,UAAA,EAAY,GAAA;AAAA,UACZ,KAAA,EAAO;AAAA,SACT;AAAA,QACA,GAAA,EAAK;AAAA,UACH,KAAA,EACE,UAAA,KAAe,MAAA,GACX,QAAA,CAAS,GAAA,CAAI,CAAC,EAAE,QAAA,EAAU,IAAA,EAAM,GAAG,IAAA,EAAK,KAAM,IAAI,CAAA,GAClD;AAAA,SACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,KAAK,aAAA,GACD;AAAA,UACE,MAAM,aAAA,CAAc,IAAA;AAAA,UACpB,IAAI,aAAA,CAAc,EAAA;AAAA,UAClB,SAAS,aAAA,CAAc,OAAA;AAAA,UACvB,YAAY,aAAA,CAAc,UAAA;AAAA,UAC1B,aAAA,EAAe,QAAA;AAAA,UACf,UAAU,MAAA,EAAQ;AAAA,YAEpB,MAAA,EAAQ,WAAA,GACN,EAAE,IAAA,EAAM,MAAM,EAAA,EAAI,IAAA,EAAM,OAAA,EAAS,IAAA,KACjC,EAAE,IAAA,EAAM,QAAQ,WAAA,EAAa,EAAA,EAAI,QAAQ,SAAA,EAAU;AAAA,QACzD,IAAA;AAAA,QACA,MAAA;AAAA,QACA;AAAA,OACF;AAAA,MACA,YAAY,EAAC;AAAA,MACb,gBAAgB,IAAA,EAAM,cAAA;AAAA,MACtB,MAAA,EAAQ;AAAA;AAAA;AAAA,QAGN,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,KAAA,IAAS,IAAA,CAAK,KAAA;AAAA,QACjC,WAAA;AAAA,QACA,IAAA,EAAM;AAAA,UACJ,MAAM,QAAA,CAAS,QAAA;AAAA,UACf,OAAO,QAAA,CAAS,KAAA;AAAA;AAAA;AAAA,UAGhB,SAAA,EAAW,QAAA,CAAS,SAAA,IAAa,IAAA,EAAM,iBAAA,IAAqB,IAAA;AAAA,UAC5D,MAAM,OAAA,CAAQ;AAAA,SAChB;AAAA,QACA,QAAA;AAAA,QACA,SAAA,EAAW,aAAA;AAAA;AAAA;AAAA;AAAA,QAIX,yBACE,IAAA,CAAA,QAAA,EAAA,EACG,QAAA,EAAA;AAAA,UAAA,IAAA,EAAM,IAAA,EAAM,OAAA;AAAA,8BACZ,mBAAA,EAAA,EAAoB;AAAA,SAAA,EACvB;AAAA,OAEJ;AAAA,MAEC;AAAA;AAAA,GACH;AAEJ;AC3WO,IAAM,gBAAA,GAAwD;AAAA,EACnE,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,aAAA,EAAe,eAAA;AAAA,EACf,MAAA,EAAQ,QAAA;AAAA,EACR,QAAA,EAAU,UAAA;AAAA,EACV,QAAA,EAAU,UAAA;AAAA,EACV,IAAA,EAAM,MAAA;AAAA,EACN,IAAA,EAAM,MAAA;AAAA,EACN,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM,OAAA;AAAA,EACN,QAAA,EAAU,UAAA;AAAA,EACV,IAAA,EAAM,MAAA;AAAA,EACN,KAAA,EAAO,OAAA;AAAA,EACP,IAAA,EAAM,MAAA;AAAA,EACN,QAAA,EAAU,WAAA;AAAA,EACV,SAAA,EAAW,WAAA;AAAA,EACX,QAAA,EAAU,UAAA;AAAA,EACV,UAAA,EAAY,YAAA;AAAA,EACZ,UAAA,EAAY,YAAA;AAAA,EACZ,KAAA,EAAO,QAAA;AAAA;AAAA,EAEP,KAAA,EAAO,OAAA;AAAA,EACP,GAAA,EAAK,KAAA;AAAA,EACL,MAAA,EAAQ,QAAA;AAAA,EACR,MAAA,EAAQ,QAAA;AAAA,EACR,IAAA,EAAM;AACR;AAEA,IAAM,eAAA,GAAkB,eAAA;AAExB,IAAM,gBAAA,GAAmB,gBAAA;AACzB,IAAM,eAAA,GAAkB,gBAAA;AACxB,IAAM,aAAA,GAAgB,6BAAA;AAwBtB,IAAM,kBAAA,GAAqB,cAAkC,IAAI,CAAA;AAE1D,SAAS,cAAA,GAAqC;AACnD,EAAA,OAAO,WAAW,kBAAkB,CAAA;AACtC;AASA,SAAS,mBAAA,CAAoB;AAAA,EAC3B,aAAA;AAAA,EACA;AACF,CAAA,EAGG;AACD,EAAA,MAAM,EAAE,IAAA,EAAM,OAAA,EAAS,cAAc,KAAA,EAAO,SAAA,KAAc,aAAA,EAAc;AACxE,EAAA,MAAM,CAAC,KAAA,EAAO,aAAa,CAAA,GAAI,SAAwB,IAAI,CAAA;AAC3D,EAAA,MAAM,CAAC,UAAA,EAAY,kBAAkB,CAAA,GAAI,SAAyB,MAAM,CAAA;AAMxE,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,GAAY,gBAAA,EAAiB,IAAK,YAAA,CAAa,QAAQ,eAAe,CAAA;AAC5E,MAAA,IAAI,SAAA,gBAAyB,SAAS,CAAA;AACtC,MAAA,MAAM,SAAA,GAAY,YAAA,CAAa,OAAA,CAAQ,eAAe,CAAA;AACtD,MAAA,IAAI,SAAA,KAAc,MAAA,IAAU,SAAA,KAAc,MAAA,IAAU,cAAc,MAAA,EAAQ;AACxE,QAAA,kBAAA,CAAmB,SAAS,CAAA;AAAA,MAC9B;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAGL,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,IAAA,IAAQ,IAAA,CAAK,MAAA,KAAW,CAAA,EAAG;AAChC,IAAA,aAAA,CAAc,CAAC,IAAA,KAAU,IAAA,IAAQ,IAAA,CAAK,KAAK,CAAC,CAAA,KAAM,CAAA,CAAE,EAAA,KAAO,IAAI,CAAA,GAAI,IAAA,GAAO,IAAA,CAAK,CAAC,EAAE,EAAG,CAAA;AAAA,EACvF,CAAA,EAAG,CAAC,IAAI,CAAC,CAAA;AAET,EAAA,MAAM,QAAA,GAAWC,WAAAA,CAAY,CAAC,EAAA,KAAe;AAC3C,IAAA,aAAA,CAAc,EAAE,CAAA;AAChB,IAAA,IAAI;AACF,MAAA,YAAA,CAAa,OAAA,CAAQ,iBAAiB,EAAE,CAAA;AAAA,IAC1C,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,oBAAA,CAAqB,EAAE,CAAA;AAAA,EACzB,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,WAAAA,CAAY,CAAC,CAAA,KAAsB;AACvD,IAAA,kBAAA,CAAmB,CAAC,CAAA;AACpB,IAAA,IAAI;AACF,MAAA,YAAA,CAAa,OAAA,CAAQ,iBAAiB,CAAC,CAAA;AAAA,IACzC,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,KAAA,GAAQC,QAAqB,MAAM;AAIvC,IAAA,IAAI,aAAA,EAAe;AACjB,MAAA,OAAO;AAAA,QACL,OAAO,aAAA,CAAc,KAAA;AAAA,QACrB,UAAU,aAAA,CAAc,QAAA;AAAA,QACxB,IAAA,EACE,cAAc,IAAA,IAAA,CACb,IAAA,IAAQ,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,UACvB,IAAI,CAAA,CAAE,EAAA;AAAA,UACN,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,SAAS,CAAA,CAAE,OAAA;AAAA,UACX,YAAY,CAAA,CAAE,UAAA;AAAA,UACd,MAAM,CAAA,CAAE,IAAA;AAAA,UACR,OAAA,EAAS,EAAE,OAAA,IAAW;AAAA,SACxB,CAAE,CAAA;AAAA,QACJ,WAAA,EAAa,KAAA;AAAA,QACb,UAAA;AAAA,QACA;AAAA,OACF;AAAA,IACF;AACA,IAAA,OAAO;AAAA,MACL,KAAA;AAAA,MACA,QAAA;AAAA,MACA,OAAO,IAAA,IAAQ,EAAC,EAAG,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QAC7B,IAAI,CAAA,CAAE,EAAA;AAAA,QACN,MAAM,CAAA,CAAE,IAAA;AAAA,QACR,SAAS,CAAA,CAAE,OAAA;AAAA,QACX,YAAY,CAAA,CAAE,UAAA;AAAA,QACd,MAAM,CAAA,CAAE,IAAA;AAAA,QACR,OAAA,EAAS,EAAE,OAAA,IAAW;AAAA,OACxB,CAAE,CAAA;AAAA;AAAA;AAAA,MAGF,WAAA,EAAa,YAAA,IAAgB,CAAC,SAAA,IAAa,CAAC,IAAA;AAAA,MAC5C,UAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,CAAA,EAAG,CAAC,aAAA,EAAe,KAAA,EAAO,QAAA,EAAU,MAAM,YAAA,EAAc,SAAA,EAAW,UAAA,EAAY,aAAa,CAAC,CAAA;AAM7F,EAAA,MAAM,cAAc,KAAA,CAAM,KAAA;AAC1B,EAAAF,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC3B,IAAA,oBAAA,CAAqB,WAAqB,CAAA;AAC1C,IAAA,IAAI;AACF,MAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACxC,MAAA,IAAI,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,sBAAsB,MAAM,WAAA,EAAa;AAChE,QAAA,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,sBAAA,EAAwB,WAAqB,CAAA;AAClE,QAAA,MAAA,CAAO,OAAA,CAAQ,aAAa,MAAA,CAAO,OAAA,CAAQ,OAAO,EAAA,EAAI,GAAA,CAAI,UAAU,CAAA;AAAA,MACtE;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA,EAAG,CAAC,WAAW,CAAC,CAAA;AAEhB,EAAA,uBAAOG,GAAAA,CAAC,kBAAA,CAAmB,QAAA,EAAnB,EAA4B,OAAe,QAAA,EAAS,CAAA;AAC9D;AAoBA,SAAS,WAAA,CAAY;AAAA,EACnB,aAAA;AAAA,EACA,aAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAMG;AACD,EAAA,MAAM,OAAA,GAAUD,OAAAA;AAAA,IACd,MACE,oBAAA,CAAqB;AAAA,MACnB,QAAA,EAAU,QAAA,EAAU,QAAA,IAAY,CAAA,EAAG,aAAa,CAAA,SAAA,CAAA;AAAA;AAAA,MAEhD,YAAA,EAAc,QAAA,EAAU,YAAA,KAAiB,YAAY,EAAA,CAAA;AAAA,MACrD,GAAI,UAAU,KAAA,GAAQ,EAAE,OAAO,QAAA,CAAS,KAAA,KAAU;AAAC,KACpD,CAAA;AAAA,IACH,CAAC,aAAA,EAAe,QAAA,EAAU,UAAU,QAAA,EAAU,YAAA,EAAc,UAAU,KAAK;AAAA,GAC7E;AAEA,EAAA,MAAM,IAAA,GAAOA,OAAAA;AAAA,IACX,OAAO;AAAA,MACL,IAAI,QAAA,CAAS,MAAA;AAAA,MACb,KAAA,EAAO,SAAS,KAAA,IAAS,EAAA;AAAA,MACzB,QAAA,EAAU,SAAS,QAAA,IAAY;AAAA,KACjC,CAAA;AAAA,IACA,CAAC,QAAA,CAAS,MAAA,EAAQ,QAAA,CAAS,KAAA,EAAO,SAAS,QAAQ;AAAA,GACrD;AAEA,EAAA,MAAM,MAAA,GAASA,OAAAA;AAAA,IACb,OAAO;AAAA,MACL,aAAA;AAAA,MACA,MAAM,QAAA,CAAS,IAAA,CAAK,WAAA,EAAY,KAAM,UAAU,OAAA,GAAU,UAAA;AAAA,MAC1D,SAAA,EAAW,IAAA;AAAA,MACX,WAAA,EAAa,MAAM,MAAA,CAAO,QAAA,CAAS;AAAA,KACrC,CAAA;AAAA,IACA,CAAC,aAAA,EAAe,QAAA,CAAS,IAAI;AAAA,GAC/B;AAIA,EAAA,IAAI,UAAU,aAAA,EAAe,uBAAOC,GAAAA,CAAC,mBAAiB,QAAA,EAAS,CAAA;AAE/D,EAAA,uBACEA,GAAAA,CAAC,gBAAA,EAAA,EAAiB,OAAA,EAAkB,IAAA,EAAY,QAC9C,QAAA,kBAAAA,GAAAA,CAAC,eAAA,EAAA,EAAiB,QAAA,EAAS,CAAA,EAC7B,CAAA;AAEJ;AAKA,IAAM,uBAAuB,aAAA,CAAyD;AAAA,EACpF,KAAA,EAAO,KAAA;AAAA,EACP,WAAW,MAAM;AAAA,EAAC;AACpB,CAAC,CAAA;AAaD,SAAS,eAAA,CAAgB,EAAE,QAAA,EAAS,EAA4B;AAC9D,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAI,SAAS,KAAK,CAAA;AAChD,EAAA,MAAM,YAAYF,WAAAA,CAAY,MAAM,aAAa,IAAI,CAAA,EAAG,EAAE,CAAA;AAC1D,EAAA,MAAM,QAAA,GAAWC,OAAAA,CAAQ,OAAO,EAAE,KAAA,EAAO,MAAM,SAAA,EAAU,CAAA,EAAI,CAAC,SAAS,CAAC,CAAA;AAExE,EAAA,uBACEE,IAAAA,CAAC,oBAAA,CAAqB,QAAA,EAArB,EAA8B,OAAO,QAAA,EACnC,QAAA,EAAA;AAAA,IAAA,QAAA;AAAA,IAIA,SAAA,mBAAYD,GAAAA,CAAC,qBAAA,EAAA,EAAsB,IAAA,EAAI,IAAA,EAAC,OAAA,EAAS,MAAM,YAAA,CAAa,KAAK,CAAA,EAAG,CAAA,GAAK,IAAA;AAAA,oBAClFA,IAAC,eAAA,EAAA,EAAgB;AAAA,GAAA,EACnB,CAAA;AAEJ;AASO,SAAS,iBAAA,CAAkB;AAAA,EAChC,aAAA;AAAA,EACA,aAAA,GAAgB,aAAA;AAAA,EAChB,SAAA,EAAW,iBAAA;AAAA,EACX,QAAA;AAAA,EACA,GAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAeG;AACD,EAAA,MAAM,cAAA,GAAiBD,OAAAA;AAAA,IACrB,MACE,OAAO,IAAA,EAAM,IAAA,KAAS;AACpB,MAAA,MAAM,MAAM,MAAM,KAAA,CAAM,GAAG,aAAa,CAAA,CAAA,EAAI,IAAI,CAAA,CAAA,EAAI;AAAA,QAClD,MAAA,EAAQ,MAAM,MAAA,IAAU,KAAA;AAAA,QACxB,SAAS,IAAA,EAAM,OAAA;AAAA,QACf,MAAM,IAAA,EAAM;AAAA,OACb,CAAA;AACD,MAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,OAAA,EAAU,IAAI,CAAA,EAAA,EAAK,GAAA,CAAI,MAAM,CAAA,CAAE,CAAA;AAC5D,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,GAAA,EAAK,OAAO,IAAA;AAC/B,MAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,MAAA,OAAO,IAAA,GAAO,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA,GAAI,IAAA;AAAA,IACnC,CAAA;AAAA,IACF,CAAC,aAAa;AAAA,GAChB;AACA,EAAA,MAAM,YAAY,iBAAA,IAAqB,cAAA;AAEvC,EAAA,uBACEC,IAAC,iBAAA,EAAA,EAAkB,SAAA,EACjB,0BAAAA,GAAAA,CAAC,mBAAA,EAAA,EAAoB,aAAA,EAAe,GAAA,EAClC,QAAA,kBAAAA,GAAAA;AAAA,IAAC,WAAA;AAAA,IAAA;AAAA,MACC,aAAA;AAAA,MACA,aAAA;AAAA,MACA,QAAA;AAAA,MACA,QAAA;AAAA,MAEC;AAAA;AAAA,KAEL,CAAA,EACF,CAAA;AAEJ;AAIO,SAAS,mBAAA,GAAsB;AACpC,EAAA,MAAM,QAAQ,cAAA,EAAe;AAC7B,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,uBACEC,IAAAA,CAAAC,QAAAA,EAAA,EACE,QAAA,EAAA;AAAA,oBAAAF,GAAAA,CAAC,UAAA,EAAA,EAAW,KAAA,EAAO,KAAA,CAAM,KAAA,EAAO,CAAA;AAAA,oBAChCA,IAAC,gBAAA,EAAA,EAAiB,KAAA,EAAO,MAAM,UAAA,EAAY,QAAA,EAAU,MAAM,aAAA,EAAe,CAAA;AAAA,oBAC1EA,GAAAA;AAAA,MAAC,gBAAA;AAAA,MAAA;AAAA,QACC,KAAA,EAAM,UAAA;AAAA,QACN,WAAA,EAAY,kDAAA;AAAA,QAEZ,QAAA,kBAAAA,IAAC,kBAAA,EAAA,EAAmB;AAAA;AAAA,KACtB;AAAA,oBACAA,GAAAA,CAAC,aAAA,EAAA,EAAc,KAAA,EAAO,MAAM,KAAA,EAAO,CAAA;AAAA,oBACnCA,GAAAA,CAAC,mBAAA,EAAA,EAAoB,KAAA,EAAO,MAAM,KAAA,EAAO;AAAA,GAAA,EAC3C,CAAA;AAEJ;AAOA,SAAS,kBAAA,GAAqB;AAC5B,EAAA,MAAM,EAAE,KAAA,EAAO,SAAA,EAAU,GAAI,WAAW,oBAAoB,CAAA;AAC5D,EAAA,IAAI,CAAC,OAAO,OAAO,IAAA;AACnB,EAAA,uBAAOA,GAAAA,CAAC,YAAA,EAAA,EAAa,QAAA,EAAS,WAAA,EAAY,aAAa,SAAA,EAAW,CAAA;AACpE;AAGO,SAAS,eAAA,GAA2B;AACzC,EAAA,MAAM,QAAQ,cAAA,EAAe;AAC7B,EAAA,OAAA,CAAQ,KAAA,EAAO,IAAA,IAAQ,EAAC,EAAG,IAAA,CAAK,CAAC,CAAA,KAAM,QAAA,CAAS,IAAA,CAAK,CAAA,CAAE,OAAA,IAAW,EAAE,CAAC,CAAA;AACvE;AAEA,IAAM,YAAA,GAAgC;AAAA,EACpC,EAAE,OAAO,SAAA,EAAW,IAAA,EAAM,GAAG,aAAa,CAAA,eAAA,CAAA,EAAmB,UAAU,IAAA,EAAK;AAAA,EAC5E,EAAE,OAAO,OAAA,EAAS,IAAA,EAAM,GAAG,aAAa,CAAA,qBAAA,CAAA,EAAyB,UAAU,IAAA,EAAK;AAAA,EAChF;AAAA,IACE,KAAA,EAAO,eAAA;AAAA,IACP,IAAA,EAAM,0DAAA;AAAA,IACN,QAAA,EAAU;AAAA;AAEd,CAAA;AAOO,SAAS,gBAAgB,IAAA,EAMlB;AACZ,EAAA,MAAM,eAAe,eAAA,EAAgB;AACrC,EAAA,OAAOD,OAAAA;AAAA,IACL,MAAO,YAAA,GAAe,EAAE,KAAA,EAAO,YAAA,EAAc,GAAG,IAAA,EAAK,GAAI,EAAE,KAAA,EAAO,YAAA,EAAa;AAAA,IAC/E,CAAC,YAAA,EAAc,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,YAAY;AAAA,GAC7F;AACF;AAYO,SAAS,kBAAkB,QAAA,EAGb;AACnB,EAAA,MAAM,EAAE,EAAA,EAAI,MAAA,EAAO,GAAI,WAAA,EAAY;AACnC,EAAA,OAAO,eAAA,CAAgB;AAAA,IACrB,SAAS,EAAA,EAAI,QAAA;AAAA,IACb,cAAc,QAAA,EAAU,QAAA;AAAA,IACxB,OAAO,QAAA,EAAU,KAAA;AAAA,IACjB,WAAW,EAAA,EAAI,SAAA;AAAA,IACf;AAAA,GACD,CAAA;AACH;AAsBO,SAAS,iBAAA,CAAkB;AAAA,EAChC,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAIgB;AACd,EAAA,MAAM,QAAQ,cAAA,EAAe;AAC7B,EAAA,MAAM,QAAQ,kBAAA,EAAmB;AACjC,EAAA,MAAM,SAAA,GAAY,KAAA,EAAO,IAAA,CAAK,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,KAAA,CAAM,KAAK,CAAA,IAAK,IAAA;AACnE,EAAA,MAAM,SAAA,GAAY,SAAA,GAAY,CAAA,KAAA,EAAQ,SAAA,CAAU,EAAE,CAAA,CAAA,GAAK,EAAA;AACvD,EAAA,OAAO;AAAA;AAAA,IAEL,aAAA,EAAe,GAAG,aAAa,CAAA,WAAA,CAAA;AAAA,IAC/B,wBAAA,EAA0B,CAAA,EAAG,aAAa,CAAA,sBAAA,EAAyB,SAAS,CAAA,CAAA;AAAA,IAC5E,KAAA,EAAA,CAAQ,KAAA,EAAO,IAAA,IAAQ,EAAC,EACrB,OAAO,CAAC,CAAA,KAAM,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,IAAI,EACjC,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAO,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,IAAA,EAAiB,OAAA,EAAS,CAAA,CAAE,IAAA,EAAK,CAAE,CAAA;AAAA,IAC1E,QAAA,EAAU,CAAC,EAAE,EAAA,EAAI,MAAA,EAAQ,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,SAAA,EAAW,IAAA,EAAM,MAAA,EAAQ,IAAA,EAAM,CAAA;AAAA,IAC/E,cAAA,EAAgB,GAAG,aAAa,CAAA,oBAAA,CAAA;AAAA,IAChC,OAAO,EAAE,KAAA,EAAO,MAAM,KAAA,EAAO,QAAA,EAAU,MAAM,QAAA;AAAS,GACxD;AACF;AAUA,IAAM,SAAA,GAOA;AAAA,EACJ;AAAA,IACE,EAAA,EAAI,cAAA;AAAA,IACJ,KAAA,EAAO,mBAAA;AAAA,IACP,IAAA,EAAM,OAAA;AAAA,IACN,IAAA,EAAM,GAAG,aAAa,CAAA,uBAAA,CAAA;AAAA,IACtB,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA,IACE,EAAA,EAAI,UAAA;AAAA,IACJ,KAAA,EAAO,eAAA;AAAA,IACP,IAAA,EAAM,WAAA;AAAA,IACN,IAAA,EAAM,GAAG,aAAa,CAAA,mBAAA,CAAA;AAAA,IACtB,QAAA,EAAU;AAAA,GACZ;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,EAAA,EAAI,UAAA;AAAA,IACJ,KAAA,EAAO,eAAA;AAAA,IACP,IAAA,EAAM,MAAA;AAAA,IACN,IAAA,EAAM,wCAAA;AAAA,IACN,QAAA,EAAU;AAAA;AAEd,CAAA;AAkCO,SAAS,aAAA,CAAc,GAAA,GAAwB,EAAC,EAAY;AACjE,EAAA,MAAM,EAAE,cAAA,GAAiB,cAAA,EAAgB,YAAA,EAAa,GACpD,OAAO,GAAA,KAAQ,QAAA,GAAW,EAAE,cAAA,EAAgB,GAAA,EAAK,YAAA,EAAc,QAAU,GAAI,GAAA;AAC/E,EAAA,MAAM,GAAA,GAAM,WAAW,kBAAkB,CAAA;AACzC,EAAA,MAAM,MAAM,mBAAA,EAAoB;AAChC,EAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,IAAS,IAAA;AAC5B,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAyC,IAAI,CAAA;AAC7E,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAI,SAAwB,IAAI,CAAA;AAGxE,EAAAF,UAAU,MAAM;AACd,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,YAAA,CAAa,OAAA,CAAQ,gBAAgB,CAAA;AACpD,MAAA,IAAI,MAAA,IAAU,SAAA,CAAU,IAAA,CAAK,CAAC,CAAA,KAAM,EAAE,EAAA,KAAO,MAAM,CAAA,EAAG,iBAAA,CAAkB,MAAM,CAAA;AAAA,IAChF,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAAA,UAAU,MAAM;AACd,IAAA,IAAI,CAAC,GAAA,IAAO,CAAC,KAAA,EAAO;AACpB,IAAA,IAAI,SAAA,GAAY,KAAA;AAChB,IAAA,GAAA,CAAI,IAAA,EAAM,EAAE,OAAA,EAAS,EAAE,gBAAA,EAAkB,KAAA,EAAM,EAAG,CAAA,CAC/C,IAAA,CAAK,CAAC,IAAA,KAAS;AACd,MAAA,IAAI,SAAA,EAAW;AACf,MAAA,MAAM,KAAA,GAAS,MAAqD,KAAA,IAAS,IAAA;AAC7E,MAAA,WAAA,CAAY,KAAK,CAAA;AAAA,IACnB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AAAA,IAEb,CAAC,CAAA;AACH,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,GAAA,EAAK,KAAK,CAAC,CAAA;AAEf,EAAA,MAAM,OAAA,GAAU,UAAU,MAAA,CAAO,CAAC,MAAM,QAAA,GAAW,CAAA,CAAE,EAAE,CAAC,CAAA;AAKxD,EAAA,MAAM,MAAA,GAAS,YAAA,GAAe,SAAA,CAAU,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,YAAY,CAAA,IAAK,IAAA,GAAO,IAAA;AACrF,EAAA,MAAM,QAAA,GAAW,cAAA,GACb,OAAA,CAAQ,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,cAAc,CAAA,IAAK,IAAA,GAChD,IAAA;AACJ,EAAA,MAAM,OAAA,GACJ,MAAA,IACA,QAAA,IACA,OAAA,CAAQ,CAAC,CAAA,IACT,SAAA,CAAU,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,EAAA,KAAO,cAAc,CAAA,IAC7C,UAAU,CAAC,CAAA;AACb,EAAA,MAAM,MAAA,GAAS,KAAA,GAAQ,CAAA,KAAA,EAAQ,KAAK,CAAA,CAAA,GAAK,EAAA;AAEzC,EAAA,OAAO;AAAA,IACL,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,MAAM,OAAA,CAAQ,IAAA;AAAA,IACd,SACE,OAAA,CAAQ,MAAA,GAAS,IACb,OAAA,CAAQ,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,IAAI,CAAA,CAAE,EAAA,EAAI,OAAO,CAAA,CAAE,KAAA,EAAO,MAAM,CAAA,CAAE,IAAA,GAAO,CAAA,GAC/D,MAAA;AAAA,IACN,QAAA,EACE,OAAA,CAAQ,MAAA,GAAS,CAAA,GACb,CAAC,EAAA,KAAe;AACd,MAAA,MAAM,MAAM,SAAA,CAAU,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AAC7C,MAAA,IAAI,CAAC,GAAA,EAAK;AAGV,MAAA,iBAAA,CAAkB,EAAE,CAAA;AACpB,MAAA,IAAI;AACF,QAAA,YAAA,CAAa,OAAA,CAAQ,kBAAkB,EAAE,CAAA;AAAA,MAC3C,CAAA,CAAA,MAAQ;AAAA,MAER;AACA,MAAA,MAAM,MAAM,GAAA,CAAI,IAAA,CAAK,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC3C,MAAA,MAAA,CAAO,QAAA,CAAS,MAAA;AAAA,QACd,GAAA,CAAI,QAAA,IAAY,MAAA,GAAS,CAAA,EAAG,IAAI,IAAI,CAAA,EAAG,GAAG,CAAA,EAAG,MAAA,CAAO,KAAA,CAAM,CAAC,CAAC,KAAK,GAAA,CAAI;AAAA,OACvE;AAAA,IACF,CAAA,GACA;AAAA,GACR;AACF","file":"app-chrome.js","sourcesContent":["// Breadcrumb derivation — pure, dependency-free logic (no React, no DevNotes),\n// so it is importable from a server module and testable in isolation.\n//\n// The standard is Dashboard > Sub > Item (HITL 2026-07-23), derived from the\n// same nav tree the sidebar renders. Apps previously hand-built their own\n// trails and disagreed: forms rendered \"Forms > Dashboard\" while polls rendered\n// a lone \"Polls\" crumb under a \"Polls\" title (\"Polls / Polls\").\n\nimport type { NavBreadcrumb, NavItem } from '../native/index'\n\nexport function deriveBreadcrumbs(\n items: NavItem[],\n pathname: string,\n opts?: { search?: string; origin?: string },\n): NavBreadcrumb[] {\n const search = opts?.search ?? (typeof window !== 'undefined' ? window.location.search : '')\n const origin = opts?.origin ?? (typeof window !== 'undefined' ? window.location.origin : '')\n\n // Same-origin comparison: cross-app hrefs are absolute, local ones relative.\n const matches = (href?: string): boolean => {\n if (!href) return false\n let path = href\n let query = ''\n if (/^https?:\\/\\//i.test(href)) {\n try {\n const u = new URL(href)\n if (origin && u.origin !== origin) return false\n path = u.pathname\n query = u.search\n } catch {\n return false\n }\n } else {\n const qi = href.indexOf('?')\n if (qi >= 0) {\n path = href.slice(0, qi) || '/'\n query = href.slice(qi)\n }\n }\n if (path !== pathname) return false\n if (!query) return true\n const want = new URLSearchParams(query)\n const have = new URLSearchParams(search)\n for (const [k, v] of want) if (have.get(k) !== v) return false\n return true\n }\n\n // Depth-first walk for the deepest item owning the current route.\n const walk = (nodes: NavItem[], trail: NavItem[]): NavItem[] | null => {\n for (const node of nodes) {\n const here = [...trail, node]\n if (node.children?.length) {\n const deeper = walk(node.children, here)\n if (deeper) return deeper\n }\n if (matches(node.href)) return here\n }\n return null\n }\n\n const trail = walk(items, [])\n // Always anchored at Dashboard; the last crumb is the current location and\n // carries no href (the shell marks it aria-current=\"page\").\n const dashboard = items.find((i) => i.label === 'Dashboard')\n const crumbs: NavBreadcrumb[] = [{ label: 'Dashboard', href: dashboard?.href }]\n if (!trail) return crumbs\n for (const node of trail) {\n if (node.label === 'Dashboard') continue\n crumbs.push({ label: node.label, href: node.href })\n }\n const last = crumbs[crumbs.length - 1]\n if (last) delete (last as { href?: string }).href\n return crumbs\n}\n","// Identity resolution — pure logic, shared by the chrome hook.\n//\n// The chrome API (VRM-core profile) is the AUTHORITY for a user's display name;\n// what an app knows from its own session token is a pre-hydration fallback. An\n// email is never a name: forms passed `user.name ?? user.email`, so a user with\n// no name set had their email rendered as their name in the top bar and the\n// shell's \"add your name\" affordance never fired (HITL 2026-07-23).\n\nexport type ResolvedIdentity = {\n userName: string | null\n email: string | null\n avatarUrl: string | null\n loaded: boolean\n}\n\n/** True when a \"name\" is really just an email address. */\nexport function looksLikeEmail(value: string | null | undefined): boolean {\n return typeof value === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())\n}\n\nexport function resolveIdentity(input: {\n apiName?: string | null\n fallbackName?: string | null\n email?: string | null\n avatarUrl?: string | null\n loaded?: boolean\n}): ResolvedIdentity {\n const apiName = looksLikeEmail(input.apiName) ? null : input.apiName ?? null\n const fallbackName = looksLikeEmail(input.fallbackName) ? null : input.fallbackName ?? null\n return {\n userName: apiName ?? fallbackName,\n email: input.email ?? null,\n avatarUrl: input.avatarUrl ?? null,\n loaded: input.loaded ?? false,\n }\n}\n","// ---------------------------------------------------------------------------\n// PolitogyAppFrame — ONE component that renders the entire app frame.\n//\n// Why this exists (HITL directive 2026-07-23): shipping the same npm package at\n// the same version is necessary but NOT sufficient. The package exposed\n// PRIMITIVES (NavAppShell + a dozen hooks) and every app hand-assembled them in\n// its own ~150-line Shell.tsx. Identical inputs were never guaranteed, so the\n// outputs diverged — measured on production: forms rendered its user's EMAIL as\n// their name, polls' breadcrumb read \"Polls / Polls\", forms and surveys pinned\n// the mode pill to a hardcoded \"relationship\", and each app declared its own\n// chrome CSS variables (surveys' accent was #C8102E where everyone else was\n// #E63946).\n//\n// The fix is to stop exporting assembly as an app responsibility. An app now\n// renders:\n//\n// <PolitogyAppFrame app=\"forms\" pathname={pathname} linkComponent={Link}\n// session={{ userId, email, role, accountName, accountId }}\n// onSignOut={signOut}>\n// {children}\n// </PolitogyAppFrame>\n//\n// and passes NOTHING about the chrome: no nav, no breadcrumbs, no title, no\n// mode, no user menu, no footer, no theme. Those are derived here, identically,\n// for every app. Anything an app cannot pass is something it cannot drift on.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react'\n\nimport { broadcastLogout, watchLogout } from '../logout-signal'\nimport {\n NavAppShell,\n standardPolitogyNav,\n type NavLinkComponent,\n} from '../native/index'\nimport {\n ChromeTopBarActions,\n PolitogyAppChrome,\n standardNavIcons,\n useChromeFooter,\n useChromeIdentity,\n useChromeMode,\n useChromeState,\n useChromeUserMenu,\n type ChromeDevNotesConfig,\n type ControlledOrgState,\n} from './index'\nimport type {\n NavBreadcrumb,\n NavChromeFetcher,\n NavItem,\n NavMode,\n NavUserMenu,\n} from '../native/index'\nimport type { StandardNavMode } from '../native/standardNav'\nimport { deriveBreadcrumbs } from '../lib/breadcrumbs'\n\n/**\n * The core app — the one that OWNS the canonical nav tree and routes in-app.\n * `PolitogyFrameData.core` is honored for this key and no other.\n */\nconst CORE_APP: PolitogyAppKey = 'vrm'\n\n/** Every app that renders the shared chrome. Keyed by its subdomain. */\nexport type PolitogyAppKey =\n | 'vrm'\n | 'contacts'\n | 'forms'\n | 'surveys'\n | 'polls'\n | 'petitions'\n // Campaign Mode standalone apps.\n | 'a2p'\n | 'phonebank'\n | 'canvass'\n | 'signs'\n\n/**\n * The operating Mode an app is scoped to, if any. It drives two things,\n * BOTH derived here from app identity so no app passes chrome config:\n * 1. the standardPolitogyNav tree the sidebar renders, and\n * 2. the static Mode pill the app shows (a campaign app shows a Campaign\n * pill, never Relationship).\n * An UNSCOPED app (no `mode`) keeps its prior behavior exactly: the\n * Relationship tree + an entitlement-driven Mode pill.\n */\nexport type PolitogyAppMode = StandardNavMode\n\n/**\n * The per-app registry — the ONLY place app identity lives. A new app adds a\n * row here and inherits every chrome behavior; it never re-derives one.\n */\nexport const POLITOGY_APPS: Record<\n PolitogyAppKey,\n { title: string; origin: string; storagePrefix: string; mode?: PolitogyAppMode }\n> = {\n vrm: { title: 'Politogy VRM', origin: 'https://app.politogyvrm.com', storagePrefix: 'vrm' },\n contacts: { title: 'Contacts', origin: 'https://contacts.politogyvrm.com', storagePrefix: 'contacts' },\n forms: { title: 'Forms', origin: 'https://forms.politogyvrm.com', storagePrefix: 'forms' },\n surveys: { title: 'Surveys', origin: 'https://surveys.politogyvrm.com', storagePrefix: 'surveys' },\n polls: { title: 'Polls', origin: 'https://polls.politogyvrm.com', storagePrefix: 'polls' },\n petitions: {\n title: 'Petitions',\n origin: 'https://petition-mode.politogyvrm.com',\n storagePrefix: 'petitions',\n mode: 'petition',\n },\n // Campaign Mode apps — each scoped to Campaign, so they render the Campaign\n // tree and a static Campaign pill. Not yet deployed; hidden from the Campaign\n // nav by CAMPAIGN_FEATURE_AVAILABILITY until their launch flips the flag.\n a2p: { title: 'A2P Messaging', origin: 'https://a2p.politogyvrm.com', storagePrefix: 'a2p', mode: 'campaign' },\n phonebank: { title: 'Phone Banking', origin: 'https://phonebank.politogyvrm.com', storagePrefix: 'phonebank', mode: 'campaign' },\n canvass: { title: 'Field Canvassing', origin: 'https://canvass.politogyvrm.com', storagePrefix: 'canvass', mode: 'campaign' },\n signs: { title: 'Sign Mapping', origin: 'https://signs.politogyvrm.com', storagePrefix: 'signs', mode: 'campaign' },\n}\n\nexport type PolitogyAppSession = {\n /** Stable IdP subject (`sub`). */\n userId: string\n email: string | null\n /** The app's own role string — feeds DevNotes admin gating. */\n role: string\n /** The app's own workspace label, used only until core orgs resolve. */\n accountName: string\n accountId: string\n /**\n * Pre-hydration display name ONLY. The chrome API is the authority; an email\n * passed here is discarded rather than rendered as a name.\n */\n userName?: string | null\n}\n\n/**\n * Optional DATA plumbing for apps whose session/auth differs — never styling.\n *\n * Everything here is about WHERE data comes from (which fetcher, which org\n * state, which routes an app owns), not what the chrome looks like. Adding a\n * visual option to this type would re-open the drift this component exists to\n * close.\n */\nexport type PolitogyFrameData = {\n /** Chrome-API fetcher override for apps that authenticate client-side. */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for those same direct-auth apps. */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state for apps that own active-org + switching. */\n org?: ControlledOrgState\n /** App-owned children for a standard nav entry this app owns (Contacts). */\n contactsItem?: { href?: string; children?: NavItem[] }\n /** Avatar fallback when the profile has no uploaded photo (memoji, default). */\n fallbackAvatarUrl?: string | null\n /**\n * CORE-ONLY frame inputs. **Honored only when `app` is the core app (`vrm`)\n * — silently ignored for every other app**, which is what keeps the\n * no-drift guarantee intact: a feature app cannot re-specify a chrome\n * element even by passing this, so the props are inert for it.\n *\n * Core is genuinely different, and pretending otherwise would have meant\n * REMOVING shipped behavior from app.politogyvrm.com to satisfy packaging —\n * which Reference/UI-Platform forbids (\"migration may not drop a shipped\n * element … extend the shell first, then migrate\"). Specifically, core:\n * - owns the canonical nav tree AND decorates it with live entitlement\n * state a static tree cannot express (10DLC gating, contact/group\n * counts, org-lock),\n * - routes in-app (react-router), so its rows must be app-relative paths,\n * not the absolute cross-app URLs `standardPolitogyNav` emits,\n * - publishes per-page title/breadcrumbs from 66 page components,\n * - switches Mode client-side rather than by full-page navigation,\n * - owns real linked-account switching (with its own modals).\n *\n * Everything here is still DATA or app-owned STATE. There is no styling\n * input, no ordering input, and no way to change the top-bar action cluster\n * — those stay identical for every app, core included.\n */\n core?: PolitogyCoreFrame\n}\n\n/** See `PolitogyFrameData.core` — core app (`vrm`) only. */\nexport type PolitogyCoreFrame = {\n /** The canonical nav tree, decorated with core's live entitlement state. */\n nav?: NavItem[]\n /** Per-page identity published by core's own pages. */\n page?: { title?: string; breadcrumbs?: NavBreadcrumb[]; actions?: ReactNode }\n /** Client-side Mode switching (core navigates in-app, not cross-origin). */\n mode?: NavMode\n /** Real linked-account switching + core's theme persistence. */\n userMenu?: NavUserMenu\n /** Mode-specific brand wordmark (core swaps it per operating Mode). */\n brandLogoSrc?: string\n /** Core wraps every page in PageContainer, which emits its own <main>. */\n contentWrapper?: 'padded' | 'bare'\n}\n\nexport type PolitogyBuildMeta = {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}\n\nexport function PolitogyAppFrame({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n /** Current path from the app's router (Next: usePathname()). */\n pathname: string\n /** Current query string, when the app routes by query (forms' ?tab=). */\n search?: string\n /** The app's routing primitive (Next: <Link>). */\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n buildMeta?: PolitogyBuildMeta\n /** Optional DATA plumbing (never styling) — see PolitogyFrameData. */\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n return (\n <PolitogyAppChrome\n storagePrefix={meta.storagePrefix}\n fetchJson={data?.fetchJson}\n devNotes={data?.devNotes}\n org={data?.org}\n identity={{\n userId: session.userId,\n userName: session.userName ?? null,\n email: session.email,\n role: session.role,\n }}\n >\n <FrameBody\n app={app}\n pathname={pathname}\n search={search}\n linkComponent={linkComponent}\n session={session}\n onSignOut={onSignOut}\n buildMeta={buildMeta}\n data={data}\n >\n {children}\n </FrameBody>\n </PolitogyAppChrome>\n )\n}\n\nfunction FrameBody({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n pathname: string\n search?: string\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n buildMeta?: PolitogyBuildMeta\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n const chrome = useChromeState()\n\n // Sign-out is a PLATFORM event, not an app event. Each app holds its own\n // host-only session cookie with a long sliding window, so without this a user\n // who signed out in one tab stayed fully signed in in every other open tab\n // and on every other subdomain. Announce the sign-out to all of them, then\n // run the app's own sign-out.\n //\n // Every app reaches sign-out through this frame, so wiring it here covers the\n // whole estate without an app having to opt in — the same guarantee the rest\n // of the chrome relies on.\n const signOutRef = useRef(onSignOut)\n signOutRef.current = onSignOut\n // This tab started the sign-out, so its own watcher must not also fire and\n // run the app's sign-out a second time concurrently.\n const signingOutRef = useRef(false)\n\n const handleSignOut = useCallback(() => {\n signingOutRef.current = true\n broadcastLogout()\n signOutRef.current()\n // The app's own handler is best-effort, for the reason spelled out below.\n // Give it a moment to do its real work — clear its session cookie, reach\n // the IdP's end-session endpoint — then force the navigation ourselves if\n // we are somehow still sitting on a page the user has just signed out of.\n window.setTimeout(() => {\n window.location.replace(window.location.href)\n }, 2000)\n }, [])\n\n useEffect(\n () =>\n // A tab that learns of a sign-out elsewhere forces a real NAVIGATION\n // rather than calling the app's own sign-out handler.\n //\n // Measured against production before this landed: calling the app's\n // handler did nothing at all. Apps wire it as\n // `onSignOut={() => { void signOutAction(); }}` — a server action invoked\n // outside a transition, so the `redirect()` inside it can never be\n // applied by the client and the tab just sits there. Building cross-tab\n // logout on that path made the whole feature a no-op.\n //\n // A hard navigation cannot fail that way, and it hands the decision to\n // the authority rather than guessing: the app's middleware sees a session\n // whose auth_time predates `politogy_logout_at`, treats it as revoked,\n // and redirects to Politogy ID. Server-side truth decides; the client\n // only forces the request. No re-broadcast — the signal that woke us is\n // already on record for every tab.\n watchLogout(() => {\n if (signingOutRef.current) return\n window.location.replace(window.location.href)\n }),\n [],\n )\n // Core-only inputs are read through this ONE gate. For every other app it is\n // undefined no matter what the app passed, so the \"an app cannot re-specify a\n // chrome element\" guarantee holds by construction, not by convention.\n const core = app === CORE_APP ? data?.core : undefined\n\n // Identity: chrome API wins, app claims are a fallback, an email is never a\n // name (that is what forms was rendering in the top right).\n const identity = useChromeIdentity({ userName: session.userName, email: session.email })\n const derivedUserMenu = useChromeUserMenu({\n userId: session.userId,\n userName: identity.userName,\n email: identity.email,\n })\n const userMenu = core?.userMenu ?? derivedUserMenu\n const footer = useChromeFooter(buildMeta ?? {})\n // A mode-scoped app (Campaign apps, Petitions) pins its pill to its own mode;\n // an unscoped app stays entitlement + user-selection driven. Either way no\n // app passes a mode — it is read from the registry (app identity).\n const appMode = meta.mode\n const derivedMode = useChromeMode(appMode ? { pinnedModeId: appMode } : undefined)\n // Core switches Mode client-side; every other app jumps to the mode's home.\n const mode = core?.mode ?? derivedMode\n\n const coreOrgs = chrome?.orgs ?? []\n const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null\n const navDisplay = chrome?.navDisplay ?? 'both'\n\n // The nav tree follows the app's OWN mode (registry), not the live pill\n // selection — so it is deterministic from app identity. Unscoped apps render\n // Relationship exactly as before. Cross-app links in the Campaign/Petition\n // trees carry the active org the way the switcher/account menu do.\n const navMode: StandardNavMode = appMode ?? 'relationship'\n const activeOrgId = chrome?.orgId ?? null\n const derivedNavItems = useMemo(\n () =>\n standardPolitogyNav({\n coreBaseUrl: POLITOGY_APPS.vrm.origin,\n icons: standardNavIcons,\n // An app that OWNS a standard nav entry (Contacts) nests its own routes\n // under it. This is routing data, not chrome configuration.\n contactsItem: data?.contactsItem,\n mode: navMode,\n activeOrgId,\n }),\n [data?.contactsItem, navMode, activeOrgId],\n )\n // Core supplies the canonical tree already decorated with its live gating\n // state (and with app-relative hrefs so its router handles them in-app).\n const navItems = core?.nav ?? derivedNavItems\n\n // Dashboard › Sub › Item, derived from the same tree the sidebar renders.\n // Core publishes its trail from the page itself, because a core page's\n // location is finer-grained than its nav row (e.g. a contact detail view).\n const derivedBreadcrumbs = useMemo(\n () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),\n [navItems, pathname, search, meta.origin],\n )\n const breadcrumbs = core ? core.page?.breadcrumbs : derivedBreadcrumbs\n\n return (\n <NavAppShell\n sidebar={{\n pathname,\n brand: {\n logoSrc: core?.brandLogoSrc ?? '/brand/logos/relationship-mode-logo.png',\n logoAlt: 'Politogy',\n badgeLabel: 'P',\n title: 'Politogy',\n },\n nav: {\n items:\n navDisplay === 'tabs'\n ? navItems.map(({ children: _sub, ...item }) => item)\n : navItems,\n },\n // The app's own workspace is NOT the organization — it is a per-app\n // bootstrap account with a different id in every app. Showing it while\n // the chrome API resolves flashed the wrong org on every load (\"not\n // connected to the account\"). Shimmer while in flight; only fall back\n // to the app label once the chrome lookup has actually failed.\n org: activeCoreOrg\n ? {\n name: activeCoreOrg.name,\n id: activeCoreOrg.id,\n logoUrl: activeCoreOrg.logoUrl,\n brandColor: activeCoreOrg.brandColor,\n organizations: coreOrgs,\n onSwitch: chrome?.setOrgId,\n }\n : chrome?.orgsLoading\n ? { name: null, id: null, loading: true }\n : { name: session.accountName, id: session.accountId },\n mode,\n footer,\n linkComponent,\n }}\n betaBanner={{}}\n contentWrapper={core?.contentWrapper}\n topBar={{\n // Core's pages publish their own identity; every other app is one\n // product surface and takes its registry title.\n title: core?.page?.title ?? meta.title,\n breadcrumbs,\n user: {\n name: identity.userName,\n email: identity.email,\n // The chrome API's avatar wins; the app may supply a fallback chain\n // (e.g. a stored memoji) for users with no uploaded photo.\n avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,\n role: session.role,\n },\n userMenu,\n onSignOut: handleSignOut,\n // The action cluster is IDENTICAL everywhere — core included. Page\n // actions sit BEFORE it, never inside it, so the Alerts bell keeps its\n // required position: farthest right, immediately before My Account.\n actions: (\n <>\n {core?.page?.actions}\n <ChromeTopBarActions />\n </>\n ),\n }}\n >\n {children}\n </NavAppShell>\n )\n}\n","// ---------------------------------------------------------------------------\n// App chrome wiring — the ONE shared implementation of the per-app glue that\n// previously lived as a copied ChromeWidgets.tsx in every Next.js feature app\n// (forms / surveys / polls / contacts). Copies drifted (org dropdown, badges);\n// this module makes that class of drift impossible: apps configure, never\n// re-implement (Reference/UI-Platform: consume via npm, never vendored).\n//\n// Provides:\n// <PolitogyAppChrome> — NavChromeProvider (same-origin proxy fetcher) +\n// chrome state (active core org, nav display) +\n// DevNotes provider, in one wrapper.\n// <ChromeTopBarActions> — the canonical top-bar cluster, in canonical order\n// (Fuel · Nav display · DevNotes · Support · Alerts\n// LAST, immediately left of My Account).\n// useChromeState / useChromeUserMenu / useIsSuperAdmin / useChromeFooter.\n//\n// The app still owns: its session (the /api/chrome proxy), page frames, build\n// metadata (env/commit/version), tab strips, and its own icons.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react'\nimport {\n NavChromeProvider,\n FuelAction,\n NotificationsAction,\n SupportAction,\n NavDisplayAction,\n TopBarWidgetSlot,\n useChromeOrgs,\n useThemePreference,\n type NavChromeFetcher,\n type NavDisplayMode,\n type NavOrgOption,\n type NavUserMenu,\n type NavFooter,\n type NavFooterLink,\n} from '../native/index'\nimport {\n DevNotesProvider,\n DevNotesMenu,\n DevNotesOverlay,\n DevNotesTaskListModal,\n createDevNotesClient,\n type DevNotesConfig,\n} from '@the-portland-company/devnotes'\nimport {\n FiActivity,\n FiBarChart2,\n FiBriefcase,\n FiClock,\n FiCreditCard,\n FiDollarSign,\n FiEdit3,\n FiFileText,\n FiFlag,\n FiGrid,\n FiHome,\n FiLayout,\n FiLifeBuoy,\n FiList,\n FiMail,\n FiMap,\n FiMapPin,\n FiMessageSquare,\n FiPhone,\n FiSend,\n FiSettings,\n FiShare2,\n FiTarget,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useChromeMe, useNavChromeFetcher } from '../native/index'\nimport {\n ACTIVE_ORG_QUERY_PARAM,\n isOrgId,\n resolveActiveOrg,\n writeActiveOrgCookie,\n} from '../active-org'\nimport type { StandardNavIconKey } from '../native/standardNav'\nimport type { NavBreadcrumb, NavIcon, NavItem, NavMode } from '../native/index'\n\n/**\n * The canonical icon map for standardPolitogyNav — ONE definition so every\n * app renders identical glyphs (including the Organization Settings branch:\n * briefcase / settings / creditCard / dollarSign / share).\n */\nexport const standardNavIcons: Record<StandardNavIconKey, NavIcon> = {\n home: FiHome,\n users: FiUsers,\n mail: FiMail,\n messageSquare: FiMessageSquare,\n layout: FiLayout,\n activity: FiActivity,\n lifeBuoy: FiLifeBuoy,\n grid: FiGrid,\n list: FiList,\n upload: FiUpload,\n edit: FiEdit3,\n fileText: FiFileText,\n send: FiSend,\n clock: FiClock,\n user: FiUser,\n barChart: FiBarChart2,\n briefcase: FiBriefcase,\n settings: FiSettings,\n creditCard: FiCreditCard,\n dollarSign: FiDollarSign,\n share: FiShare2,\n // Campaign / Petition mode slots.\n phone: FiPhone,\n map: FiMap,\n mapPin: FiMapPin,\n target: FiTarget,\n flag: FiFlag,\n}\n\nconst ORG_STORAGE_KEY = 'vrm.activeOrg'\n// The user's mode choice, shared by every app on the origin's localStorage.\nconst MODE_STORAGE_KEY = 'vrm.activeMode'\nconst NAV_DISPLAY_KEY = 'vrm.navDisplay'\nconst CORE_BASE_URL = 'https://app.politogyvrm.com'\n\nexport type ChromeIdentity = {\n userId: string\n userName: string | null\n email: string | null\n /** The APP's own role string (any casing) — feeds DevNotes admin gating. */\n role: string\n}\n\ntype ChromeState = {\n orgId: string | null\n setOrgId: (id: string) => void\n orgs: (NavOrgOption & { rawRole?: string | null })[]\n /**\n * The org list is still in flight. Consumers must render a skeleton rather\n * than fall back to the app's own workspace label, which is a DIFFERENT\n * account and flashes as the wrong organization.\n */\n orgsLoading: boolean\n navDisplay: NavDisplayMode\n setNavDisplay: (m: NavDisplayMode) => void\n}\n\nconst ChromeStateContext = createContext<ChromeState | null>(null)\n\nexport function useChromeState(): ChromeState | null {\n return useContext(ChromeStateContext)\n}\n\nexport type ControlledOrgState = {\n orgId: string | null\n onSwitch: (id: string) => void\n /** Override the org list (e.g. the app's own session-scoped list). */\n orgs?: (NavOrgOption & { rawRole?: string | null })[]\n}\n\nfunction ChromeStateProvider({\n controlledOrg,\n children,\n}: {\n controlledOrg?: ControlledOrgState\n children: ReactNode\n}) {\n const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>('both')\n\n // Boot: restore persisted choices. The cross-app active org (URL `?org=` or\n // the `.politogyvrm.com` cookie) wins over host-only localStorage so every\n // app + subdomain resolves the SAME org instead of each defaulting to its own\n // earliest-joined org.\n useEffect(() => {\n try {\n const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY)\n if (storedOrg) setOrgIdState(storedOrg)\n const storedNav = localStorage.getItem(NAV_DISPLAY_KEY)\n if (storedNav === 'menu' || storedNav === 'tabs' || storedNav === 'both') {\n setNavDisplayState(storedNav)\n }\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n // Once orgs resolve: validate the stored org, default to the first.\n useEffect(() => {\n if (!orgs || orgs.length === 0) return\n setOrgIdState((prev) => (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id))\n }, [orgs])\n\n const setOrgId = useCallback((id: string) => {\n setOrgIdState(id)\n try {\n localStorage.setItem(ORG_STORAGE_KEY, id)\n } catch {\n /* best effort */\n }\n // Remember the choice cross-subdomain so the next app opens on the same org.\n writeActiveOrgCookie(id)\n }, [])\n\n const setNavDisplay = useCallback((m: NavDisplayMode) => {\n setNavDisplayState(m)\n try {\n localStorage.setItem(NAV_DISPLAY_KEY, m)\n } catch {\n /* best effort */\n }\n }, [])\n\n const value = useMemo<ChromeState>(() => {\n // CONTROLLED mode (Contacts): the APP owns the active org + switching —\n // its switch does a full session re-scope. The shared widgets read the\n // app's state so there is exactly one org truth (unification directive).\n if (controlledOrg) {\n return {\n orgId: controlledOrg.orgId,\n setOrgId: controlledOrg.onSwitch,\n orgs:\n controlledOrg.orgs ??\n (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n orgsLoading: false,\n navDisplay,\n setNavDisplay,\n }\n }\n return {\n orgId,\n setOrgId,\n orgs: (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n // Loading until the fetch settles. On error we stop loading so the frame\n // falls back to the app label rather than shimmering forever.\n orgsLoading: orgsFetching && !orgsError && !orgs,\n navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay])\n\n // Keep the shared cookie + address-bar `?org=` in sync with the ACTIVE org —\n // whichever path owns it, controlled (Contacts) or uncontrolled (feature\n // apps) — so every app + tab resolves the same org and every URL is\n // bookmarkable/shareable with its org context. Idempotent; no-op on server.\n const activeOrgId = value.orgId\n useEffect(() => {\n if (!isOrgId(activeOrgId)) return\n writeActiveOrgCookie(activeOrgId as string)\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {\n url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId as string)\n window.history.replaceState(window.history.state, '', url.toString())\n }\n } catch {\n /* SSR / no window */\n }\n }, [activeOrgId])\n\n return <ChromeStateContext.Provider value={value}>{children}</ChromeStateContext.Provider>\n}\n\nexport type ChromeDevNotesConfig = {\n /** Override the DevNotes API base (default: `${proxyBasePath}/devnotes`). */\n basePath?: string\n /** Client-side token supplier (default: empty — same-origin proxy injects). */\n getAuthToken?: () => Promise<string> | string\n /** Custom fetch (e.g. credentials:'include' against a cross-origin API). */\n fetch?: typeof globalThis.fetch\n /**\n * The app already mounts its own `<DevNotesProvider>` ABOVE this chrome, so\n * the chrome must not mount a second one (VRM core, whose provider carries\n * capabilities the generic client has no equivalent for — user stories, its\n * own role gating). The working SURFACE still comes from here, so the\n * launcher, the overlay and the task modal behave identically everywhere;\n * only the data adapter is the app's.\n */\n providedByApp?: boolean\n}\n\nfunction AppDevNotes({\n storagePrefix,\n proxyBasePath,\n devNotes,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath: string\n devNotes?: ChromeDevNotesConfig\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const adapter = useMemo(\n () =>\n createDevNotesClient({\n basePath: devNotes?.basePath ?? `${proxyBasePath}/devnotes`,\n // Default: the same-origin proxy attaches the session's Bearer.\n getAuthToken: devNotes?.getAuthToken ?? (async () => ''),\n ...(devNotes?.fetch ? { fetch: devNotes.fetch } : {}),\n }),\n [proxyBasePath, devNotes?.basePath, devNotes?.getAuthToken, devNotes?.fetch],\n )\n\n const user = useMemo(\n () => ({\n id: identity.userId,\n email: identity.email ?? '',\n fullName: identity.userName ?? undefined,\n }),\n [identity.userId, identity.email, identity.userName],\n )\n\n const config = useMemo<DevNotesConfig>(\n () => ({\n storagePrefix,\n role: identity.role.toLowerCase() === 'admin' ? 'admin' : 'reporter',\n disableAi: true,\n getPagePath: () => window.location.pathname,\n }),\n [storagePrefix, identity.role],\n )\n\n // The app brought its own provider — mount only the surface, so a second\n // DevNotesProvider never shadows the app's adapter.\n if (devNotes?.providedByApp) return <DevNotesSurface>{children}</DevNotesSurface>\n\n return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n <DevNotesSurface>{children}</DevNotesSurface>\n </DevNotesProvider>\n )\n}\n\n// \"View All Tasks\" is triggered from the menu in the top bar but the modal has\n// to render at the chrome ROOT, or it would be trapped inside the top bar's\n// stacking/blur context. The menu publishes the request through this context.\nconst DevNotesTasksContext = createContext<{ ready: boolean; openTasks: () => void }>({\n ready: false,\n openTasks: () => {},\n})\n\n/**\n * The DevNotes working surface, mounted once at the chrome root.\n *\n * `<DevNotesMenu>` only TOGGLES creation mode — `<DevNotesOverlay>` is what\n * renders the click-to-place capture layer, the report dots and the report\n * form. The shell shipped the provider and the menu but never the overlay, so\n * on every app that used this chrome (forms / surveys / polls) \"Create Task\"\n * closed the menu and did nothing at all: the mode flipped on with nothing\n * listening (HITL bug report 2026-07-23). Contacts didn't hit it only because\n * it still had its own hand-rolled surface.\n */\nfunction DevNotesSurface({ children }: { children: ReactNode }) {\n const [tasksOpen, setTasksOpen] = useState(false)\n const openTasks = useCallback(() => setTasksOpen(true), [])\n const tasksCtx = useMemo(() => ({ ready: true, openTasks }), [openTasks])\n\n return (\n <DevNotesTasksContext.Provider value={tasksCtx}>\n {children}\n {/* Mounted only while open: the modal loads data on mount regardless of\n its `open` prop, so keeping it unmounted avoids fetching every user's\n tasks before they ask for them. */}\n {tasksOpen ? <DevNotesTaskListModal open onClose={() => setTasksOpen(false)} /> : null}\n <DevNotesOverlay />\n </DevNotesTasksContext.Provider>\n )\n}\n\n/**\n * The app-chrome root. Wrap the authenticated frame once:\n *\n * <PolitogyAppChrome storagePrefix=\"forms\" identity={{userId, userName, email, role}}>\n * <ShellFrame …/>\n * </PolitogyAppChrome>\n */\nexport function PolitogyAppChrome({\n storagePrefix,\n proxyBasePath = '/api/chrome',\n fetchJson: fetchJsonOverride,\n devNotes,\n org,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath?: string\n /**\n * Override the same-origin proxy fetcher for apps that talk to the chrome\n * API directly with their own auth (Contacts sends a client-side Bearer).\n * Paths stay relative ('orgs', 'me', …) — the override owns base + auth.\n */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for direct-auth apps (Contacts). */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state — the app owns active-org + switching (Contacts). */\n org?: ControlledOrgState\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const proxyFetchJson = useMemo<NavChromeFetcher>(\n () =>\n async (path, init) => {\n const res = await fetch(`${proxyBasePath}/${path}`, {\n method: init?.method ?? 'GET',\n headers: init?.headers,\n body: init?.body,\n })\n if (!res.ok) throw new Error(`chrome ${path}: ${res.status}`)\n if (res.status === 204) return null\n const text = await res.text()\n return text ? JSON.parse(text) : null\n },\n [proxyBasePath],\n )\n const fetchJson = fetchJsonOverride ?? proxyFetchJson\n\n return (\n <NavChromeProvider fetchJson={fetchJson}>\n <ChromeStateProvider controlledOrg={org}>\n <AppDevNotes\n storagePrefix={storagePrefix}\n proxyBasePath={proxyBasePath}\n devNotes={devNotes}\n identity={identity}\n >\n {children}\n </AppDevNotes>\n </ChromeStateProvider>\n </NavChromeProvider>\n )\n}\n\n/** The standard top-bar action cluster. The Alerts bell renders LAST so it\n * sits farthest right, immediately left of the My Account badge. */\nexport function ChromeTopBarActions() {\n const state = useChromeState()\n if (!state) return null\n return (\n <>\n <FuelAction orgId={state.orgId} />\n <NavDisplayAction value={state.navDisplay} onChange={state.setNavDisplay} />\n <TopBarWidgetSlot\n title=\"DevNotes\"\n description=\"Capture bugs and notes, filed straight to Forge.\"\n >\n <ChromeDevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction orgId={state.orgId} />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\n}\n\n/**\n * The DevNotes launcher. Renders nothing until the surface above is mounted, so\n * it never touches DevNotes context before the provider exists, and forwards\n * \"View All Tasks\" to the root-level modal.\n */\nfunction ChromeDevNotesMenu() {\n const { ready, openTasks } = useContext(DevNotesTasksContext)\n if (!ready) return null\n return <DevNotesMenu position=\"top-right\" onViewTasks={openTasks} />\n}\n\n/** True when any core-org membership carries a super-admin tier. */\nexport function useIsSuperAdmin(): boolean {\n const state = useChromeState()\n return (state?.orgs ?? []).some((o) => /super/i.test(o.rawRole ?? ''))\n}\n\nconst FOOTER_LINKS: NavFooterLink[] = [\n { label: 'Privacy', href: `${CORE_BASE_URL}/privacy-policy`, external: true },\n { label: 'Terms', href: `${CORE_BASE_URL}/terms-and-conditions`, external: true },\n {\n label: 'Subscriptions',\n href: 'https://politogyvrm.com/subscription-cancellation-policy',\n external: true,\n },\n]\n\n/**\n * Sidebar footer: legal links for everyone; build metadata (env / commit /\n * version / date / author) ONLY for Super Administrators\n * (Reference/UI-Platform). Pass the app's build-time metadata in.\n */\nexport function useChromeFooter(meta: {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}): NavFooter {\n const isSuperAdmin = useIsSuperAdmin()\n return useMemo<NavFooter>(\n () => (isSuperAdmin ? { links: FOOTER_LINKS, ...meta } : { links: FOOTER_LINKS }),\n [isSuperAdmin, meta.envLabel, meta.version, meta.commit, meta.commitDate, meta.commitAuthor],\n )\n}\n\n// ---------------------------------------------------------------------------\n// Identity — resolved by the PACKAGE, from the chrome API.\n//\n// Apps used to pass a display name straight from their own session claims, and\n// forms passed `user.name ?? user.email` — so a user with no name set had their\n// EMAIL rendered as their name, and the chrome API's real profile name could\n// never win (HITL 2026-07-23). The chrome API is the authority; what the app\n// passes is a pre-hydration fallback only, and an email is never a name.\n// ---------------------------------------------------------------------------\n\nexport function useChromeIdentity(fallback?: {\n userName?: string | null\n email?: string | null\n}): ResolvedIdentity {\n const { me, loaded } = useChromeMe()\n return resolveIdentity({\n apiName: me?.userName,\n fallbackName: fallback?.userName,\n email: fallback?.email,\n avatarUrl: me?.avatarUrl,\n loaded,\n })\n}\n\n// Breadcrumbs live in ../lib/breadcrumbs (pure, dependency-free) and are\n// re-exported here so apps keep a single import surface.\nexport { deriveBreadcrumbs } from '../lib/breadcrumbs'\nexport { looksLikeEmail, resolveIdentity, type ResolvedIdentity } from '../lib/identity'\nimport { resolveIdentity, type ResolvedIdentity } from '../lib/identity'\n\n// ---------------------------------------------------------------------------\n// Breadcrumbs — derived by the PACKAGE from the nav tree + the current path.\n//\n// Every app hand-built its own trail, so they disagreed: forms rendered\n// \"Forms › Dashboard\" while polls rendered \"Polls\" under a \"Polls\" title\n// (\"Polls / Polls\"). The standard is Dashboard › Sub › Item (HITL 2026-07-23),\n// derived from the same nav tree the sidebar renders, so no app can drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The My Account menu config — profile/org links into VRM core, real roles\n * from the user's core organizations, the signed-in account, and the shared\n * Light/System/Dark theme control (auth-derived, cross-app).\n */\nexport function useChromeUserMenu({\n userId,\n userName,\n email,\n}: {\n userId: string\n userName: string | null\n email: string | null\n}): NavUserMenu {\n const state = useChromeState()\n const theme = useThemePreference()\n const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null\n const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : ''\n return {\n // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).\n myProfileHref: `${CORE_BASE_URL}/my-profile`,\n organizationSettingsHref: `${CORE_BASE_URL}/organization-settings${orgSuffix}`,\n roles: (state?.orgs ?? [])\n .filter((o) => o.rawRole || o.role)\n .map((o) => ({ role: (o.rawRole ?? o.role) as string, orgName: o.name })),\n accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],\n addAccountHref: `${CORE_BASE_URL}/my-profile#accounts`,\n theme: { value: theme.value, onChange: theme.setValue },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mode selector — entitlement-driven (HITL 2026-07-23: an org with multiple\n// active modes selects them from the left nav). Entitlements come from the\n// chrome API: GET me with the active org header → modes booleans\n// {relationship, campaign, petition, polling}. Selecting a mode navigates to\n// that mode's VRM-core dashboard carrying the active org.\n// ---------------------------------------------------------------------------\n\nconst MODE_DEFS: {\n id: string\n label: string\n icon: NavIcon\n /** Absolute destination, or a VRM-core path prefixed with CORE_BASE_URL. */\n href: string\n orgQuery: boolean\n}[] = [\n {\n id: 'relationship',\n label: 'Relationship Mode',\n icon: FiUsers,\n href: `${CORE_BASE_URL}/relationship/dashboard`,\n orgQuery: true,\n },\n {\n id: 'campaign',\n label: 'Campaign Mode',\n icon: FiBarChart2,\n href: `${CORE_BASE_URL}/campaign/dashboard`,\n orgQuery: true,\n },\n {\n // Petition Mode lives in its own standalone app on its own subdomain — the\n // petitions repo deploys production to petition-mode.politogyvrm.com.\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petition-mode.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\ntype ModeId = 'relationship' | 'campaign' | 'petition'\n\ntype UseChromeModeArg =\n | ModeId\n | {\n /** Pre-entitlement fallback for first paint (default 'relationship'). */\n fallbackModeId?: ModeId\n /**\n * Registry-pinned mode for a MODE-SCOPED app (e.g. the Campaign apps\n * a2p / phonebank / canvass / signs, from `POLITOGY_APPS[app].mode`). The\n * pill DISPLAYS this mode regardless of the cross-app selection, so a\n * campaign app never shows a Relationship pill. The switcher still appears\n * when the active org is entitled to more than one mode, so the user can\n * jump out to another mode's home.\n */\n pinnedModeId?: ModeId\n }\n\n/**\n * The active mode pill / selector.\n *\n * For an UNSCOPED app the current mode is the USER'S OWN SELECTION (persisted\n * cross-app in localStorage under `vrm.activeMode`), narrowed to what the active\n * org is entitled to — never a literal an app hardcodes. Apps used to call\n * `useChromeMode('relationship')`, which pinned the pill to Relationship even\n * for an org entitled to Campaign or Petition (HITL 2026-07-23); the string\n * argument now only supplies the pre-entitlement fallback for first paint.\n *\n * A MODE-SCOPED app passes `{ pinnedModeId }` so its pill displays that mode\n * (its own mode from the app registry) rather than the cross-app selection —\n * while a multi-mode org still gets the switcher to leave.\n */\nexport function useChromeMode(arg: UseChromeModeArg = {}): NavMode {\n const { fallbackModeId = 'relationship', pinnedModeId } =\n typeof arg === 'string' ? { fallbackModeId: arg, pinnedModeId: undefined } : arg\n const ctx = useContext(ChromeStateContext)\n const nav = useNavChromeFetcher()\n const orgId = ctx?.orgId ?? null\n const [entitled, setEntitled] = useState<Record<string, boolean> | null>(null)\n const [selectedModeId, setSelectedModeId] = useState<string | null>(null)\n\n // Restore the user's last mode choice (shared key across every app).\n useEffect(() => {\n try {\n const stored = localStorage.getItem(MODE_STORAGE_KEY)\n if (stored && MODE_DEFS.some((m) => m.id === stored)) setSelectedModeId(stored)\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n useEffect(() => {\n if (!nav || !orgId) return\n let cancelled = false\n nav('me', { headers: { 'x-contacts-org': orgId } })\n .then((data) => {\n if (cancelled) return\n const modes = (data as { modes?: Record<string, boolean> | null })?.modes ?? null\n setEntitled(modes)\n })\n .catch(() => {\n /* entitlements unavailable — single-mode pill stands */\n })\n return () => {\n cancelled = true\n }\n }, [nav, orgId])\n\n const options = MODE_DEFS.filter((m) => entitled?.[m.id])\n // Resolution order: a registry pin (mode-scoped app) wins outright → then the\n // user's selection (if the org is still entitled to it) → the first entitled\n // mode → the app's fallback. Entitlements are async, so before they land the\n // pin/fallback holds the pill's shape (no layout shift).\n const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null\n const selected = selectedModeId\n ? options.find((m) => m.id === selectedModeId) ?? null\n : null\n const current =\n pinned ??\n selected ??\n options[0] ??\n MODE_DEFS.find((m) => m.id === fallbackModeId) ??\n MODE_DEFS[0]\n const suffix = orgId ? `?org=${orgId}` : ''\n\n return {\n label: current.label,\n icon: current.icon,\n options:\n options.length > 1\n ? options.map((o) => ({ id: o.id, label: o.label, icon: o.icon }))\n : undefined,\n onSelect:\n options.length > 1\n ? (id: string) => {\n const def = MODE_DEFS.find((m) => m.id === id)\n if (!def) return\n // Remember the choice BEFORE navigating so the destination app —\n // and every app after it — opens in the mode the user picked.\n setSelectedModeId(id)\n try {\n localStorage.setItem(MODE_STORAGE_KEY, id)\n } catch {\n /* storage unavailable */\n }\n const sep = def.href.includes('?') ? '&' : '?'\n window.location.assign(\n def.orgQuery && suffix ? `${def.href}${sep}${suffix.slice(1)}` : def.href,\n )\n }\n : undefined,\n }\n}\n\n// The one-component app frame (see ./frame.tsx). Re-exported here so apps have\n// a single import surface: `@the-portland-company/shell/app-chrome`.\nexport {\n PolitogyAppFrame,\n POLITOGY_APPS,\n type PolitogyAppKey,\n type PolitogyAppMode,\n type PolitogyAppSession,\n type PolitogyBuildMeta,\n type PolitogyFrameData,\n} from './frame'\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/breadcrumbs.ts","../src/lib/identity.ts","../src/app-chrome/frame.tsx","../src/app-chrome/index.tsx"],"names":["trail","useEffect","useCallback","useMemo","jsx","jsxs","Fragment"],"mappings":"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","file":"app-chrome.js","sourcesContent":["// Breadcrumb derivation — pure, dependency-free logic (no React, no DevNotes),\n// so it is importable from a server module and testable in isolation.\n//\n// The standard is Dashboard > Sub > Item (HITL 2026-07-23), derived from the\n// same nav tree the sidebar renders. Apps previously hand-built their own\n// trails and disagreed: forms rendered \"Forms > Dashboard\" while polls rendered\n// a lone \"Polls\" crumb under a \"Polls\" title (\"Polls / Polls\").\n\nimport type { NavBreadcrumb, NavItem } from '../native/index'\n\nexport function deriveBreadcrumbs(\n items: NavItem[],\n pathname: string,\n opts?: { search?: string; origin?: string },\n): NavBreadcrumb[] {\n const search = opts?.search ?? (typeof window !== 'undefined' ? window.location.search : '')\n const origin = opts?.origin ?? (typeof window !== 'undefined' ? window.location.origin : '')\n\n // Same-origin comparison: cross-app hrefs are absolute, local ones relative.\n const matches = (href?: string): boolean => {\n if (!href) return false\n let path = href\n let query = ''\n if (/^https?:\\/\\//i.test(href)) {\n try {\n const u = new URL(href)\n if (origin && u.origin !== origin) return false\n path = u.pathname\n query = u.search\n } catch {\n return false\n }\n } else {\n const qi = href.indexOf('?')\n if (qi >= 0) {\n path = href.slice(0, qi) || '/'\n query = href.slice(qi)\n }\n }\n if (path !== pathname) return false\n if (!query) return true\n const want = new URLSearchParams(query)\n const have = new URLSearchParams(search)\n for (const [k, v] of want) if (have.get(k) !== v) return false\n return true\n }\n\n // Depth-first walk for the deepest item owning the current route.\n const walk = (nodes: NavItem[], trail: NavItem[]): NavItem[] | null => {\n for (const node of nodes) {\n const here = [...trail, node]\n if (node.children?.length) {\n const deeper = walk(node.children, here)\n if (deeper) return deeper\n }\n if (matches(node.href)) return here\n }\n return null\n }\n\n const trail = walk(items, [])\n // Always anchored at Dashboard; the last crumb is the current location and\n // carries no href (the shell marks it aria-current=\"page\").\n const dashboard = items.find((i) => i.label === 'Dashboard')\n const crumbs: NavBreadcrumb[] = [{ label: 'Dashboard', href: dashboard?.href }]\n if (!trail) return crumbs\n for (const node of trail) {\n if (node.label === 'Dashboard') continue\n crumbs.push({ label: node.label, href: node.href })\n }\n const last = crumbs[crumbs.length - 1]\n if (last) delete (last as { href?: string }).href\n return crumbs\n}\n","// Identity resolution — pure logic, shared by the chrome hook.\n//\n// The chrome API (VRM-core profile) is the AUTHORITY for a user's display name;\n// what an app knows from its own session token is a pre-hydration fallback. An\n// email is never a name: forms passed `user.name ?? user.email`, so a user with\n// no name set had their email rendered as their name in the top bar and the\n// shell's \"add your name\" affordance never fired (HITL 2026-07-23).\n\nexport type ResolvedIdentity = {\n userName: string | null\n email: string | null\n avatarUrl: string | null\n loaded: boolean\n}\n\n/** True when a \"name\" is really just an email address. */\nexport function looksLikeEmail(value: string | null | undefined): boolean {\n return typeof value === 'string' && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value.trim())\n}\n\nexport function resolveIdentity(input: {\n apiName?: string | null\n fallbackName?: string | null\n email?: string | null\n avatarUrl?: string | null\n loaded?: boolean\n}): ResolvedIdentity {\n const apiName = looksLikeEmail(input.apiName) ? null : input.apiName ?? null\n const fallbackName = looksLikeEmail(input.fallbackName) ? null : input.fallbackName ?? null\n return {\n userName: apiName ?? fallbackName,\n email: input.email ?? null,\n avatarUrl: input.avatarUrl ?? null,\n loaded: input.loaded ?? false,\n }\n}\n","// ---------------------------------------------------------------------------\n// PolitogyAppFrame — ONE component that renders the entire app frame.\n//\n// Why this exists (HITL directive 2026-07-23): shipping the same npm package at\n// the same version is necessary but NOT sufficient. The package exposed\n// PRIMITIVES (NavAppShell + a dozen hooks) and every app hand-assembled them in\n// its own ~150-line Shell.tsx. Identical inputs were never guaranteed, so the\n// outputs diverged — measured on production: forms rendered its user's EMAIL as\n// their name, polls' breadcrumb read \"Polls / Polls\", forms and surveys pinned\n// the mode pill to a hardcoded \"relationship\", and each app declared its own\n// chrome CSS variables (surveys' accent was #C8102E where everyone else was\n// #E63946).\n//\n// The fix is to stop exporting assembly as an app responsibility. An app now\n// renders:\n//\n// <PolitogyAppFrame app=\"forms\" pathname={pathname} linkComponent={Link}\n// session={{ userId, email, role, accountName, accountId }}\n// onSignOut={signOut}>\n// {children}\n// </PolitogyAppFrame>\n//\n// and passes NOTHING about the chrome: no nav, no breadcrumbs, no title, no\n// mode, no user menu, no footer, no theme. Those are derived here, identically,\n// for every app. Anything an app cannot pass is something it cannot drift on.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport { useCallback, useEffect, useMemo, useRef, type ReactNode } from 'react'\n\nimport { broadcastLogout, watchLogout } from '../logout-signal'\nimport {\n NavAppShell,\n standardPolitogyNav,\n type NavLinkComponent,\n} from '../native/index'\nimport {\n ChromeTopBarActions,\n PolitogyAppChrome,\n standardNavIcons,\n useChromeFooter,\n useChromeIdentity,\n useChromeMode,\n useChromeState,\n useChromeUserMenu,\n type ChromeDevNotesConfig,\n type ControlledOrgState,\n} from './index'\nimport type {\n NavBreadcrumb,\n NavChromeFetcher,\n NavItem,\n NavMode,\n NavUserMenu,\n} from '../native/index'\nimport type { StandardNavMode } from '../native/standardNav'\nimport { deriveBreadcrumbs } from '../lib/breadcrumbs'\n\n/**\n * The core app — the one that OWNS the canonical nav tree and routes in-app.\n * `PolitogyFrameData.core` is honored for this key and no other.\n */\nconst CORE_APP: PolitogyAppKey = 'vrm'\n\n/**\n * Template A convention: every feature app exposes GET /auth/signout, which\n * clears its session cookie and 302s to the IdP's end_session endpoint. Apps\n * that route sign-out elsewhere (core: /api/auth/logout) pass `signOutUrl`.\n */\nconst DEFAULT_SIGN_OUT_URL = '/auth/signout'\n\n/** Every app that renders the shared chrome. Keyed by its subdomain. */\nexport type PolitogyAppKey =\n | 'vrm'\n | 'contacts'\n | 'forms'\n | 'surveys'\n | 'polls'\n | 'petitions'\n // Campaign Mode standalone apps.\n | 'a2p'\n | 'phonebank'\n | 'canvass'\n | 'signs'\n\n/**\n * The operating Mode an app is scoped to, if any. It drives two things,\n * BOTH derived here from app identity so no app passes chrome config:\n * 1. the standardPolitogyNav tree the sidebar renders, and\n * 2. the static Mode pill the app shows (a campaign app shows a Campaign\n * pill, never Relationship).\n * An UNSCOPED app (no `mode`) keeps its prior behavior exactly: the\n * Relationship tree + an entitlement-driven Mode pill.\n */\nexport type PolitogyAppMode = StandardNavMode\n\n/**\n * The per-app registry — the ONLY place app identity lives. A new app adds a\n * row here and inherits every chrome behavior; it never re-derives one.\n */\nexport const POLITOGY_APPS: Record<\n PolitogyAppKey,\n { title: string; origin: string; storagePrefix: string; mode?: PolitogyAppMode }\n> = {\n vrm: { title: 'Politogy VRM', origin: 'https://app.politogyvrm.com', storagePrefix: 'vrm' },\n contacts: { title: 'Contacts', origin: 'https://contacts.politogyvrm.com', storagePrefix: 'contacts' },\n forms: { title: 'Forms', origin: 'https://forms.politogyvrm.com', storagePrefix: 'forms' },\n surveys: { title: 'Surveys', origin: 'https://surveys.politogyvrm.com', storagePrefix: 'surveys' },\n polls: { title: 'Polls', origin: 'https://polls.politogyvrm.com', storagePrefix: 'polls' },\n petitions: {\n title: 'Petitions',\n origin: 'https://petition-mode.politogyvrm.com',\n storagePrefix: 'petitions',\n mode: 'petition',\n },\n // Campaign Mode apps — each scoped to Campaign, so they render the Campaign\n // tree and a static Campaign pill. Not yet deployed; hidden from the Campaign\n // nav by CAMPAIGN_FEATURE_AVAILABILITY until their launch flips the flag.\n a2p: { title: 'A2P Messaging', origin: 'https://a2p.politogyvrm.com', storagePrefix: 'a2p', mode: 'campaign' },\n phonebank: { title: 'Phone Banking', origin: 'https://phonebank.politogyvrm.com', storagePrefix: 'phonebank', mode: 'campaign' },\n canvass: { title: 'Field Canvassing', origin: 'https://canvass.politogyvrm.com', storagePrefix: 'canvass', mode: 'campaign' },\n signs: { title: 'Sign Mapping', origin: 'https://signs.politogyvrm.com', storagePrefix: 'signs', mode: 'campaign' },\n}\n\nexport type PolitogyAppSession = {\n /** Stable IdP subject (`sub`). */\n userId: string\n email: string | null\n /** The app's own role string — feeds DevNotes admin gating. */\n role: string\n /** The app's own workspace label, used only until core orgs resolve. */\n accountName: string\n accountId: string\n /**\n * Pre-hydration display name ONLY. The chrome API is the authority; an email\n * passed here is discarded rather than rendered as a name.\n */\n userName?: string | null\n}\n\n/**\n * Optional DATA plumbing for apps whose session/auth differs — never styling.\n *\n * Everything here is about WHERE data comes from (which fetcher, which org\n * state, which routes an app owns), not what the chrome looks like. Adding a\n * visual option to this type would re-open the drift this component exists to\n * close.\n */\nexport type PolitogyFrameData = {\n /** Chrome-API fetcher override for apps that authenticate client-side. */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for those same direct-auth apps. */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state for apps that own active-org + switching. */\n org?: ControlledOrgState\n /** App-owned children for a standard nav entry this app owns (Contacts). */\n contactsItem?: { href?: string; children?: NavItem[] }\n /** Avatar fallback when the profile has no uploaded photo (memoji, default). */\n fallbackAvatarUrl?: string | null\n /**\n * CORE-ONLY frame inputs. **Honored only when `app` is the core app (`vrm`)\n * — silently ignored for every other app**, which is what keeps the\n * no-drift guarantee intact: a feature app cannot re-specify a chrome\n * element even by passing this, so the props are inert for it.\n *\n * Core is genuinely different, and pretending otherwise would have meant\n * REMOVING shipped behavior from app.politogyvrm.com to satisfy packaging —\n * which Reference/UI-Platform forbids (\"migration may not drop a shipped\n * element … extend the shell first, then migrate\"). Specifically, core:\n * - owns the canonical nav tree AND decorates it with live entitlement\n * state a static tree cannot express (10DLC gating, contact/group\n * counts, org-lock),\n * - routes in-app (react-router), so its rows must be app-relative paths,\n * not the absolute cross-app URLs `standardPolitogyNav` emits,\n * - publishes per-page title/breadcrumbs from 66 page components,\n * - switches Mode client-side rather than by full-page navigation,\n * - owns real linked-account switching (with its own modals).\n *\n * Everything here is still DATA or app-owned STATE. There is no styling\n * input, no ordering input, and no way to change the top-bar action cluster\n * — those stay identical for every app, core included.\n */\n core?: PolitogyCoreFrame\n}\n\n/** See `PolitogyFrameData.core` — core app (`vrm`) only. */\nexport type PolitogyCoreFrame = {\n /** The canonical nav tree, decorated with core's live entitlement state. */\n nav?: NavItem[]\n /** Per-page identity published by core's own pages. */\n page?: { title?: string; breadcrumbs?: NavBreadcrumb[]; actions?: ReactNode }\n /** Client-side Mode switching (core navigates in-app, not cross-origin). */\n mode?: NavMode\n /** Real linked-account switching + core's theme persistence. */\n userMenu?: NavUserMenu\n /** Mode-specific brand wordmark (core swaps it per operating Mode). */\n brandLogoSrc?: string\n /** Core wraps every page in PageContainer, which emits its own <main>. */\n contentWrapper?: 'padded' | 'bare'\n}\n\nexport type PolitogyBuildMeta = {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}\n\nexport function PolitogyAppFrame({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl = DEFAULT_SIGN_OUT_URL,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n /** Current path from the app's router (Next: usePathname()). */\n pathname: string\n /** Current query string, when the app routes by query (forms' ?tab=). */\n search?: string\n /** The app's routing primitive (Next: <Link>). */\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n /**\n * The app's sign-out ROUTE — a GET endpoint that clears the session cookie\n * and redirects to RP-initiated logout at the IdP. Defaults to the Template A\n * convention; core overrides it. This, not `onSignOut`, is what actually\n * signs the user out (see handleSignOut).\n */\n signOutUrl?: string\n buildMeta?: PolitogyBuildMeta\n /** Optional DATA plumbing (never styling) — see PolitogyFrameData. */\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n return (\n <PolitogyAppChrome\n storagePrefix={meta.storagePrefix}\n fetchJson={data?.fetchJson}\n devNotes={data?.devNotes}\n org={data?.org}\n identity={{\n userId: session.userId,\n userName: session.userName ?? null,\n email: session.email,\n role: session.role,\n }}\n >\n <FrameBody\n app={app}\n pathname={pathname}\n search={search}\n linkComponent={linkComponent}\n session={session}\n onSignOut={onSignOut}\n signOutUrl={signOutUrl}\n buildMeta={buildMeta}\n data={data}\n >\n {children}\n </FrameBody>\n </PolitogyAppChrome>\n )\n}\n\nfunction FrameBody({\n app,\n pathname,\n search,\n linkComponent,\n session,\n onSignOut,\n signOutUrl,\n buildMeta,\n data,\n children,\n}: {\n app: PolitogyAppKey\n pathname: string\n search?: string\n linkComponent: NavLinkComponent\n session: PolitogyAppSession\n onSignOut: () => void\n signOutUrl: string\n buildMeta?: PolitogyBuildMeta\n data?: PolitogyFrameData\n children: ReactNode\n}) {\n const meta = POLITOGY_APPS[app]\n const chrome = useChromeState()\n\n // Sign-out is a PLATFORM event, not an app event. Each app holds its own\n // host-only session cookie with a long sliding window, so without this a user\n // who signed out in one tab stayed fully signed in in every other open tab\n // and on every other subdomain. Announce the sign-out to all of them, then\n // run the app's own sign-out.\n //\n // Every app reaches sign-out through this frame, so wiring it here covers the\n // whole estate without an app having to opt in — the same guarantee the rest\n // of the chrome relies on.\n const signOutRef = useRef(onSignOut)\n signOutRef.current = onSignOut\n // This tab started the sign-out, so its own watcher must not also fire and\n // run the app's sign-out a second time concurrently.\n const signingOutRef = useRef(false)\n\n const handleSignOut = useCallback(() => {\n signingOutRef.current = true\n broadcastLogout()\n // Navigate to the app's sign-out ROUTE. Apps also pass an onSignOut\n // handler, but it cannot be relied on: they wire it as\n // `onSignOut={() => { void signOutAction(); }}`, and a server action\n // invoked outside a transition can never have its redirect() applied by\n // the client. Measured against production 2026-07-25: clicking Sign out\n // left the user signed in, and an app they had never opened authenticated\n // with no credentials — the IdP session was still alive because the\n // browser never reached its end-session endpoint.\n //\n // A plain navigation to a GET route cannot fail that way: the route drops\n // the session cookie and 302s to RP-initiated logout at the IdP.\n signOutRef.current()\n window.location.assign(signOutUrl)\n }, [signOutUrl])\n\n useEffect(\n () =>\n // A tab that learns of a sign-out elsewhere ends its own session for\n // real, by navigating to the same sign-out route.\n //\n // Measured against production before this landed: calling the app's\n // handler did nothing at all. Apps wire it as\n // `onSignOut={() => { void signOutAction(); }}` — a server action invoked\n // outside a transition, so the `redirect()` inside it can never be\n // applied by the client and the tab just sits there. Building cross-tab\n // logout on that path made the whole feature a no-op.\n //\n // A hard navigation cannot fail that way, and it hands the decision to\n // the authority rather than guessing: the app's middleware sees a session\n // whose auth_time predates `politogy_logout_at`, treats it as revoked,\n // and redirects to Politogy ID. Server-side truth decides; the client\n // only forces the request. No re-broadcast — the signal that woke us is\n // already on record for every tab.\n watchLogout(() => {\n if (signingOutRef.current) return\n window.location.assign(signOutUrl)\n }),\n [signOutUrl],\n )\n // Core-only inputs are read through this ONE gate. For every other app it is\n // undefined no matter what the app passed, so the \"an app cannot re-specify a\n // chrome element\" guarantee holds by construction, not by convention.\n const core = app === CORE_APP ? data?.core : undefined\n\n // Identity: chrome API wins, app claims are a fallback, an email is never a\n // name (that is what forms was rendering in the top right).\n const identity = useChromeIdentity({ userName: session.userName, email: session.email })\n const derivedUserMenu = useChromeUserMenu({\n userId: session.userId,\n userName: identity.userName,\n email: identity.email,\n })\n const userMenu = core?.userMenu ?? derivedUserMenu\n const footer = useChromeFooter(buildMeta ?? {})\n // A mode-scoped app (Campaign apps, Petitions) pins its pill to its own mode;\n // an unscoped app stays entitlement + user-selection driven. Either way no\n // app passes a mode — it is read from the registry (app identity).\n const appMode = meta.mode\n const derivedMode = useChromeMode(appMode ? { pinnedModeId: appMode } : undefined)\n // Core switches Mode client-side; every other app jumps to the mode's home.\n const mode = core?.mode ?? derivedMode\n\n const coreOrgs = chrome?.orgs ?? []\n const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null\n const navDisplay = chrome?.navDisplay ?? 'both'\n\n // The nav tree follows the app's OWN mode (registry), not the live pill\n // selection — so it is deterministic from app identity. Unscoped apps render\n // Relationship exactly as before. Cross-app links in the Campaign/Petition\n // trees carry the active org the way the switcher/account menu do.\n const navMode: StandardNavMode = appMode ?? 'relationship'\n const activeOrgId = chrome?.orgId ?? null\n const derivedNavItems = useMemo(\n () =>\n standardPolitogyNav({\n coreBaseUrl: POLITOGY_APPS.vrm.origin,\n icons: standardNavIcons,\n // An app that OWNS a standard nav entry (Contacts) nests its own routes\n // under it. This is routing data, not chrome configuration.\n contactsItem: data?.contactsItem,\n mode: navMode,\n activeOrgId,\n }),\n [data?.contactsItem, navMode, activeOrgId],\n )\n // Core supplies the canonical tree already decorated with its live gating\n // state (and with app-relative hrefs so its router handles them in-app).\n const navItems = core?.nav ?? derivedNavItems\n\n // Dashboard › Sub › Item, derived from the same tree the sidebar renders.\n // Core publishes its trail from the page itself, because a core page's\n // location is finer-grained than its nav row (e.g. a contact detail view).\n const derivedBreadcrumbs = useMemo(\n () => deriveBreadcrumbs(navItems, pathname, { search, origin: meta.origin }),\n [navItems, pathname, search, meta.origin],\n )\n const breadcrumbs = core ? core.page?.breadcrumbs : derivedBreadcrumbs\n\n return (\n <NavAppShell\n sidebar={{\n pathname,\n brand: {\n logoSrc: core?.brandLogoSrc ?? '/brand/logos/relationship-mode-logo.png',\n logoAlt: 'Politogy',\n badgeLabel: 'P',\n title: 'Politogy',\n },\n nav: {\n items:\n navDisplay === 'tabs'\n ? navItems.map(({ children: _sub, ...item }) => item)\n : navItems,\n },\n // The app's own workspace is NOT the organization — it is a per-app\n // bootstrap account with a different id in every app. Showing it while\n // the chrome API resolves flashed the wrong org on every load (\"not\n // connected to the account\"). Shimmer while in flight; only fall back\n // to the app label once the chrome lookup has actually failed.\n org: activeCoreOrg\n ? {\n name: activeCoreOrg.name,\n id: activeCoreOrg.id,\n logoUrl: activeCoreOrg.logoUrl,\n brandColor: activeCoreOrg.brandColor,\n organizations: coreOrgs,\n onSwitch: chrome?.setOrgId,\n }\n : chrome?.orgsLoading\n ? { name: null, id: null, loading: true }\n : { name: session.accountName, id: session.accountId },\n mode,\n footer,\n linkComponent,\n }}\n betaBanner={{}}\n contentWrapper={core?.contentWrapper}\n topBar={{\n // Core's pages publish their own identity; every other app is one\n // product surface and takes its registry title.\n title: core?.page?.title ?? meta.title,\n breadcrumbs,\n user: {\n name: identity.userName,\n email: identity.email,\n // The chrome API's avatar wins; the app may supply a fallback chain\n // (e.g. a stored memoji) for users with no uploaded photo.\n avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,\n role: session.role,\n },\n userMenu,\n onSignOut: handleSignOut,\n // The action cluster is IDENTICAL everywhere — core included. Page\n // actions sit BEFORE it, never inside it, so the Alerts bell keeps its\n // required position: farthest right, immediately before My Account.\n actions: (\n <>\n {core?.page?.actions}\n <ChromeTopBarActions />\n </>\n ),\n }}\n >\n {children}\n </NavAppShell>\n )\n}\n","// ---------------------------------------------------------------------------\n// App chrome wiring — the ONE shared implementation of the per-app glue that\n// previously lived as a copied ChromeWidgets.tsx in every Next.js feature app\n// (forms / surveys / polls / contacts). Copies drifted (org dropdown, badges);\n// this module makes that class of drift impossible: apps configure, never\n// re-implement (Reference/UI-Platform: consume via npm, never vendored).\n//\n// Provides:\n// <PolitogyAppChrome> — NavChromeProvider (same-origin proxy fetcher) +\n// chrome state (active core org, nav display) +\n// DevNotes provider, in one wrapper.\n// <ChromeTopBarActions> — the canonical top-bar cluster, in canonical order\n// (Fuel · Nav display · DevNotes · Support · Alerts\n// LAST, immediately left of My Account).\n// useChromeState / useChromeUserMenu / useIsSuperAdmin / useChromeFooter.\n//\n// The app still owns: its session (the /api/chrome proxy), page frames, build\n// metadata (env/commit/version), tab strips, and its own icons.\n// ---------------------------------------------------------------------------\n\n'use client'\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n type ReactNode,\n} from 'react'\nimport {\n NavChromeProvider,\n FuelAction,\n NotificationsAction,\n SupportAction,\n NavDisplayAction,\n TopBarWidgetSlot,\n useChromeOrgs,\n useThemePreference,\n type NavChromeFetcher,\n type NavDisplayMode,\n type NavOrgOption,\n type NavUserMenu,\n type NavFooter,\n type NavFooterLink,\n} from '../native/index'\nimport {\n DevNotesProvider,\n DevNotesMenu,\n DevNotesOverlay,\n DevNotesTaskListModal,\n createDevNotesClient,\n type DevNotesConfig,\n} from '@the-portland-company/devnotes'\nimport {\n FiActivity,\n FiBarChart2,\n FiBriefcase,\n FiClock,\n FiCreditCard,\n FiDollarSign,\n FiEdit3,\n FiFileText,\n FiFlag,\n FiGrid,\n FiHome,\n FiLayout,\n FiLifeBuoy,\n FiList,\n FiMail,\n FiMap,\n FiMapPin,\n FiMessageSquare,\n FiPhone,\n FiSend,\n FiSettings,\n FiShare2,\n FiTarget,\n FiUpload,\n FiUser,\n FiUsers,\n} from 'react-icons/fi'\nimport { useChromeMe, useNavChromeFetcher } from '../native/index'\nimport {\n ACTIVE_ORG_QUERY_PARAM,\n isOrgId,\n resolveActiveOrg,\n writeActiveOrgCookie,\n} from '../active-org'\nimport type { StandardNavIconKey } from '../native/standardNav'\nimport type { NavBreadcrumb, NavIcon, NavItem, NavMode } from '../native/index'\n\n/**\n * The canonical icon map for standardPolitogyNav — ONE definition so every\n * app renders identical glyphs (including the Organization Settings branch:\n * briefcase / settings / creditCard / dollarSign / share).\n */\nexport const standardNavIcons: Record<StandardNavIconKey, NavIcon> = {\n home: FiHome,\n users: FiUsers,\n mail: FiMail,\n messageSquare: FiMessageSquare,\n layout: FiLayout,\n activity: FiActivity,\n lifeBuoy: FiLifeBuoy,\n grid: FiGrid,\n list: FiList,\n upload: FiUpload,\n edit: FiEdit3,\n fileText: FiFileText,\n send: FiSend,\n clock: FiClock,\n user: FiUser,\n barChart: FiBarChart2,\n briefcase: FiBriefcase,\n settings: FiSettings,\n creditCard: FiCreditCard,\n dollarSign: FiDollarSign,\n share: FiShare2,\n // Campaign / Petition mode slots.\n phone: FiPhone,\n map: FiMap,\n mapPin: FiMapPin,\n target: FiTarget,\n flag: FiFlag,\n}\n\nconst ORG_STORAGE_KEY = 'vrm.activeOrg'\n// The user's mode choice, shared by every app on the origin's localStorage.\nconst MODE_STORAGE_KEY = 'vrm.activeMode'\nconst NAV_DISPLAY_KEY = 'vrm.navDisplay'\nconst CORE_BASE_URL = 'https://app.politogyvrm.com'\n\nexport type ChromeIdentity = {\n userId: string\n userName: string | null\n email: string | null\n /** The APP's own role string (any casing) — feeds DevNotes admin gating. */\n role: string\n}\n\ntype ChromeState = {\n orgId: string | null\n setOrgId: (id: string) => void\n orgs: (NavOrgOption & { rawRole?: string | null })[]\n /**\n * The org list is still in flight. Consumers must render a skeleton rather\n * than fall back to the app's own workspace label, which is a DIFFERENT\n * account and flashes as the wrong organization.\n */\n orgsLoading: boolean\n navDisplay: NavDisplayMode\n setNavDisplay: (m: NavDisplayMode) => void\n}\n\nconst ChromeStateContext = createContext<ChromeState | null>(null)\n\nexport function useChromeState(): ChromeState | null {\n return useContext(ChromeStateContext)\n}\n\nexport type ControlledOrgState = {\n orgId: string | null\n onSwitch: (id: string) => void\n /** Override the org list (e.g. the app's own session-scoped list). */\n orgs?: (NavOrgOption & { rawRole?: string | null })[]\n}\n\nfunction ChromeStateProvider({\n controlledOrg,\n children,\n}: {\n controlledOrg?: ControlledOrgState\n children: ReactNode\n}) {\n const { orgs, loading: orgsFetching, error: orgsError } = useChromeOrgs()\n const [orgId, setOrgIdState] = useState<string | null>(null)\n const [navDisplay, setNavDisplayState] = useState<NavDisplayMode>('both')\n\n // Boot: restore persisted choices. The cross-app active org (URL `?org=` or\n // the `.politogyvrm.com` cookie) wins over host-only localStorage so every\n // app + subdomain resolves the SAME org instead of each defaulting to its own\n // earliest-joined org.\n useEffect(() => {\n try {\n const storedOrg = resolveActiveOrg() ?? localStorage.getItem(ORG_STORAGE_KEY)\n if (storedOrg) setOrgIdState(storedOrg)\n const storedNav = localStorage.getItem(NAV_DISPLAY_KEY)\n if (storedNav === 'menu' || storedNav === 'tabs' || storedNav === 'both') {\n setNavDisplayState(storedNav)\n }\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n // Once orgs resolve: validate the stored org, default to the first.\n useEffect(() => {\n if (!orgs || orgs.length === 0) return\n setOrgIdState((prev) => (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id))\n }, [orgs])\n\n const setOrgId = useCallback((id: string) => {\n setOrgIdState(id)\n try {\n localStorage.setItem(ORG_STORAGE_KEY, id)\n } catch {\n /* best effort */\n }\n // Remember the choice cross-subdomain so the next app opens on the same org.\n writeActiveOrgCookie(id)\n }, [])\n\n const setNavDisplay = useCallback((m: NavDisplayMode) => {\n setNavDisplayState(m)\n try {\n localStorage.setItem(NAV_DISPLAY_KEY, m)\n } catch {\n /* best effort */\n }\n }, [])\n\n const value = useMemo<ChromeState>(() => {\n // CONTROLLED mode (Contacts): the APP owns the active org + switching —\n // its switch does a full session re-scope. The shared widgets read the\n // app's state so there is exactly one org truth (unification directive).\n if (controlledOrg) {\n return {\n orgId: controlledOrg.orgId,\n setOrgId: controlledOrg.onSwitch,\n orgs:\n controlledOrg.orgs ??\n (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n orgsLoading: false,\n navDisplay,\n setNavDisplay,\n }\n }\n return {\n orgId,\n setOrgId,\n orgs: (orgs ?? []).map((o) => ({\n id: o.id,\n name: o.name,\n logoUrl: o.logoUrl,\n brandColor: o.brandColor,\n role: o.role,\n rawRole: o.rawRole ?? null,\n })),\n // Loading until the fetch settles. On error we stop loading so the frame\n // falls back to the app label rather than shimmering forever.\n orgsLoading: orgsFetching && !orgsError && !orgs,\n navDisplay,\n setNavDisplay,\n }\n }, [controlledOrg, orgId, setOrgId, orgs, orgsFetching, orgsError, navDisplay, setNavDisplay])\n\n // Keep the shared cookie + address-bar `?org=` in sync with the ACTIVE org —\n // whichever path owns it, controlled (Contacts) or uncontrolled (feature\n // apps) — so every app + tab resolves the same org and every URL is\n // bookmarkable/shareable with its org context. Idempotent; no-op on server.\n const activeOrgId = value.orgId\n useEffect(() => {\n if (!isOrgId(activeOrgId)) return\n writeActiveOrgCookie(activeOrgId as string)\n try {\n const url = new URL(window.location.href)\n if (url.searchParams.get(ACTIVE_ORG_QUERY_PARAM) !== activeOrgId) {\n url.searchParams.set(ACTIVE_ORG_QUERY_PARAM, activeOrgId as string)\n window.history.replaceState(window.history.state, '', url.toString())\n }\n } catch {\n /* SSR / no window */\n }\n }, [activeOrgId])\n\n return <ChromeStateContext.Provider value={value}>{children}</ChromeStateContext.Provider>\n}\n\nexport type ChromeDevNotesConfig = {\n /** Override the DevNotes API base (default: `${proxyBasePath}/devnotes`). */\n basePath?: string\n /** Client-side token supplier (default: empty — same-origin proxy injects). */\n getAuthToken?: () => Promise<string> | string\n /** Custom fetch (e.g. credentials:'include' against a cross-origin API). */\n fetch?: typeof globalThis.fetch\n /**\n * The app already mounts its own `<DevNotesProvider>` ABOVE this chrome, so\n * the chrome must not mount a second one (VRM core, whose provider carries\n * capabilities the generic client has no equivalent for — user stories, its\n * own role gating). The working SURFACE still comes from here, so the\n * launcher, the overlay and the task modal behave identically everywhere;\n * only the data adapter is the app's.\n */\n providedByApp?: boolean\n}\n\nfunction AppDevNotes({\n storagePrefix,\n proxyBasePath,\n devNotes,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath: string\n devNotes?: ChromeDevNotesConfig\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const adapter = useMemo(\n () =>\n createDevNotesClient({\n basePath: devNotes?.basePath ?? `${proxyBasePath}/devnotes`,\n // Default: the same-origin proxy attaches the session's Bearer.\n getAuthToken: devNotes?.getAuthToken ?? (async () => ''),\n ...(devNotes?.fetch ? { fetch: devNotes.fetch } : {}),\n }),\n [proxyBasePath, devNotes?.basePath, devNotes?.getAuthToken, devNotes?.fetch],\n )\n\n const user = useMemo(\n () => ({\n id: identity.userId,\n email: identity.email ?? '',\n fullName: identity.userName ?? undefined,\n }),\n [identity.userId, identity.email, identity.userName],\n )\n\n const config = useMemo<DevNotesConfig>(\n () => ({\n storagePrefix,\n role: identity.role.toLowerCase() === 'admin' ? 'admin' : 'reporter',\n disableAi: true,\n getPagePath: () => window.location.pathname,\n }),\n [storagePrefix, identity.role],\n )\n\n // The app brought its own provider — mount only the surface, so a second\n // DevNotesProvider never shadows the app's adapter.\n if (devNotes?.providedByApp) return <DevNotesSurface>{children}</DevNotesSurface>\n\n return (\n <DevNotesProvider adapter={adapter} user={user} config={config}>\n <DevNotesSurface>{children}</DevNotesSurface>\n </DevNotesProvider>\n )\n}\n\n// \"View All Tasks\" is triggered from the menu in the top bar but the modal has\n// to render at the chrome ROOT, or it would be trapped inside the top bar's\n// stacking/blur context. The menu publishes the request through this context.\nconst DevNotesTasksContext = createContext<{ ready: boolean; openTasks: () => void }>({\n ready: false,\n openTasks: () => {},\n})\n\n/**\n * The DevNotes working surface, mounted once at the chrome root.\n *\n * `<DevNotesMenu>` only TOGGLES creation mode — `<DevNotesOverlay>` is what\n * renders the click-to-place capture layer, the report dots and the report\n * form. The shell shipped the provider and the menu but never the overlay, so\n * on every app that used this chrome (forms / surveys / polls) \"Create Task\"\n * closed the menu and did nothing at all: the mode flipped on with nothing\n * listening (HITL bug report 2026-07-23). Contacts didn't hit it only because\n * it still had its own hand-rolled surface.\n */\nfunction DevNotesSurface({ children }: { children: ReactNode }) {\n const [tasksOpen, setTasksOpen] = useState(false)\n const openTasks = useCallback(() => setTasksOpen(true), [])\n const tasksCtx = useMemo(() => ({ ready: true, openTasks }), [openTasks])\n\n return (\n <DevNotesTasksContext.Provider value={tasksCtx}>\n {children}\n {/* Mounted only while open: the modal loads data on mount regardless of\n its `open` prop, so keeping it unmounted avoids fetching every user's\n tasks before they ask for them. */}\n {tasksOpen ? <DevNotesTaskListModal open onClose={() => setTasksOpen(false)} /> : null}\n <DevNotesOverlay />\n </DevNotesTasksContext.Provider>\n )\n}\n\n/**\n * The app-chrome root. Wrap the authenticated frame once:\n *\n * <PolitogyAppChrome storagePrefix=\"forms\" identity={{userId, userName, email, role}}>\n * <ShellFrame …/>\n * </PolitogyAppChrome>\n */\nexport function PolitogyAppChrome({\n storagePrefix,\n proxyBasePath = '/api/chrome',\n fetchJson: fetchJsonOverride,\n devNotes,\n org,\n identity,\n children,\n}: {\n storagePrefix: string\n proxyBasePath?: string\n /**\n * Override the same-origin proxy fetcher for apps that talk to the chrome\n * API directly with their own auth (Contacts sends a client-side Bearer).\n * Paths stay relative ('orgs', 'me', …) — the override owns base + auth.\n */\n fetchJson?: NavChromeFetcher\n /** DevNotes wiring override for direct-auth apps (Contacts). */\n devNotes?: ChromeDevNotesConfig\n /** Controlled org state — the app owns active-org + switching (Contacts). */\n org?: ControlledOrgState\n identity: ChromeIdentity\n children: ReactNode\n}) {\n const proxyFetchJson = useMemo<NavChromeFetcher>(\n () =>\n async (path, init) => {\n const res = await fetch(`${proxyBasePath}/${path}`, {\n method: init?.method ?? 'GET',\n headers: init?.headers,\n body: init?.body,\n })\n if (!res.ok) throw new Error(`chrome ${path}: ${res.status}`)\n if (res.status === 204) return null\n const text = await res.text()\n return text ? JSON.parse(text) : null\n },\n [proxyBasePath],\n )\n const fetchJson = fetchJsonOverride ?? proxyFetchJson\n\n return (\n <NavChromeProvider fetchJson={fetchJson}>\n <ChromeStateProvider controlledOrg={org}>\n <AppDevNotes\n storagePrefix={storagePrefix}\n proxyBasePath={proxyBasePath}\n devNotes={devNotes}\n identity={identity}\n >\n {children}\n </AppDevNotes>\n </ChromeStateProvider>\n </NavChromeProvider>\n )\n}\n\n/** The standard top-bar action cluster. The Alerts bell renders LAST so it\n * sits farthest right, immediately left of the My Account badge. */\nexport function ChromeTopBarActions() {\n const state = useChromeState()\n if (!state) return null\n return (\n <>\n <FuelAction orgId={state.orgId} />\n <NavDisplayAction value={state.navDisplay} onChange={state.setNavDisplay} />\n <TopBarWidgetSlot\n title=\"DevNotes\"\n description=\"Capture bugs and notes, filed straight to Forge.\"\n >\n <ChromeDevNotesMenu />\n </TopBarWidgetSlot>\n <SupportAction orgId={state.orgId} />\n <NotificationsAction orgId={state.orgId} />\n </>\n )\n}\n\n/**\n * The DevNotes launcher. Renders nothing until the surface above is mounted, so\n * it never touches DevNotes context before the provider exists, and forwards\n * \"View All Tasks\" to the root-level modal.\n */\nfunction ChromeDevNotesMenu() {\n const { ready, openTasks } = useContext(DevNotesTasksContext)\n if (!ready) return null\n return <DevNotesMenu position=\"top-right\" onViewTasks={openTasks} />\n}\n\n/** True when any core-org membership carries a super-admin tier. */\nexport function useIsSuperAdmin(): boolean {\n const state = useChromeState()\n return (state?.orgs ?? []).some((o) => /super/i.test(o.rawRole ?? ''))\n}\n\nconst FOOTER_LINKS: NavFooterLink[] = [\n { label: 'Privacy', href: `${CORE_BASE_URL}/privacy-policy`, external: true },\n { label: 'Terms', href: `${CORE_BASE_URL}/terms-and-conditions`, external: true },\n {\n label: 'Subscriptions',\n href: 'https://politogyvrm.com/subscription-cancellation-policy',\n external: true,\n },\n]\n\n/**\n * Sidebar footer: legal links for everyone; build metadata (env / commit /\n * version / date / author) ONLY for Super Administrators\n * (Reference/UI-Platform). Pass the app's build-time metadata in.\n */\nexport function useChromeFooter(meta: {\n envLabel?: string\n version?: string\n commit?: string\n commitDate?: string\n commitAuthor?: string\n}): NavFooter {\n const isSuperAdmin = useIsSuperAdmin()\n return useMemo<NavFooter>(\n () => (isSuperAdmin ? { links: FOOTER_LINKS, ...meta } : { links: FOOTER_LINKS }),\n [isSuperAdmin, meta.envLabel, meta.version, meta.commit, meta.commitDate, meta.commitAuthor],\n )\n}\n\n// ---------------------------------------------------------------------------\n// Identity — resolved by the PACKAGE, from the chrome API.\n//\n// Apps used to pass a display name straight from their own session claims, and\n// forms passed `user.name ?? user.email` — so a user with no name set had their\n// EMAIL rendered as their name, and the chrome API's real profile name could\n// never win (HITL 2026-07-23). The chrome API is the authority; what the app\n// passes is a pre-hydration fallback only, and an email is never a name.\n// ---------------------------------------------------------------------------\n\nexport function useChromeIdentity(fallback?: {\n userName?: string | null\n email?: string | null\n}): ResolvedIdentity {\n const { me, loaded } = useChromeMe()\n return resolveIdentity({\n apiName: me?.userName,\n fallbackName: fallback?.userName,\n email: fallback?.email,\n avatarUrl: me?.avatarUrl,\n loaded,\n })\n}\n\n// Breadcrumbs live in ../lib/breadcrumbs (pure, dependency-free) and are\n// re-exported here so apps keep a single import surface.\nexport { deriveBreadcrumbs } from '../lib/breadcrumbs'\nexport { looksLikeEmail, resolveIdentity, type ResolvedIdentity } from '../lib/identity'\nimport { resolveIdentity, type ResolvedIdentity } from '../lib/identity'\n\n// ---------------------------------------------------------------------------\n// Breadcrumbs — derived by the PACKAGE from the nav tree + the current path.\n//\n// Every app hand-built its own trail, so they disagreed: forms rendered\n// \"Forms › Dashboard\" while polls rendered \"Polls\" under a \"Polls\" title\n// (\"Polls / Polls\"). The standard is Dashboard › Sub › Item (HITL 2026-07-23),\n// derived from the same nav tree the sidebar renders, so no app can drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The My Account menu config — profile/org links into VRM core, real roles\n * from the user's core organizations, the signed-in account, and the shared\n * Light/System/Dark theme control (auth-derived, cross-app).\n */\nexport function useChromeUserMenu({\n userId,\n userName,\n email,\n}: {\n userId: string\n userName: string | null\n email: string | null\n}): NavUserMenu {\n const state = useChromeState()\n const theme = useThemePreference()\n const activeOrg = state?.orgs.find((o) => o.id === state.orgId) ?? null\n const orgSuffix = activeOrg ? `?org=${activeOrg.id}` : ''\n return {\n // My Profile is global — NOT org-suffixed (main-app ShellChromeBridge).\n myProfileHref: `${CORE_BASE_URL}/my-profile`,\n organizationSettingsHref: `${CORE_BASE_URL}/organization-settings${orgSuffix}`,\n roles: (state?.orgs ?? [])\n .filter((o) => o.rawRole || o.role)\n .map((o) => ({ role: (o.rawRole ?? o.role) as string, orgName: o.name })),\n accounts: [{ id: userId, name: userName, email, avatarUrl: null, active: true }],\n addAccountHref: `${CORE_BASE_URL}/my-profile#accounts`,\n theme: { value: theme.value, onChange: theme.setValue },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Mode selector — entitlement-driven (HITL 2026-07-23: an org with multiple\n// active modes selects them from the left nav). Entitlements come from the\n// chrome API: GET me with the active org header → modes booleans\n// {relationship, campaign, petition, polling}. Selecting a mode navigates to\n// that mode's VRM-core dashboard carrying the active org.\n// ---------------------------------------------------------------------------\n\nconst MODE_DEFS: {\n id: string\n label: string\n icon: NavIcon\n /** Absolute destination, or a VRM-core path prefixed with CORE_BASE_URL. */\n href: string\n orgQuery: boolean\n}[] = [\n {\n id: 'relationship',\n label: 'Relationship Mode',\n icon: FiUsers,\n href: `${CORE_BASE_URL}/relationship/dashboard`,\n orgQuery: true,\n },\n {\n id: 'campaign',\n label: 'Campaign Mode',\n icon: FiBarChart2,\n href: `${CORE_BASE_URL}/campaign/dashboard`,\n orgQuery: true,\n },\n {\n // Petition Mode lives in its own standalone app on its own subdomain — the\n // petitions repo deploys production to petition-mode.politogyvrm.com.\n id: 'petition',\n label: 'Petition Mode',\n icon: FiFlag,\n href: 'https://petition-mode.politogyvrm.com/',\n orgQuery: true,\n },\n]\n\ntype ModeId = 'relationship' | 'campaign' | 'petition'\n\ntype UseChromeModeArg =\n | ModeId\n | {\n /** Pre-entitlement fallback for first paint (default 'relationship'). */\n fallbackModeId?: ModeId\n /**\n * Registry-pinned mode for a MODE-SCOPED app (e.g. the Campaign apps\n * a2p / phonebank / canvass / signs, from `POLITOGY_APPS[app].mode`). The\n * pill DISPLAYS this mode regardless of the cross-app selection, so a\n * campaign app never shows a Relationship pill. The switcher still appears\n * when the active org is entitled to more than one mode, so the user can\n * jump out to another mode's home.\n */\n pinnedModeId?: ModeId\n }\n\n/**\n * The active mode pill / selector.\n *\n * For an UNSCOPED app the current mode is the USER'S OWN SELECTION (persisted\n * cross-app in localStorage under `vrm.activeMode`), narrowed to what the active\n * org is entitled to — never a literal an app hardcodes. Apps used to call\n * `useChromeMode('relationship')`, which pinned the pill to Relationship even\n * for an org entitled to Campaign or Petition (HITL 2026-07-23); the string\n * argument now only supplies the pre-entitlement fallback for first paint.\n *\n * A MODE-SCOPED app passes `{ pinnedModeId }` so its pill displays that mode\n * (its own mode from the app registry) rather than the cross-app selection —\n * while a multi-mode org still gets the switcher to leave.\n */\nexport function useChromeMode(arg: UseChromeModeArg = {}): NavMode {\n const { fallbackModeId = 'relationship', pinnedModeId } =\n typeof arg === 'string' ? { fallbackModeId: arg, pinnedModeId: undefined } : arg\n const ctx = useContext(ChromeStateContext)\n const nav = useNavChromeFetcher()\n const orgId = ctx?.orgId ?? null\n const [entitled, setEntitled] = useState<Record<string, boolean> | null>(null)\n const [selectedModeId, setSelectedModeId] = useState<string | null>(null)\n\n // Restore the user's last mode choice (shared key across every app).\n useEffect(() => {\n try {\n const stored = localStorage.getItem(MODE_STORAGE_KEY)\n if (stored && MODE_DEFS.some((m) => m.id === stored)) setSelectedModeId(stored)\n } catch {\n /* storage unavailable */\n }\n }, [])\n\n useEffect(() => {\n if (!nav || !orgId) return\n let cancelled = false\n nav('me', { headers: { 'x-contacts-org': orgId } })\n .then((data) => {\n if (cancelled) return\n const modes = (data as { modes?: Record<string, boolean> | null })?.modes ?? null\n setEntitled(modes)\n })\n .catch(() => {\n /* entitlements unavailable — single-mode pill stands */\n })\n return () => {\n cancelled = true\n }\n }, [nav, orgId])\n\n const options = MODE_DEFS.filter((m) => entitled?.[m.id])\n // Resolution order: a registry pin (mode-scoped app) wins outright → then the\n // user's selection (if the org is still entitled to it) → the first entitled\n // mode → the app's fallback. Entitlements are async, so before they land the\n // pin/fallback holds the pill's shape (no layout shift).\n const pinned = pinnedModeId ? MODE_DEFS.find((m) => m.id === pinnedModeId) ?? null : null\n const selected = selectedModeId\n ? options.find((m) => m.id === selectedModeId) ?? null\n : null\n const current =\n pinned ??\n selected ??\n options[0] ??\n MODE_DEFS.find((m) => m.id === fallbackModeId) ??\n MODE_DEFS[0]\n const suffix = orgId ? `?org=${orgId}` : ''\n\n return {\n label: current.label,\n icon: current.icon,\n options:\n options.length > 1\n ? options.map((o) => ({ id: o.id, label: o.label, icon: o.icon }))\n : undefined,\n onSelect:\n options.length > 1\n ? (id: string) => {\n const def = MODE_DEFS.find((m) => m.id === id)\n if (!def) return\n // Remember the choice BEFORE navigating so the destination app —\n // and every app after it — opens in the mode the user picked.\n setSelectedModeId(id)\n try {\n localStorage.setItem(MODE_STORAGE_KEY, id)\n } catch {\n /* storage unavailable */\n }\n const sep = def.href.includes('?') ? '&' : '?'\n window.location.assign(\n def.orgQuery && suffix ? `${def.href}${sep}${suffix.slice(1)}` : def.href,\n )\n }\n : undefined,\n }\n}\n\n// The one-component app frame (see ./frame.tsx). Re-exported here so apps have\n// a single import surface: `@the-portland-company/shell/app-chrome`.\nexport {\n PolitogyAppFrame,\n POLITOGY_APPS,\n type PolitogyAppKey,\n type PolitogyAppMode,\n type PolitogyAppSession,\n type PolitogyBuildMeta,\n type PolitogyFrameData,\n} from './frame'\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@the-portland-company/shell",
|
|
3
|
-
"version": "0.36.
|
|
3
|
+
"version": "0.36.3",
|
|
4
4
|
"description": "Shared chrome (header, sidebar, footer, theme, auth) for politogy apps. Drop into any Vite SPA to inherit the politogy look and signed-in user.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|