@the-portland-company/shell 0.81.2 → 0.84.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/app-chrome.cjs +273 -181
- package/dist/app-chrome.cjs.map +1 -1
- package/dist/app-chrome.d.cts +32 -2
- package/dist/app-chrome.d.ts +32 -2
- package/dist/app-chrome.js +51 -13
- package/dist/app-chrome.js.map +1 -1
- package/dist/chrome.css +5 -0
- package/dist/{chunk-L73FZKGE.js → chunk-PRZP2SB6.js} +229 -176
- package/dist/chunk-PRZP2SB6.js.map +1 -0
- package/dist/{chunk-MYY6N6D4.js → chunk-QS3Z4N73.js} +3 -3
- package/dist/chunk-QS3Z4N73.js.map +1 -0
- package/dist/{index-_Y7jOKPN.d.cts → index--q40spnY.d.cts} +66 -4
- package/dist/{index-bYu6DxNt.d.ts → index-C4ob05Pc.d.ts} +66 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/native.cjs +233 -172
- package/dist/native.cjs.map +1 -1
- package/dist/native.d.cts +1 -1
- package/dist/native.d.ts +1 -1
- package/dist/native.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-L73FZKGE.js.map +0 -1
- package/dist/chunk-MYY6N6D4.js.map +0 -1
package/dist/app-chrome.cjs
CHANGED
|
@@ -423,6 +423,81 @@ function SpeculationRulesScript({ items }) {
|
|
|
423
423
|
return null;
|
|
424
424
|
}
|
|
425
425
|
|
|
426
|
+
// src/native/credits.ts
|
|
427
|
+
var CREDIT_TYPE_ORDER = [
|
|
428
|
+
"signal",
|
|
429
|
+
"broadcast",
|
|
430
|
+
"ai",
|
|
431
|
+
"enhancement"
|
|
432
|
+
];
|
|
433
|
+
var CREDIT_TYPE_LABELS = {
|
|
434
|
+
signal: "Signal Credits",
|
|
435
|
+
broadcast: "Broadcast Credits",
|
|
436
|
+
ai: "AI Credits",
|
|
437
|
+
enhancement: "Enhancement Credits"
|
|
438
|
+
};
|
|
439
|
+
var RANK = {
|
|
440
|
+
unknown: 0,
|
|
441
|
+
good: 1,
|
|
442
|
+
low: 2,
|
|
443
|
+
out: 3
|
|
444
|
+
};
|
|
445
|
+
function creditsIconState(channels) {
|
|
446
|
+
let worst = "unknown";
|
|
447
|
+
for (const channel of channels) {
|
|
448
|
+
const state = channel?.state ?? null;
|
|
449
|
+
if (state === null || state === "not_in_use") continue;
|
|
450
|
+
if (RANK[state] > RANK[worst]) worst = state;
|
|
451
|
+
}
|
|
452
|
+
return worst;
|
|
453
|
+
}
|
|
454
|
+
function creditGaugeRatio(channel) {
|
|
455
|
+
if (!channel || channel.state == null || channel.state === "not_in_use")
|
|
456
|
+
return null;
|
|
457
|
+
const fullMark = channel.fullMarkUnits ?? 0;
|
|
458
|
+
if (!(fullMark > 0)) return null;
|
|
459
|
+
return Math.min(1, Math.max(0, channel.remainingUnits / fullMark));
|
|
460
|
+
}
|
|
461
|
+
function creditStateWord(state) {
|
|
462
|
+
switch (state) {
|
|
463
|
+
case "good":
|
|
464
|
+
return "Good";
|
|
465
|
+
case "low":
|
|
466
|
+
return "Low";
|
|
467
|
+
case "out":
|
|
468
|
+
return "Out";
|
|
469
|
+
case "not_in_use":
|
|
470
|
+
return "Not in use";
|
|
471
|
+
default:
|
|
472
|
+
return "Unknown";
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function daysPhrase(days) {
|
|
476
|
+
if (days < 1) return "less than a day at your recent pace";
|
|
477
|
+
const rounded = Math.round(days);
|
|
478
|
+
return `about ${rounded} ${rounded === 1 ? "day" : "days"} at your recent pace`;
|
|
479
|
+
}
|
|
480
|
+
function creditTooltipLine(type, channel) {
|
|
481
|
+
const label = CREDIT_TYPE_LABELS[type];
|
|
482
|
+
if (!channel) return `${label}: unavailable`;
|
|
483
|
+
const left = `${Math.max(0, channel.remainingUnits).toLocaleString()} left`;
|
|
484
|
+
if (channel.state === "not_in_use") return `${label}: not in use`;
|
|
485
|
+
if (channel.state == null) return `${label}: ${left}`;
|
|
486
|
+
if (channel.state === "out") return `${label}: ${left}, out`;
|
|
487
|
+
const days = channel.daysOfUseLeft;
|
|
488
|
+
const pace = typeof days === "number" && Number.isFinite(days) ? `, ${daysPhrase(days)}` : "";
|
|
489
|
+
return `${label}: ${left}${pace}`;
|
|
490
|
+
}
|
|
491
|
+
function creditsAriaLabel(channels) {
|
|
492
|
+
const worst = creditsIconState(CREDIT_TYPE_ORDER.map((t) => channels[t]));
|
|
493
|
+
if (worst === "unknown") return "Credits";
|
|
494
|
+
if (worst === "good") return "Credits: all good";
|
|
495
|
+
const named = CREDIT_TYPE_ORDER.filter(
|
|
496
|
+
(t) => channels[t]?.state === worst
|
|
497
|
+
).map((t) => CREDIT_TYPE_LABELS[t]);
|
|
498
|
+
return `Credits: ${worst === "low" ? "low on" : "out of"} ${named.join(", ")}`;
|
|
499
|
+
}
|
|
500
|
+
|
|
426
501
|
// src/notifications/adapter.ts
|
|
427
502
|
function emptyNotificationsFeed() {
|
|
428
503
|
return { notifications: [], badge: 0 };
|
|
@@ -517,7 +592,7 @@ function resolveOrgLogoUrl(url) {
|
|
|
517
592
|
}
|
|
518
593
|
var TOPBAR_GLYPH_SIZE = 17;
|
|
519
594
|
function topBarIconButtonClass(tone = "default") {
|
|
520
|
-
const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--chrome-danger)] hover:text-[color:var(--chrome-danger)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
|
|
595
|
+
const toneClass = tone === "danger" || tone === "warning" ? "text-[color:var(--chrome-danger)] hover:text-[color:var(--chrome-danger)]" : tone === "ok" ? "text-[color:var(--credits-good,#00783B)] hover:text-[color:var(--credits-good,#00783B)]" : tone === "low" ? "text-[color:var(--credits-low,#B7791F)] hover:text-[color:var(--credits-low,#B7791F)]" : "text-[color:var(--topbar-icon-color,var(--muted))] hover:text-[color:var(--topbar-icon-hover-color,var(--foreground))] hover:bg-[color:var(--topbar-icon-hover-bg,transparent)]";
|
|
521
596
|
return [
|
|
522
597
|
// `group/tbtip` powers the shared <TopBarTooltip> rendered inside the trigger.
|
|
523
598
|
"group/tbtip relative flex items-center justify-center rounded-[var(--topbar-icon-button-radius,5px)] border border-transparent transition",
|
|
@@ -746,6 +821,9 @@ function ValueSkeleton({ className }) {
|
|
|
746
821
|
}
|
|
747
822
|
);
|
|
748
823
|
}
|
|
824
|
+
function IconGlyph({ icon: Icon, size = TOPBAR_GLYPH_SIZE }) {
|
|
825
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Icon, { size, className: "text-current" });
|
|
826
|
+
}
|
|
749
827
|
function SlotGlyph({
|
|
750
828
|
slot,
|
|
751
829
|
fallback: Fallback,
|
|
@@ -758,9 +836,12 @@ function SlotGlyph({
|
|
|
758
836
|
}
|
|
759
837
|
function TopBarPopover({
|
|
760
838
|
label,
|
|
839
|
+
ariaLabel,
|
|
761
840
|
description,
|
|
841
|
+
tooltip,
|
|
762
842
|
slot,
|
|
763
843
|
fallbackIcon,
|
|
844
|
+
icon,
|
|
764
845
|
badge,
|
|
765
846
|
tone,
|
|
766
847
|
onOpen,
|
|
@@ -810,7 +891,7 @@ function TopBarPopover({
|
|
|
810
891
|
"button",
|
|
811
892
|
{
|
|
812
893
|
type: "button",
|
|
813
|
-
"aria-label": label,
|
|
894
|
+
"aria-label": ariaLabel ?? label,
|
|
814
895
|
"aria-expanded": open,
|
|
815
896
|
className: topBarIconButtonClass(tone),
|
|
816
897
|
onClick: () => {
|
|
@@ -821,9 +902,9 @@ function TopBarPopover({
|
|
|
821
902
|
});
|
|
822
903
|
},
|
|
823
904
|
children: [
|
|
824
|
-
/* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
|
|
905
|
+
icon ? /* @__PURE__ */ jsxRuntime.jsx(IconGlyph, { icon }) : /* @__PURE__ */ jsxRuntime.jsx(SlotGlyph, { slot, fallback: fallbackIcon }),
|
|
825
906
|
typeof badge === "number" ? /* @__PURE__ */ jsxRuntime.jsx(TopBarCountBadge, { count: badge }) : null,
|
|
826
|
-
!open && !hideTooltip ? /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: label, description }) : null
|
|
907
|
+
!open && !hideTooltip ? /* @__PURE__ */ jsxRuntime.jsx(TopBarTooltip, { title: label, description: tooltip ?? description }) : null
|
|
827
908
|
]
|
|
828
909
|
}
|
|
829
910
|
),
|
|
@@ -897,147 +978,145 @@ function XGlyph({ size = 14, className }) {
|
|
|
897
978
|
function CheckGlyph({ size = 14, className }) {
|
|
898
979
|
return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", className, "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "20 6 9 17 4 12" }) });
|
|
899
980
|
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
981
|
+
function batteryFillWidth(state) {
|
|
982
|
+
switch (state) {
|
|
983
|
+
case "good":
|
|
984
|
+
return 12;
|
|
985
|
+
case "low":
|
|
986
|
+
return 5;
|
|
987
|
+
default:
|
|
988
|
+
return 0;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function creditsBatteryGlyph(state) {
|
|
992
|
+
const fill = batteryFillWidth(state);
|
|
993
|
+
const Glyph = ({ size = TOPBAR_GLYPH_SIZE, className }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
994
|
+
"svg",
|
|
995
|
+
{
|
|
996
|
+
width: size,
|
|
997
|
+
height: size,
|
|
998
|
+
viewBox: "0 0 24 24",
|
|
999
|
+
fill: "none",
|
|
1000
|
+
stroke: "currentColor",
|
|
1001
|
+
strokeWidth: 2,
|
|
1002
|
+
strokeLinecap: "round",
|
|
1003
|
+
strokeLinejoin: "round",
|
|
1004
|
+
className,
|
|
1005
|
+
"data-credits-state": state,
|
|
1006
|
+
"aria-hidden": true,
|
|
1007
|
+
children: [
|
|
1008
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "1", y: "6", width: "18", height: "12", rx: "2", ry: "2" }),
|
|
1009
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "23", y1: "10", x2: "23", y2: "14" }),
|
|
1010
|
+
fill > 0 ? /* @__PURE__ */ jsxRuntime.jsx("rect", { x: "4", y: "9", width: fill, height: "6", rx: "1", fill: "currentColor", stroke: "none", opacity: 0.35 }) : null,
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M11.5 8 8.5 12.5h3L10.5 16l3.5-4.5h-3L11.5 8z", fill: "currentColor", stroke: "none" })
|
|
1012
|
+
]
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
Glyph.displayName = `CreditsBatteryGlyph(${state})`;
|
|
1016
|
+
return Glyph;
|
|
1017
|
+
}
|
|
1018
|
+
function stateTone(state) {
|
|
1019
|
+
switch (state) {
|
|
1020
|
+
case "good":
|
|
1021
|
+
return "ok";
|
|
1022
|
+
case "low":
|
|
1023
|
+
return "low";
|
|
1024
|
+
case "out":
|
|
1025
|
+
return "danger";
|
|
1026
|
+
default:
|
|
1027
|
+
return "default";
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function stateColorVar(state) {
|
|
1031
|
+
switch (state) {
|
|
1032
|
+
case "good":
|
|
1033
|
+
return "var(--credits-good,#00783B)";
|
|
1034
|
+
case "low":
|
|
1035
|
+
return "var(--credits-low,#B7791F)";
|
|
1036
|
+
case "out":
|
|
1037
|
+
return "var(--chrome-danger)";
|
|
1038
|
+
default:
|
|
1039
|
+
return "var(--muted)";
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
function CreditGauge({ channel, className }) {
|
|
1043
|
+
const ratio = creditGaugeRatio(channel);
|
|
1044
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1045
|
+
"div",
|
|
1046
|
+
{
|
|
1047
|
+
role: "meter",
|
|
1048
|
+
"aria-label": `${creditStateWord(channel?.state)} level`,
|
|
1049
|
+
"aria-valuemin": 0,
|
|
1050
|
+
"aria-valuemax": 100,
|
|
1051
|
+
"aria-valuenow": ratio === null ? void 0 : Math.round(ratio * 100),
|
|
1052
|
+
className: ["h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", className ?? ""].join(" "),
|
|
1053
|
+
children: ratio === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
1054
|
+
"div",
|
|
1055
|
+
{
|
|
1056
|
+
className: "h-full rounded-full transition-[width]",
|
|
1057
|
+
style: { width: `${Math.round(ratio * 100)}%`, backgroundColor: stateColorVar(channel?.state) }
|
|
1058
|
+
}
|
|
1059
|
+
)
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
function formatGrantDate(iso) {
|
|
1064
|
+
if (!iso) return null;
|
|
1065
|
+
const t = Date.parse(iso);
|
|
1066
|
+
if (!Number.isFinite(t)) return null;
|
|
1067
|
+
return new Date(t).toLocaleDateString("en-US", { month: "short", day: "numeric" });
|
|
1068
|
+
}
|
|
1069
|
+
function CreditRow({
|
|
1070
|
+
type,
|
|
903
1071
|
channel,
|
|
904
1072
|
loading,
|
|
905
|
-
addHref
|
|
906
|
-
addLabel
|
|
1073
|
+
addHref
|
|
907
1074
|
}) {
|
|
1075
|
+
const label = CREDIT_TYPE_LABELS[type];
|
|
908
1076
|
const remaining = channel?.remainingUnits ?? 0;
|
|
909
|
-
const
|
|
910
|
-
const
|
|
911
|
-
const
|
|
912
|
-
|
|
1077
|
+
const state = channel?.state ?? null;
|
|
1078
|
+
const unavailable = !loading && !channel;
|
|
1079
|
+
const nextGrant = formatGrantDate(channel?.nextGrantAt);
|
|
1080
|
+
const pools = channel && typeof channel.includedUnits === "number" && typeof channel.purchasedUnits === "number" ? `Included ${channel.includedUnits.toLocaleString()}${nextGrant ? ` (next ${nextGrant})` : ""} \xB7 Purchased ${channel.purchasedUnits.toLocaleString()}` : null;
|
|
1081
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full", "data-credit-type": type, children: [
|
|
913
1082
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [
|
|
914
1083
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-[color:var(--foreground)]", children: label }),
|
|
915
1084
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
916
|
-
loading ? /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-3.5 w-14" }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
" left"
|
|
926
|
-
]
|
|
927
|
-
}
|
|
928
|
-
),
|
|
929
|
-
addHref ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
1085
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(ValueSkeleton, { className: "h-3.5 w-14" }) : unavailable ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-[color:var(--muted)]", children: "Unavailable" }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs font-semibold tabular-nums", style: { color: stateColorVar(state) }, children: [
|
|
1086
|
+
remaining.toLocaleString(),
|
|
1087
|
+
" left",
|
|
1088
|
+
state === "not_in_use" ? null : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-normal text-[color:var(--muted)]", children: [
|
|
1089
|
+
" \xB7 ",
|
|
1090
|
+
creditStateWord(state)
|
|
1091
|
+
] })
|
|
1092
|
+
] }),
|
|
1093
|
+
addHref && !unavailable ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
930
1094
|
"a",
|
|
931
1095
|
{
|
|
932
1096
|
href: addHref,
|
|
933
|
-
"aria-label":
|
|
1097
|
+
"aria-label": `Add ${label}`,
|
|
934
1098
|
className: "flex h-5 w-5 items-center justify-center rounded-full border border-[color:var(--line)] text-[color:var(--muted)] transition hover:border-[color:var(--foreground)] hover:text-[color:var(--foreground)]",
|
|
935
1099
|
children: /* @__PURE__ */ jsxRuntime.jsx(PlusGlyph, {})
|
|
936
1100
|
}
|
|
937
1101
|
) : null
|
|
938
1102
|
] })
|
|
939
1103
|
] }),
|
|
940
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", children:
|
|
941
|
-
|
|
942
|
-
{
|
|
943
|
-
className: [
|
|
944
|
-
"h-full rounded-full transition-[width]",
|
|
945
|
-
low ? "bg-[color:var(--chrome-danger)]" : "bg-[color:var(--color-ok,#00783B)]"
|
|
946
|
-
].join(" "),
|
|
947
|
-
style: { width: `${ratio}%` }
|
|
948
|
-
}
|
|
949
|
-
) })
|
|
1104
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-1.5 w-full overflow-hidden rounded-full bg-[color:var(--panel)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full animate-pulse bg-[color:var(--line)]" }) }) : /* @__PURE__ */ jsxRuntime.jsx(CreditGauge, { channel: unavailable ? null : channel }),
|
|
1105
|
+
!loading && state === "not_in_use" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[11px] text-[color:var(--muted)]", children: "Not in use" }) : pools ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-[11px] text-[color:var(--muted)]", children: pools }) : null
|
|
950
1106
|
] });
|
|
951
1107
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1108
|
+
function CreditsTooltip({ fuel }) {
|
|
1109
|
+
return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex w-56 flex-col gap-1.5", "data-testid": "credits-tooltip", children: CREDIT_TYPE_ORDER.map((type) => {
|
|
1110
|
+
const channel = fuel ? fuel[type] ?? null : null;
|
|
1111
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex flex-col gap-0.5", children: [
|
|
1112
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: creditTooltipLine(type, channel) }),
|
|
1113
|
+
/* @__PURE__ */ jsxRuntime.jsx(CreditGauge, { channel, className: "bg-[#FFFFFF]/20" })
|
|
1114
|
+
] }, type);
|
|
1115
|
+
}) });
|
|
958
1116
|
}
|
|
959
|
-
function
|
|
960
|
-
|
|
961
|
-
return
|
|
962
|
-
}
|
|
963
|
-
function useLowFuelHintVisible() {
|
|
964
|
-
return react.useSyncExternalStore(
|
|
965
|
-
subscribeLowFuelHintVisible,
|
|
966
|
-
() => lowFuelHintVisible,
|
|
967
|
-
() => false
|
|
968
|
-
);
|
|
969
|
-
}
|
|
970
|
-
var LOW_FUEL_HINT_CLEARANCE_PX = 96;
|
|
971
|
-
function LowFuelHint({ lowFuel, orgId }) {
|
|
972
|
-
const [dismissed, setDismissed] = react.useState(() => {
|
|
973
|
-
if (typeof window === "undefined") return false;
|
|
974
|
-
try {
|
|
975
|
-
return window.sessionStorage.getItem(LOW_FUEL_HINT_DISMISSED_KEY) === "1";
|
|
976
|
-
} catch {
|
|
977
|
-
return false;
|
|
978
|
-
}
|
|
979
|
-
});
|
|
980
|
-
react.useEffect(() => {
|
|
981
|
-
if (lowFuel) return;
|
|
982
|
-
try {
|
|
983
|
-
window.sessionStorage.removeItem(LOW_FUEL_HINT_DISMISSED_KEY);
|
|
984
|
-
} catch {
|
|
985
|
-
}
|
|
986
|
-
}, [lowFuel]);
|
|
987
|
-
const panelOpen = useAnyTopBarPanelOpen();
|
|
988
|
-
const showing = lowFuel && !dismissed && !panelOpen;
|
|
989
|
-
react.useEffect(() => {
|
|
990
|
-
setLowFuelHintVisible(showing);
|
|
991
|
-
return () => setLowFuelHintVisible(false);
|
|
992
|
-
}, [showing]);
|
|
993
|
-
if (!showing) return null;
|
|
994
|
-
const dismiss = () => {
|
|
995
|
-
try {
|
|
996
|
-
window.sessionStorage.setItem(LOW_FUEL_HINT_DISMISSED_KEY, "1");
|
|
997
|
-
} catch {
|
|
998
|
-
}
|
|
999
|
-
setDismissed(true);
|
|
1000
|
-
};
|
|
1001
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1002
|
-
"div",
|
|
1003
|
-
{
|
|
1004
|
-
role: "status",
|
|
1005
|
-
className: "absolute right-0 top-[calc(100%+10px)] z-[60] w-60 rounded-lg border border-[color:var(--chrome-danger)] bg-[color:var(--chrome-danger)] px-3 py-2 text-[#FFFFFF] shadow-[var(--shadow-soft)]",
|
|
1006
|
-
onClick: (e) => e.stopPropagation(),
|
|
1007
|
-
children: [
|
|
1008
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1009
|
-
"span",
|
|
1010
|
-
{
|
|
1011
|
-
"aria-hidden": true,
|
|
1012
|
-
className: "absolute -top-[5px] right-[11px] h-2.5 w-2.5 rotate-45 bg-[color:var(--chrome-danger)]"
|
|
1013
|
-
}
|
|
1014
|
-
),
|
|
1015
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [
|
|
1016
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
1017
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold", children: "You\u2019re low on fuel" }),
|
|
1018
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1019
|
-
"a",
|
|
1020
|
-
{
|
|
1021
|
-
href: withOrgParam(CORE_BILLING_URL, orgId),
|
|
1022
|
-
className: "mt-1 inline-block text-sm font-bold underline hover:opacity-90",
|
|
1023
|
-
children: "Buy More Fuel"
|
|
1024
|
-
}
|
|
1025
|
-
)
|
|
1026
|
-
] }),
|
|
1027
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1028
|
-
"button",
|
|
1029
|
-
{
|
|
1030
|
-
type: "button",
|
|
1031
|
-
"aria-label": "Dismiss low fuel notice",
|
|
1032
|
-
onClick: dismiss,
|
|
1033
|
-
className: "flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[#FFFFFF]/90 transition hover:bg-[#FFFFFF]/20",
|
|
1034
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(XGlyph, {})
|
|
1035
|
-
}
|
|
1036
|
-
)
|
|
1037
|
-
] })
|
|
1038
|
-
]
|
|
1039
|
-
}
|
|
1040
|
-
);
|
|
1117
|
+
function channelsOf(fuel) {
|
|
1118
|
+
if (!fuel) return {};
|
|
1119
|
+
return { signal: fuel.signal, broadcast: fuel.broadcast, ai: fuel.ai, enhancement: fuel.enhancement };
|
|
1041
1120
|
}
|
|
1042
1121
|
function FuelAction({ orgId }) {
|
|
1043
1122
|
const { dedupedFetch } = useNavChrome("FuelAction");
|
|
@@ -1069,50 +1148,25 @@ function FuelAction({ orgId }) {
|
|
|
1069
1148
|
cancelled = true;
|
|
1070
1149
|
};
|
|
1071
1150
|
}, [dedupedFetch, orgId]);
|
|
1072
|
-
const
|
|
1073
|
-
const
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
FuelRow,
|
|
1092
|
-
{
|
|
1093
|
-
label: "SMS Signal",
|
|
1094
|
-
channel: fuel?.signal ?? null,
|
|
1095
|
-
loading,
|
|
1096
|
-
addHref: withOrgParam(CORE_BILLING_URL, orgId),
|
|
1097
|
-
addLabel: "Add SMS Signal fuel"
|
|
1098
|
-
}
|
|
1099
|
-
),
|
|
1100
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1101
|
-
FuelRow,
|
|
1102
|
-
{
|
|
1103
|
-
label: "Email Broadcast",
|
|
1104
|
-
channel: fuel?.broadcast ?? null,
|
|
1105
|
-
loading,
|
|
1106
|
-
addHref: withOrgParam(CORE_BILLING_URL, orgId),
|
|
1107
|
-
addLabel: "Add Email Broadcast fuel"
|
|
1108
|
-
}
|
|
1109
|
-
)
|
|
1110
|
-
] })
|
|
1111
|
-
] })
|
|
1112
|
-
}
|
|
1113
|
-
),
|
|
1114
|
-
/* @__PURE__ */ jsxRuntime.jsx(LowFuelHint, { lowFuel: low, orgId })
|
|
1115
|
-
] })
|
|
1151
|
+
const channels = channelsOf(fuel);
|
|
1152
|
+
const iconState = creditsIconState(CREDIT_TYPE_ORDER.map((t) => channels[t]));
|
|
1153
|
+
const Glyph = react.useMemo(() => creditsBatteryGlyph(iconState), [iconState]);
|
|
1154
|
+
const addHref = withOrgParam(CORE_BILLING_URL, orgId);
|
|
1155
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1156
|
+
TopBarPopover,
|
|
1157
|
+
{
|
|
1158
|
+
label: "Credits",
|
|
1159
|
+
ariaLabel: creditsAriaLabel(channels),
|
|
1160
|
+
tooltip: /* @__PURE__ */ jsxRuntime.jsx(CreditsTooltip, { fuel }),
|
|
1161
|
+
slot: "comms-fuel",
|
|
1162
|
+
fallbackIcon: BatteryGlyph,
|
|
1163
|
+
icon: Glyph,
|
|
1164
|
+
tone: stateTone(iconState),
|
|
1165
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3", children: [
|
|
1166
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "mb-3 text-[11px] font-semibold uppercase tracking-[0.08em] text-[color:var(--muted)]", children: "Credits" }),
|
|
1167
|
+
error || !loading && !fuel ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-[color:var(--muted)]", children: "Credits are unavailable right now." }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-3", children: CREDIT_TYPE_ORDER.map((type) => /* @__PURE__ */ jsxRuntime.jsx(CreditRow, { type, channel: channels[type] ?? null, loading, addHref }, type)) })
|
|
1168
|
+
] })
|
|
1169
|
+
}
|
|
1116
1170
|
);
|
|
1117
1171
|
}
|
|
1118
1172
|
function notifStateKey(orgId) {
|
|
@@ -1193,7 +1247,6 @@ function NewReplyToast({
|
|
|
1193
1247
|
const [visible, setVisible] = react.useState(false);
|
|
1194
1248
|
const onCloseRef = react.useRef(onClose);
|
|
1195
1249
|
onCloseRef.current = onClose;
|
|
1196
|
-
const lowFuelHintVisible2 = useLowFuelHintVisible();
|
|
1197
1250
|
react.useEffect(() => {
|
|
1198
1251
|
setVisible(true);
|
|
1199
1252
|
const fadeAt = Math.max(0, ttlMs - 450);
|
|
@@ -1223,7 +1276,6 @@ function NewReplyToast({
|
|
|
1223
1276
|
},
|
|
1224
1277
|
className: "absolute right-[-6px] top-[calc(100%+12px)] z-50 min-w-[240px] max-w-[320px] cursor-pointer rounded-xl bg-[#161616] px-4 py-3 text-[#FFFFFF] shadow-[var(--shadow-soft)]",
|
|
1225
1278
|
style: {
|
|
1226
|
-
top: lowFuelHintVisible2 ? `calc(100% + 12px + ${LOW_FUEL_HINT_CLEARANCE_PX}px)` : void 0,
|
|
1227
1279
|
opacity: visible ? 1 : 0,
|
|
1228
1280
|
transform: visible ? "translateY(0px)" : "translateY(-4px)",
|
|
1229
1281
|
transition: "opacity 250ms ease, transform 250ms ease"
|
|
@@ -4614,8 +4666,10 @@ var POLITOGY_APPS = {
|
|
|
4614
4666
|
canvass: { title: "Field Canvassing", origin: "https://canvass.politogyvrm.com", storagePrefix: "canvass", mode: "campaign", grants: ["title"] },
|
|
4615
4667
|
signs: { title: "Sign Mapping", origin: "https://signs.politogyvrm.com", storagePrefix: "signs", mode: "campaign", grants: ["title"] }
|
|
4616
4668
|
};
|
|
4617
|
-
var
|
|
4618
|
-
|
|
4669
|
+
var MODE_SEARCH_PLACEHOLDER = {
|
|
4670
|
+
relationship: "Search contacts, Tribes, forms",
|
|
4671
|
+
campaign: "Search voters, walk lists, polls",
|
|
4672
|
+
petition: "Search petitions, sheets, circulators"
|
|
4619
4673
|
};
|
|
4620
4674
|
function PolitogyAppFrame({
|
|
4621
4675
|
app,
|
|
@@ -4822,7 +4876,7 @@ function FrameBody({
|
|
|
4822
4876
|
{
|
|
4823
4877
|
visible: bootVisible,
|
|
4824
4878
|
bootProgress,
|
|
4825
|
-
syntheticReady: appReady ?? chromeResolved,
|
|
4879
|
+
syntheticReady: (appReady ?? chromeResolved) && !chrome?.orgPending,
|
|
4826
4880
|
onExitComplete: () => setBootVisible(false)
|
|
4827
4881
|
},
|
|
4828
4882
|
bootGeneration
|
|
@@ -4893,7 +4947,9 @@ function FrameBody({
|
|
|
4893
4947
|
// search is the only thing on the left end of the bar. No app wires
|
|
4894
4948
|
// search yet, so this stays decorative (`NavTopBarProps.search` — "an
|
|
4895
4949
|
// app that has not wired search yet still gets the correct chrome").
|
|
4896
|
-
search: {
|
|
4950
|
+
search: {
|
|
4951
|
+
placeholder: data?.searchPlaceholder ?? MODE_SEARCH_PLACEHOLDER[appMode ?? mode.id ?? "relationship"] ?? "Search"
|
|
4952
|
+
},
|
|
4897
4953
|
// No Build notes control (Reference/UI-Platform, decided 2026-10-03).
|
|
4898
4954
|
// The action cluster is IDENTICAL everywhere — core included. Page
|
|
4899
4955
|
// actions sit BEFORE it, never inside it, so the Alerts bell keeps its
|
|
@@ -4910,6 +4966,18 @@ function FrameBody({
|
|
|
4910
4966
|
}
|
|
4911
4967
|
var ORG_STORAGE_KEY = "vrm.activeOrg";
|
|
4912
4968
|
var MODE_STORAGE_KEY = "vrm.activeMode";
|
|
4969
|
+
var orgSwitchLanding = {
|
|
4970
|
+
href(orgId) {
|
|
4971
|
+
return modeLandingHref("relationship", {
|
|
4972
|
+
orgId,
|
|
4973
|
+
hostname: typeof window === "undefined" ? null : window.location.hostname
|
|
4974
|
+
});
|
|
4975
|
+
},
|
|
4976
|
+
navigate(href) {
|
|
4977
|
+
window.location.assign(href);
|
|
4978
|
+
}
|
|
4979
|
+
};
|
|
4980
|
+
var ORG_SWITCH_REEXCHANGE_BUDGET_MS = 4e3;
|
|
4913
4981
|
var ChromeStateContext = react.createContext(null);
|
|
4914
4982
|
function useChromeState() {
|
|
4915
4983
|
return react.useContext(ChromeStateContext);
|
|
@@ -4925,6 +4993,7 @@ function TokenChromeStateProvider({
|
|
|
4925
4993
|
const [pendingOrg, setPendingOrg] = react.useState(null);
|
|
4926
4994
|
const tokenOrgRef = react.useRef(tokenOrg);
|
|
4927
4995
|
tokenOrgRef.current = tokenOrg;
|
|
4996
|
+
const leavingRef = react.useRef(false);
|
|
4928
4997
|
react.useEffect(() => {
|
|
4929
4998
|
setNavDisplayState(resolveNavDisplay());
|
|
4930
4999
|
}, []);
|
|
@@ -4965,6 +5034,7 @@ function TokenChromeStateProvider({
|
|
|
4965
5034
|
}, [overrideOrg, tokenOrg.orgId]);
|
|
4966
5035
|
react.useEffect(() => {
|
|
4967
5036
|
if (!pendingOrg || tokenOrg.orgId !== pendingOrg) return;
|
|
5037
|
+
if (leavingRef.current) return;
|
|
4968
5038
|
setPendingOrg(null);
|
|
4969
5039
|
try {
|
|
4970
5040
|
const next = syncOrgIdInUrl(window.location.href, pendingOrg);
|
|
@@ -4987,8 +5057,16 @@ function TokenChromeStateProvider({
|
|
|
4987
5057
|
window.history.replaceState(window.history.state, "", url.toString());
|
|
4988
5058
|
} catch {
|
|
4989
5059
|
}
|
|
4990
|
-
|
|
5060
|
+
leavingRef.current = true;
|
|
5061
|
+
await Promise.race([
|
|
5062
|
+
Promise.resolve(tokenOrgRef.current.onReexchange(claims.orgId, { perTab: false })).catch(() => {
|
|
5063
|
+
}),
|
|
5064
|
+
new Promise((resolve) => setTimeout(resolve, ORG_SWITCH_REEXCHANGE_BUDGET_MS))
|
|
5065
|
+
]);
|
|
5066
|
+
const href = orgSwitchLanding.href(claims.orgId);
|
|
5067
|
+
if (href) orgSwitchLanding.navigate(href);
|
|
4991
5068
|
} catch (err) {
|
|
5069
|
+
leavingRef.current = false;
|
|
4992
5070
|
setPendingOrg(null);
|
|
4993
5071
|
tokenOrgRef.current.onSwitchError?.(err);
|
|
4994
5072
|
}
|
|
@@ -5053,10 +5131,13 @@ function ChromeStateProvider({
|
|
|
5053
5131
|
const navReconciled = react.useRef(false);
|
|
5054
5132
|
const orgReconciled = react.useRef(false);
|
|
5055
5133
|
const hadBootSelector = react.useRef(false);
|
|
5134
|
+
const bootUrlTail = react.useRef(null);
|
|
5056
5135
|
react.useEffect(() => {
|
|
5057
5136
|
try {
|
|
5137
|
+
const urlSelector = readOrgSelectorFromUrl(window.location.search);
|
|
5138
|
+
if (urlSelector && !isOrgId(urlSelector)) bootUrlTail.current = urlSelector;
|
|
5058
5139
|
const selector = resolveActiveOrg();
|
|
5059
|
-
if (selector) hadBootSelector.current = true;
|
|
5140
|
+
if (selector || urlSelector) hadBootSelector.current = true;
|
|
5060
5141
|
const storedOrg = selector ?? localStorage.getItem(ORG_STORAGE_KEY);
|
|
5061
5142
|
if (storedOrg) setOrgIdState(storedOrg);
|
|
5062
5143
|
} catch {
|
|
@@ -5093,7 +5174,9 @@ function ChromeStateProvider({
|
|
|
5093
5174
|
}, []);
|
|
5094
5175
|
react.useEffect(() => {
|
|
5095
5176
|
if (!orgs || orgs.length === 0) return;
|
|
5096
|
-
|
|
5177
|
+
const fromUrl = matchOrgSelector(bootUrlTail.current, orgs)?.id ?? null;
|
|
5178
|
+
bootUrlTail.current = null;
|
|
5179
|
+
setOrgIdState((prev) => fromUrl ?? (prev && orgs.some((o) => o.id === prev) ? prev : orgs[0].id));
|
|
5097
5180
|
}, [orgs]);
|
|
5098
5181
|
const activeOrgRef = react.useRef(null);
|
|
5099
5182
|
const memberOrgsRef = react.useRef([]);
|
|
@@ -5151,6 +5234,10 @@ function ChromeStateProvider({
|
|
|
5151
5234
|
} catch {
|
|
5152
5235
|
}
|
|
5153
5236
|
writeActiveOrgCookie(id);
|
|
5237
|
+
if (isOrgId(id)) {
|
|
5238
|
+
const href = orgSwitchLanding.href(id);
|
|
5239
|
+
if (href) orgSwitchLanding.navigate(href);
|
|
5240
|
+
}
|
|
5154
5241
|
}, []);
|
|
5155
5242
|
const setNavDisplay = react.useCallback(
|
|
5156
5243
|
(m) => {
|
|
@@ -5217,8 +5304,10 @@ function ChromeStateProvider({
|
|
|
5217
5304
|
}
|
|
5218
5305
|
try {
|
|
5219
5306
|
const url = new URL(window.location.href);
|
|
5220
|
-
|
|
5221
|
-
|
|
5307
|
+
const tail = orgIdTail(activeOrgId);
|
|
5308
|
+
if (url.searchParams.has(ACTIVE_ORG_QUERY_PARAM) || url.searchParams.get(ORG_ID_QUERY_PARAM) !== tail) {
|
|
5309
|
+
url.searchParams.delete(ACTIVE_ORG_QUERY_PARAM);
|
|
5310
|
+
url.searchParams.set(ORG_ID_QUERY_PARAM, tail);
|
|
5222
5311
|
window.history.replaceState(window.history.state, "", url.toString());
|
|
5223
5312
|
}
|
|
5224
5313
|
} catch {
|
|
@@ -5539,6 +5628,7 @@ function useChromeMode(arg = {}) {
|
|
|
5539
5628
|
const selected = selectedModeId ? options.find((m) => m.id === selectedModeId) ?? null : null;
|
|
5540
5629
|
const current = pinned ?? selected ?? options[0] ?? MODE_DEFS.find((m) => m.id === fallbackModeId) ?? MODE_DEFS[0];
|
|
5541
5630
|
return {
|
|
5631
|
+
id: current.id,
|
|
5542
5632
|
label: current.label,
|
|
5543
5633
|
icon: current.icon,
|
|
5544
5634
|
// Left-Navigation §7 / Reference/UI-Platform: always the row of three icon
|
|
@@ -5571,6 +5661,7 @@ function useChromeMode(arg = {}) {
|
|
|
5571
5661
|
|
|
5572
5662
|
exports.ChromeTopBarActions = ChromeTopBarActions;
|
|
5573
5663
|
exports.LoadingScreen = LoadingScreen;
|
|
5664
|
+
exports.MODE_SEARCH_PLACEHOLDER = MODE_SEARCH_PLACEHOLDER;
|
|
5574
5665
|
exports.POLITOGY_APPS = POLITOGY_APPS;
|
|
5575
5666
|
exports.PolitogyAppChrome = PolitogyAppChrome;
|
|
5576
5667
|
exports.PolitogyAppFrame = PolitogyAppFrame;
|
|
@@ -5578,6 +5669,7 @@ exports.deriveBreadcrumbs = deriveBreadcrumbs;
|
|
|
5578
5669
|
exports.footerLinks = footerLinks;
|
|
5579
5670
|
exports.looksLikeEmail = looksLikeEmail;
|
|
5580
5671
|
exports.modeLandingHref = modeLandingHref;
|
|
5672
|
+
exports.orgSwitchLanding = orgSwitchLanding;
|
|
5581
5673
|
exports.resolveIdentity = resolveIdentity;
|
|
5582
5674
|
exports.standardNavIcons = standardNavIcons;
|
|
5583
5675
|
exports.useChromeFooter = useChromeFooter;
|