@the-portland-company/shell 0.85.0 → 0.87.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/app-chrome.cjs +105 -5
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +14 -1
- package/dist/app-chrome.d.ts +14 -1
- package/dist/app-chrome.js +40 -7
- package/dist/app-chrome.js.map +1 -1
- package/dist/{chunk-OOVS5D2K.js → chunk-3JOLELB4.js} +9 -3
- package/dist/chunk-3JOLELB4.js.map +1 -0
- package/dist/chunk-GVQKZI5X.js +92 -0
- package/dist/chunk-GVQKZI5X.js.map +1 -0
- package/dist/native.cjs +7 -1
- package/dist/native.cjs.map +1 -1
- package/dist/native.js +1 -1
- package/dist/url-state.js +2 -90
- package/dist/url-state.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-OOVS5D2K.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
+
## 0.87.1
|
|
2
|
+
|
|
3
|
+
Release fix only; same code as 0.87.0, which never reached npm. The 0.87.0 version bump regenerated `package-lock.json` with a newer npm that dropped two optional `yaml@2.9.1` entries, so the publish gate's `npm ci` refused it. The lock file is restored from 0.86.0 with only its version changed.
|
|
4
|
+
|
|
5
|
+
## 0.87.0
|
|
6
|
+
|
|
7
|
+
The top-bar search works in Relationship Mode. Spec cited: Reference/UI-Platform "Top bar — required elements" (Relationship search returns the Account's Contacts only) and Data-System/UVP §7.1 (a result opens the Contact Card).
|
|
8
|
+
|
|
9
|
+
- **Why.** Reported by Ben 2026-10-18: clicking the search box in Contacts did nothing. The frame passed only a placeholder, so `NavTopBar` drew a read-only input in every app.
|
|
10
|
+
- **Relationship Mode.** The box takes typing; Enter opens Contacts → All Contacts filtered to the query: `?mode=relationship&page=contacts&tab=contacts§ion=table&org_id=<tail>&q=<query>`. Same origin from inside Contacts, the `-staging` twin from a staging host. No app wiring and no new prop (an app never passes chrome configuration).
|
|
11
|
+
- **Campaign and Petition.** Unchanged: decorative until their search is built (Campaign voter lookup is Campaign-Voter-Record §3).
|
|
12
|
+
- **New export** `relationshipSearchHref(query, { orgId, fromApp, hostname })`.
|
|
13
|
+
- **Needs** Contacts to read `q` into its table search (politogy-contacts, same day).
|
|
14
|
+
- Tests: `__tests__/relationshipSearch.test.ts`, `__tests__/topbarSearch.test.tsx`.
|
|
15
|
+
|
|
16
|
+
## 0.86.0
|
|
17
|
+
|
|
18
|
+
Campaign Mode gets a Tribes row. Spec cited: Specs/Tribes §2.3–§2.4 and Specs/Left-Navigation §9.2 (decided 2026-10-16 by Ben: Campaign and Relationship Tribes are separate; Campaign Tribes live in Campaign Mode).
|
|
19
|
+
|
|
20
|
+
- **Campaign tree.** Race data now lists Battleground · District data · **Tribes** (`users-round`), linking to `https://battleground.politogyvrm.com/tribes` (org carried as before). It is a page of the Battleground app at `/tribes`, so it is the only active row there.
|
|
21
|
+
- **Relationship tree unchanged.** Relationship Tribes stay inside Contacts → Tribes; there is still no Relationship Tribes row.
|
|
22
|
+
- **Release order.** Publish after the Battleground app serves `/tribes`, so the row never links to a missing page.
|
|
23
|
+
- Tests: `__tests__/standardNav.test.ts`.
|
|
24
|
+
|
|
1
25
|
## 0.85.0
|
|
2
26
|
|
|
3
27
|
The mode switcher, the rail and the rows always show one mode. Spec cited: Specs/Left-Navigation §9.1, §9.2, §9.4 and §11.1 ("the rail never shows a mix of two modes").
|
package/dist/app-chrome.cjs
CHANGED
|
@@ -2443,13 +2443,19 @@ function standardPolitogyNav(opts) {
|
|
|
2443
2443
|
const rows = [
|
|
2444
2444
|
// Left-Navigation §9.2: Race data -> Research -> GOTV -> Account.
|
|
2445
2445
|
// Battleground is the Campaign landing page; there is no Campaign
|
|
2446
|
-
// Dashboard row, and Voter Contact
|
|
2446
|
+
// Dashboard row, and Voter Contact has no row. Campaign Tribes has its
|
|
2447
|
+
// own row after District data (decided 2026-10-16).
|
|
2447
2448
|
{ kind: "section", label: "Race data" },
|
|
2448
2449
|
// Battleground is the Campaign Mode landing page, its own app on its own
|
|
2449
2450
|
// host (registry key `battleground`; Specs/Left-Navigation §9.2, §13, §14).
|
|
2450
2451
|
{ label: "Battleground", icon: icon("crosshair"), href: withOrg(`${BATTLEGROUND_ORIGIN}battleground`) },
|
|
2451
2452
|
// District Data is its own Campaign Mode app on its own host (registry key `districts`).
|
|
2452
2453
|
{ label: "District data", icon: icon("landmark"), href: withOrg(`${DISTRICTS_ORIGIN}districts`) },
|
|
2454
|
+
// Campaign Tribes (Specs/Tribes §2.3–§2.4, Left-Navigation §9.2; decided
|
|
2455
|
+
// 2026-10-16, Ben): a page of the Battleground app at /tribes, so it is
|
|
2456
|
+
// the only active row there (Battleground's /battleground prefix does
|
|
2457
|
+
// not match it). Relationship Tribes stay inside Contacts.
|
|
2458
|
+
{ label: "Tribes", icon: icon("usersRound"), href: withOrg(`${BATTLEGROUND_ORIGIN}tribes`) },
|
|
2453
2459
|
{ kind: "section", label: "Research" },
|
|
2454
2460
|
{
|
|
2455
2461
|
label: "Polling",
|
|
@@ -4543,6 +4549,67 @@ function LoadingScreen({
|
|
|
4543
4549
|
}
|
|
4544
4550
|
);
|
|
4545
4551
|
}
|
|
4552
|
+
|
|
4553
|
+
// src/url-state.ts
|
|
4554
|
+
var URL_STATE_MODES = ["relationship", "campaign", "petition"];
|
|
4555
|
+
var URL_STATE_KEYS = ["mode", "page", "tab", "section", "org_id"];
|
|
4556
|
+
var SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
4557
|
+
function isUrlStateMode(v) {
|
|
4558
|
+
return typeof v === "string" && URL_STATE_MODES.includes(v);
|
|
4559
|
+
}
|
|
4560
|
+
function searchOf(input) {
|
|
4561
|
+
if (input instanceof URLSearchParams) return new URLSearchParams(input);
|
|
4562
|
+
return new URLSearchParams();
|
|
4563
|
+
}
|
|
4564
|
+
var slugOrNull = (v) => v && SLUG_RE.test(v) ? v : null;
|
|
4565
|
+
function shortOrgId(v) {
|
|
4566
|
+
if (!v) return null;
|
|
4567
|
+
const tail = orgIdTail(v);
|
|
4568
|
+
if (tail) return tail;
|
|
4569
|
+
const t = String(v).trim().toLowerCase();
|
|
4570
|
+
return /^[0-9a-f]{6,7}$/.test(t) ? t : null;
|
|
4571
|
+
}
|
|
4572
|
+
function readUrlState(input) {
|
|
4573
|
+
const params = searchOf(input);
|
|
4574
|
+
const mode = params.get("mode");
|
|
4575
|
+
const orgRaw = params.get("org_id");
|
|
4576
|
+
const feature = new URLSearchParams();
|
|
4577
|
+
for (const [k, v] of params) if (!URL_STATE_KEYS.includes(k)) feature.append(k, v);
|
|
4578
|
+
return {
|
|
4579
|
+
mode: isUrlStateMode(mode) ? mode : null,
|
|
4580
|
+
page: slugOrNull(params.get("page")),
|
|
4581
|
+
tab: slugOrNull(params.get("tab")),
|
|
4582
|
+
section: slugOrNull(params.get("section")),
|
|
4583
|
+
org_id: shortOrgId(orgRaw),
|
|
4584
|
+
feature
|
|
4585
|
+
};
|
|
4586
|
+
}
|
|
4587
|
+
function buildSearch(state) {
|
|
4588
|
+
const out = new URLSearchParams();
|
|
4589
|
+
for (const key of URL_STATE_KEYS) {
|
|
4590
|
+
const v = state[key];
|
|
4591
|
+
if (v) out.set(key, v);
|
|
4592
|
+
}
|
|
4593
|
+
for (const [k, v] of state.feature) out.append(k, v);
|
|
4594
|
+
const s = out.toString();
|
|
4595
|
+
return s ? `?${s}` : "";
|
|
4596
|
+
}
|
|
4597
|
+
function writeUrlState(current, patch) {
|
|
4598
|
+
const state = readUrlState(current);
|
|
4599
|
+
if (patch.mode !== void 0) state.mode = patch.mode;
|
|
4600
|
+
if (patch.page !== void 0) state.page = patch.page;
|
|
4601
|
+
if (patch.tab !== void 0) state.tab = patch.tab;
|
|
4602
|
+
if (patch.section !== void 0) state.section = patch.section;
|
|
4603
|
+
if (patch.org_id !== void 0) state.org_id = shortOrgId(patch.org_id);
|
|
4604
|
+
if (patch.feature) {
|
|
4605
|
+
for (const [k, v] of Object.entries(patch.feature)) {
|
|
4606
|
+
if (URL_STATE_KEYS.includes(k)) continue;
|
|
4607
|
+
if (v === null) state.feature.delete(k);
|
|
4608
|
+
else state.feature.set(k, v);
|
|
4609
|
+
}
|
|
4610
|
+
}
|
|
4611
|
+
return buildSearch(state);
|
|
4612
|
+
}
|
|
4546
4613
|
function parseCssUrl(value) {
|
|
4547
4614
|
if (!value) return null;
|
|
4548
4615
|
const m = /^url\(\s*(['"]?)(.*?)\1\s*\)$/i.exec(value.trim());
|
|
@@ -4680,6 +4747,22 @@ var MODE_SEARCH_PLACEHOLDER = {
|
|
|
4680
4747
|
campaign: "Search voters, walk lists, polls",
|
|
4681
4748
|
petition: "Search petitions, sheets, circulators"
|
|
4682
4749
|
};
|
|
4750
|
+
function relationshipSearchHref(query, opts = {}) {
|
|
4751
|
+
const q = query.trim();
|
|
4752
|
+
if (!q) return null;
|
|
4753
|
+
const search = writeUrlState("", {
|
|
4754
|
+
mode: "relationship",
|
|
4755
|
+
page: "contacts",
|
|
4756
|
+
tab: "contacts",
|
|
4757
|
+
section: "table",
|
|
4758
|
+
org_id: opts.orgId ?? null,
|
|
4759
|
+
feature: { q }
|
|
4760
|
+
});
|
|
4761
|
+
if (opts.fromApp === "contacts") return `/${search}`;
|
|
4762
|
+
const staging = (opts.hostname ?? "").toLowerCase().includes("staging");
|
|
4763
|
+
const origin = staging ? "https://contacts-staging.politogyvrm.com" : POLITOGY_APPS.contacts.origin;
|
|
4764
|
+
return `${origin}/${search}`;
|
|
4765
|
+
}
|
|
4683
4766
|
function PolitogyAppFrame({
|
|
4684
4767
|
app,
|
|
4685
4768
|
pathname,
|
|
@@ -4841,6 +4924,8 @@ function FrameBody({
|
|
|
4841
4924
|
}, [cookieMode]);
|
|
4842
4925
|
const coreOrgs = chrome?.orgs ?? [];
|
|
4843
4926
|
const activeCoreOrg = coreOrgs.find((o) => o.id === chrome?.orgId) ?? null;
|
|
4927
|
+
const searchMode = appMode ?? mode.id ?? "relationship";
|
|
4928
|
+
const [searchText, setSearchText] = react.useState("");
|
|
4844
4929
|
const navDisplay = chrome?.navDisplay ?? "both";
|
|
4845
4930
|
const navMode = appMode ?? (app !== CORE_APP && isStandardNavMode(mode.id) ? mode.id : "relationship");
|
|
4846
4931
|
const activeOrgId = chrome?.orgId ?? null;
|
|
@@ -4953,11 +5038,25 @@ function FrameBody({
|
|
|
4953
5038
|
userMenu,
|
|
4954
5039
|
onSignOut: handleSignOut,
|
|
4955
5040
|
// Reference/UI-Platform "Top bar — required elements": Universal
|
|
4956
|
-
// search is the only thing on the left end of the bar.
|
|
4957
|
-
//
|
|
4958
|
-
//
|
|
5041
|
+
// search is the only thing on the left end of the bar. In
|
|
5042
|
+
// Relationship Mode it searches the Account's Contacts: Enter opens
|
|
5043
|
+
// Contacts → All Contacts filtered to the query, from every app, with
|
|
5044
|
+
// no app wiring (an app never passes chrome configuration). Campaign
|
|
5045
|
+
// and Petition search are not built yet and stay decorative.
|
|
4959
5046
|
search: {
|
|
4960
|
-
placeholder: data?.searchPlaceholder ?? MODE_SEARCH_PLACEHOLDER[
|
|
5047
|
+
placeholder: data?.searchPlaceholder ?? MODE_SEARCH_PLACEHOLDER[searchMode] ?? "Search",
|
|
5048
|
+
...searchMode === "relationship" ? {
|
|
5049
|
+
value: searchText,
|
|
5050
|
+
onChange: setSearchText,
|
|
5051
|
+
onSubmit: (value) => {
|
|
5052
|
+
const href = relationshipSearchHref(value, {
|
|
5053
|
+
orgId: activeCoreOrg?.id ?? session.accountId ?? null,
|
|
5054
|
+
fromApp: app,
|
|
5055
|
+
hostname: typeof window === "undefined" ? "" : window.location.hostname
|
|
5056
|
+
});
|
|
5057
|
+
if (href) window.location.assign(href);
|
|
5058
|
+
}
|
|
5059
|
+
} : {}
|
|
4961
5060
|
},
|
|
4962
5061
|
// No Build notes control (Reference/UI-Platform, decided 2026-10-03).
|
|
4963
5062
|
// The action cluster is IDENTICAL everywhere — core included. Page
|
|
@@ -5686,6 +5785,7 @@ exports.footerLinks = footerLinks;
|
|
|
5686
5785
|
exports.looksLikeEmail = looksLikeEmail;
|
|
5687
5786
|
exports.modeLandingHref = modeLandingHref;
|
|
5688
5787
|
exports.orgSwitchLanding = orgSwitchLanding;
|
|
5788
|
+
exports.relationshipSearchHref = relationshipSearchHref;
|
|
5689
5789
|
exports.resolveIdentity = resolveIdentity;
|
|
5690
5790
|
exports.standardNavIcons = standardNavIcons;
|
|
5691
5791
|
exports.useChromeFooter = useChromeFooter;
|