@the-portland-company/shell 0.98.0 → 0.99.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 CHANGED
@@ -1,3 +1,20 @@
1
+ ## 0.99.1
2
+
3
+ The user card says "Account Manager" for an Account Manager (bug seen live 2026-11-07: benedtl@hotmail.com, `account_manager` in "TEST Campaign A (link test)", a Linked Accounts roster membership, was shown as "Member").
4
+
5
+ - **Role line from the real role.** The card under the name printed the app's own role string, and apps collapse roles to their permission pair (Contacts: admin | member), so `account_manager` came out as "Member". `PolitogyAppFrame` now labels the active organization's raw membership role (the chrome API's `rawRole`; in token mode the `org_role` claim) and falls back to the app's string. Labels (Specs/Organization-Settings §8.4 groups): owner / admin / account_admin → "Admin" and member / field roles → "Member" as before; account_manager → "Account Manager"; viewer → "Viewer".
6
+ - `useChromeState()` gains `activeRawRole`. In controlled mode (Contacts) it comes from the chrome API org list when the app's list has no `rawRole`.
7
+ - Tests: `__tests__/userCardRole.test.tsx` (new).
8
+
9
+ ## 0.99.0
10
+
11
+ VRM Verified badge and the Contact Card slide-in (Ben 2026-11-06, typed in the UI debugging box; specs Contacts §4.2, §8.6A and VRM-Verified §2 updated with his go).
12
+
13
+ - **Verified in a pill is a green dot.** `VrmVerifiedBadge` (pill, the default) draws Verified like the other tiers: a dot (`--vrm-tier-verified`, fallback `#00A651`) and the words "VRM Verified". Ben: "where it is in a pill… it should just be a green dot and remain in the pill".
14
+ - **New `variant="icon"`** for a contact view's name line: the Brand `verifiedIcon` alone (no words, no pill, `role="img"`, label "VRM Verified", the VRM Verified tooltip), placed before the first letter of the first name. It renders nothing for any other tier.
15
+ - **New `ContactSlideIn` + `useContactSlideIn`** (on `.` and `./native`): the Contact Card slides in from the right over the current page, matching Contacts' own drawer (40% overlay, right panel up to 672px, "Contact Card" header, close button, Esc and overlay click close), with an "Open in Contacts" link. It frames Contacts' chrome-less card view `/contacts/embed/<uvp id>?org=<org>`. Helpers `contactEmbedUrl`, `contactPageUrl`, `CONTACTS_ORIGIN`. Contacts §8: the Card opens as a slide-in "anywhere a contact reference appears".
16
+ - Tests: `__tests__/vrmVerifiedBadge.test.tsx` (pill dot, icon variant), `__tests__/contactSlideIn.test.tsx` (new).
17
+
1
18
  ## 0.98.0
2
19
 
3
20
  The one VRM Verified badge (Contacts v59 §4, decided 2026-11-05, Ben: "Every badge is the brand `verifiedIcon`, drawn by one shared shell component"; Data-System/VRM-Verified v7 §2, §2.6).
@@ -4094,6 +4094,34 @@ function LoadingScreen({
4094
4094
  );
4095
4095
  }
4096
4096
 
4097
+ // src/lib/roleLabel.ts
4098
+ var ADMIN_ROLES = /* @__PURE__ */ new Set(["owner", "admin", "account_admin"]);
4099
+ var STAFF_ROLES = /* @__PURE__ */ new Set(["super_admin", "super administrator", "politogy_admin"]);
4100
+ var MANAGER_ROLES = /* @__PURE__ */ new Set(["account_manager", "account manager", "manager", "campaign_manager"]);
4101
+ var FIELD_ROLES = /* @__PURE__ */ new Set([
4102
+ "member",
4103
+ "field_user",
4104
+ "field user",
4105
+ "field",
4106
+ "volunteer",
4107
+ "canvasser",
4108
+ "caller",
4109
+ "staff"
4110
+ ]);
4111
+ var VIEWER_ROLES = /* @__PURE__ */ new Set(["viewer"]);
4112
+ function roleLabel(raw) {
4113
+ const role = (raw ?? "").trim().toLowerCase();
4114
+ if (!role) return null;
4115
+ if (ADMIN_ROLES.has(role) || STAFF_ROLES.has(role)) return "Admin";
4116
+ if (MANAGER_ROLES.has(role)) return "Account Manager";
4117
+ if (FIELD_ROLES.has(role)) return "Member";
4118
+ if (VIEWER_ROLES.has(role)) return "Viewer";
4119
+ return null;
4120
+ }
4121
+ function userCardRole(activeRawRole, appRole) {
4122
+ return roleLabel(activeRawRole) ?? roleLabel(appRole) ?? (appRole?.trim() || null);
4123
+ }
4124
+
4097
4125
  // src/url-state.ts
4098
4126
  var URL_STATE_MODES = ["relationship", "campaign", "petition"];
4099
4127
  var URL_STATE_KEYS = ["mode", "page", "tab", "section", "org_id"];
@@ -4538,7 +4566,9 @@ function FrameBody({
4538
4566
  // The chrome API's avatar wins; the app may supply a fallback chain
4539
4567
  // (e.g. a stored memoji) for users with no uploaded photo.
4540
4568
  avatarUrl: identity.avatarUrl ?? data?.fallbackAvatarUrl ?? null,
4541
- role: session.role
4569
+ // The active org's RAW role wins over the app's collapsed one, so an
4570
+ // Account Manager is not shown as "Member" (Organization-Settings §8.4).
4571
+ role: userCardRole(chrome?.activeRawRole ?? activeCoreOrg?.rawRole, session.role)
4542
4572
  },
4543
4573
  userMenu,
4544
4574
  onSignOut: handleSignOut,
@@ -4726,10 +4756,13 @@ function TokenChromeStateProvider({
4726
4756
  orgs,
4727
4757
  orgsLoading: !tokenOrg.orgs && orgsFetching && !orgsError && !fetchedOrgs,
4728
4758
  orgPending: pendingOrg !== null,
4759
+ // The list's raw role, else the token's `org_role` claim — only while the
4760
+ // token is still for the active org (not mid-switch).
4761
+ activeRawRole: orgs.find((o) => o.id === orgId)?.rawRole ?? (orgId && tokenOrg.orgId === orgId ? tokenOrg.orgRole ?? null : null),
4729
4762
  navDisplay,
4730
4763
  setNavDisplay
4731
4764
  }),
4732
- [orgId, setOrgId, orgs, tokenOrg.orgs, orgsFetching, orgsError, fetchedOrgs, pendingOrg, navDisplay, setNavDisplay]
4765
+ [orgId, setOrgId, orgs, tokenOrg.orgs, tokenOrg.orgId, tokenOrg.orgRole, orgsFetching, orgsError, fetchedOrgs, pendingOrg, navDisplay, setNavDisplay]
4733
4766
  );
4734
4767
  return /* @__PURE__ */ jsxRuntime.jsx(ChromeStateContext.Provider, { value, children });
4735
4768
  }
@@ -4883,6 +4916,9 @@ function ChromeStateProvider({
4883
4916
  })),
4884
4917
  orgsLoading: false,
4885
4918
  orgPending: false,
4919
+ // The app's list collapses roles (Contacts: admin | member); the
4920
+ // chrome API's list carries the raw one.
4921
+ activeRawRole: controlledOrg.orgs?.find((o) => o.id === controlledOrg.orgId)?.rawRole ?? orgs?.find((o) => o.id === controlledOrg.orgId)?.rawRole ?? null,
4886
4922
  navDisplay,
4887
4923
  setNavDisplay
4888
4924
  };
@@ -4903,6 +4939,7 @@ function ChromeStateProvider({
4903
4939
  // falls back to the app label rather than shimmering forever.
4904
4940
  orgsLoading: orgsFetching && !orgsError && !orgs,
4905
4941
  orgPending: false,
4942
+ activeRawRole: orgs?.find((o) => o.id === orgId)?.rawRole ?? null,
4906
4943
  navDisplay,
4907
4944
  setNavDisplay
4908
4945
  };