@the-portland-company/shell 0.3.0 → 0.3.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,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1 — 2026-05-13
4
+
5
+ Closes the last two visual gaps in the shell-v3 chrome vs production.
6
+
7
+ ### Fixed
8
+
9
+ - **Header icon order**: Removed the `devNotesMenu` slot from
10
+ `<Header>`'s icon row. Previously the shell appended DevNotes (the
11
+ "Task" icon) AFTER `headerIconsSlot`, forcing it to the rightmost
12
+ position. That broke production order (Fuel → Stack → Bell → Task →
13
+ Support → Avatar). Apps must now inline DevNotesMenu inside
14
+ `headerIconsSlot` at the position they want. The `devNotesMenu` prop
15
+ on `<ShellChrome>` is still accepted for backward compat but is
16
+ ignored at render time.
17
+ - **Sidebar footer commit info**: Replaced the plain-text
18
+ `"staging v2.34.1 #51e4e3f"` row with a full port of the legacy
19
+ production `CommitCopyButton` (env chip + last-4 SHA + version +
20
+ optional diagnostics icon, plus a second-line byline `| Mon D |
21
+ firstName |`). Clicking the env chip copies the full SHA. Hovering
22
+ shows the full commit message body when `commitLog` is supplied.
23
+
24
+ ### Added
25
+
26
+ - `<CommitCopyButton>` — new exported component. Ported from
27
+ `react/app/src/layouts/AuthenticatedLayout.tsx::CommitCopyButton`
28
+ (lines 3017–3284). Accepts `envNode` (pre-rendered chip),
29
+ `environmentLabel` (default styled chip — green/orange/red for
30
+ local/staging/production), `commit`, `version`, `commitDate`,
31
+ `commitAuthor`, `commitLog`, and `onDiagnosticsClick`. Used by the
32
+ shell's `<Sidebar>` footer; also exported for apps that want to mount
33
+ it elsewhere.
34
+ - `ShellFooterValue.envNode?: ReactNode` — pre-rendered chip override
35
+ (useful when the app already has its own `EnvironmentBanner`).
36
+ - `ShellFooterValue.onDiagnosticsClick?: () => void` — gates the
37
+ diagnostics icon. When unset, the icon is omitted.
38
+
39
+ ### Doc-only
40
+
41
+ - `ShellProviderProps.support` doc no longer claims the shell renders
42
+ a `<SupportRequestButton />` automatically. It never did — apps wire
43
+ their own support trigger via `headerIconsSlot`.
44
+
3
45
  ## 0.3.0 — 2026-05-13
4
46
 
5
47
  Visual-parity rebuild. Ports the legacy production sidebar + header chrome
package/dist/index.cjs CHANGED
@@ -1450,8 +1450,7 @@ function Header({ onOpen, id }) {
1450
1450
  breadcrumbs,
1451
1451
  headerActions,
1452
1452
  headerIconsSlot,
1453
- brandLogoSrc,
1454
- devNotesMenu
1453
+ brandLogoSrc
1455
1454
  } = useShellChrome();
1456
1455
  const mobileNavBg = react.useColorModeValue("white", "gray.900");
1457
1456
  const mobileNavBorderColor = react.useColorModeValue("gray.200", "gray.700");
@@ -1527,8 +1526,7 @@ function Header({ onOpen, id }) {
1527
1526
  },
1528
1527
  children: [
1529
1528
  headerActions,
1530
- headerIconsSlot,
1531
- devNotesMenu
1529
+ headerIconsSlot
1532
1530
  ]
1533
1531
  }
1534
1532
  ),
@@ -2184,6 +2182,237 @@ function ModePill() {
2184
2182
  ] }) });
2185
2183
  }
2186
2184
  var ModePill_default = ModePill;
2185
+ function ActivityIcon() {
2186
+ return /* @__PURE__ */ jsxRuntime.jsx(
2187
+ "svg",
2188
+ {
2189
+ width: "12",
2190
+ height: "12",
2191
+ viewBox: "0 0 24 24",
2192
+ fill: "none",
2193
+ stroke: "currentColor",
2194
+ strokeWidth: "2",
2195
+ strokeLinecap: "round",
2196
+ strokeLinejoin: "round",
2197
+ "aria-hidden": "true",
2198
+ children: /* @__PURE__ */ jsxRuntime.jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" })
2199
+ }
2200
+ );
2201
+ }
2202
+ function defaultEnvChip(label) {
2203
+ if (!label) return null;
2204
+ const lower = label.toLowerCase();
2205
+ if (lower.includes("prod")) {
2206
+ return { color: "#ef4444", bg: "#fee2e2", border: "#fecaca", name: label };
2207
+ }
2208
+ if (lower.includes("stag")) {
2209
+ return { color: "#f97316", bg: "#ffedd5", border: "#fed7aa", name: label };
2210
+ }
2211
+ if (lower.includes("local") || lower.includes("dev")) {
2212
+ return { color: "#22c55e", bg: "#dcfce7", border: "#bbf7d0", name: label };
2213
+ }
2214
+ return { color: "#6b7280", bg: "#f3f4f6", border: "#e5e7eb", name: label };
2215
+ }
2216
+ var PLACEHOLDER_SHA_VALUES = /* @__PURE__ */ new Set(["unknown", "n/a", "null", "undefined", ""]);
2217
+ function isMeaningfulCommit(value) {
2218
+ if (typeof value !== "string") return false;
2219
+ const trimmed = value.trim();
2220
+ if (!trimmed) return false;
2221
+ return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2222
+ }
2223
+ function isMeaningfulAuthor(value) {
2224
+ if (typeof value !== "string") return false;
2225
+ const trimmed = value.trim();
2226
+ if (!trimmed) return false;
2227
+ return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2228
+ }
2229
+ function getFirstName(value) {
2230
+ if (!value) return "";
2231
+ const stripped = value.replace(/<[^>]+>/g, "").trim();
2232
+ if (!stripped) return "";
2233
+ const [firstToken] = stripped.split(/\s+/);
2234
+ return firstToken || stripped;
2235
+ }
2236
+ function CommitCopyButton({
2237
+ envNode,
2238
+ environmentLabel,
2239
+ commit,
2240
+ version = "",
2241
+ commitDate = "",
2242
+ commitAuthor = "",
2243
+ commitLog = "",
2244
+ onDiagnosticsClick
2245
+ }) {
2246
+ const safeCommit = isMeaningfulCommit(commit) ? commit.trim() : "";
2247
+ const { hasCopied, onCopy } = react.useClipboard(safeCommit);
2248
+ const shortCommit = safeCommit ? safeCommit.slice(-4) : "";
2249
+ const copiedAnimation = react$2.keyframes`
2250
+ 0% { opacity: 0; transform: translateY(6px); }
2251
+ 20% { opacity: 1; transform: translateY(0); }
2252
+ 100% { opacity: 0; transform: translateY(-8px); }
2253
+ `;
2254
+ const formattedDate = react$1.useMemo(() => {
2255
+ if (!commitDate) return "";
2256
+ const parsed = new Date(commitDate);
2257
+ if (Number.isNaN(parsed.getTime())) return commitDate;
2258
+ return parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2259
+ }, [commitDate]);
2260
+ const metaLine = react$1.useMemo(() => {
2261
+ const firstName = isMeaningfulAuthor(commitAuthor) ? getFirstName(commitAuthor) : "";
2262
+ return [formattedDate, firstName].filter(Boolean).join(" | ");
2263
+ }, [formattedDate, commitAuthor]);
2264
+ const normalizedCommitLog = react$1.useMemo(() => {
2265
+ if (commitLog?.trim()) {
2266
+ return commitLog.replace(/\r\n/g, "\n").replace(/\\n/g, "\n");
2267
+ }
2268
+ return "";
2269
+ }, [commitLog]);
2270
+ const tooltipContent = normalizedCommitLog.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
2271
+ react.Box,
2272
+ {
2273
+ maxW: "600px",
2274
+ width: "min(600px, 90vw)",
2275
+ maxH: "80vh",
2276
+ whiteSpace: "pre-wrap",
2277
+ overflowY: "auto",
2278
+ overflowX: "auto",
2279
+ fontSize: "xs",
2280
+ fontFamily: "mono",
2281
+ wordBreak: "break-word",
2282
+ children: normalizedCommitLog
2283
+ }
2284
+ ) : void 0;
2285
+ const envChip = react$1.useMemo(() => {
2286
+ if (envNode) return envNode;
2287
+ const chip = defaultEnvChip(environmentLabel);
2288
+ if (!chip) return null;
2289
+ return /* @__PURE__ */ jsxRuntime.jsx(
2290
+ react.Box,
2291
+ {
2292
+ as: "span",
2293
+ display: "inline-flex",
2294
+ alignItems: "center",
2295
+ px: "1.5",
2296
+ py: "0.5",
2297
+ borderWidth: "1px",
2298
+ borderColor: chip.border,
2299
+ bg: chip.bg,
2300
+ color: chip.color,
2301
+ borderRadius: "md",
2302
+ fontSize: "2xs",
2303
+ fontWeight: "600",
2304
+ lineHeight: "1",
2305
+ textTransform: "capitalize",
2306
+ "aria-label": `Environment: ${chip.name}`,
2307
+ children: chip.name
2308
+ }
2309
+ );
2310
+ }, [envNode, environmentLabel]);
2311
+ const showEnv = Boolean(envChip);
2312
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2313
+ react.VStack,
2314
+ {
2315
+ justify: "center",
2316
+ align: "center",
2317
+ borderRadius: "md",
2318
+ py: 0.5,
2319
+ px: 1,
2320
+ position: "relative",
2321
+ spacing: 0,
2322
+ textAlign: "center",
2323
+ w: "full",
2324
+ children: [
2325
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Flex, { justify: "center", align: "center", gap: 2, whiteSpace: "nowrap", children: [
2326
+ showEnv ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2327
+ /* @__PURE__ */ jsxRuntime.jsx(react.Box, { flexShrink: 0, children: /* @__PURE__ */ jsxRuntime.jsx(
2328
+ react.Tooltip,
2329
+ {
2330
+ label: tooltipContent,
2331
+ isDisabled: !tooltipContent,
2332
+ hasArrow: true,
2333
+ placement: "top-start",
2334
+ openDelay: 150,
2335
+ closeOnClick: false,
2336
+ shouldWrapChildren: true,
2337
+ modifiers: [
2338
+ { name: "preventOverflow", options: { padding: 16 } },
2339
+ { name: "offset", options: { offset: [0, 12] } }
2340
+ ],
2341
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2342
+ react.Button,
2343
+ {
2344
+ variant: "unstyled",
2345
+ minW: "auto",
2346
+ h: "auto",
2347
+ p: 0,
2348
+ lineHeight: "1",
2349
+ color: "inherit",
2350
+ onClick: onCopy,
2351
+ "aria-label": "Copy commit ID",
2352
+ isDisabled: !safeCommit,
2353
+ children: envChip
2354
+ }
2355
+ )
2356
+ }
2357
+ ) }),
2358
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" })
2359
+ ] }) : null,
2360
+ shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: hasCopied ? "green.500" : "gray.400", flexShrink: 0, children: shortCommit }) : null,
2361
+ version ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2362
+ shortCommit ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }) : null,
2363
+ /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "2xs", color: "gray.400", flexShrink: 0, children: [
2364
+ "v",
2365
+ version
2366
+ ] })
2367
+ ] }) : null,
2368
+ onDiagnosticsClick ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2369
+ /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }),
2370
+ /* @__PURE__ */ jsxRuntime.jsx(react.Tooltip, { label: "Diagnostics", hasArrow: true, placement: "top", children: /* @__PURE__ */ jsxRuntime.jsx(
2371
+ react.Button,
2372
+ {
2373
+ variant: "unstyled",
2374
+ minW: "auto",
2375
+ h: "auto",
2376
+ p: 0,
2377
+ lineHeight: "1",
2378
+ color: "gray.400",
2379
+ _hover: { color: "gray.600" },
2380
+ onClick: (e) => {
2381
+ e.stopPropagation();
2382
+ onDiagnosticsClick();
2383
+ },
2384
+ "aria-label": "Diagnostics",
2385
+ children: /* @__PURE__ */ jsxRuntime.jsx(react.Icon, { as: ActivityIcon, boxSize: 3 })
2386
+ }
2387
+ ) })
2388
+ ] }) : null
2389
+ ] }),
2390
+ metaLine ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontSize: "2xs", color: "gray.500", textAlign: "center", whiteSpace: "nowrap", children: [
2391
+ "| ",
2392
+ metaLine,
2393
+ " |"
2394
+ ] }) : null,
2395
+ hasCopied && /* @__PURE__ */ jsxRuntime.jsx(
2396
+ react.Box,
2397
+ {
2398
+ position: "absolute",
2399
+ bottom: "100%",
2400
+ mb: "6px",
2401
+ px: "2",
2402
+ py: "1",
2403
+ fontSize: "xs",
2404
+ bg: "gray.700",
2405
+ color: "white",
2406
+ borderRadius: "md",
2407
+ animation: `${copiedAnimation} 0.9s ease-out`,
2408
+ pointerEvents: "none",
2409
+ children: "Copied"
2410
+ }
2411
+ )
2412
+ ]
2413
+ }
2414
+ );
2415
+ }
2187
2416
  function Sidebar({ onClose, id }) {
2188
2417
  const { brandLogoSrc } = useShellChrome();
2189
2418
  const navItems = useShellNavItems()?.items ?? [];
@@ -2316,17 +2545,19 @@ function Sidebar({ onClose, id }) {
2316
2545
  }
2317
2546
  )
2318
2547
  ] }, `${l.label}-${idx}`)) }),
2319
- (footer.version || footer.commit || footer.environmentLabel) && /* @__PURE__ */ jsxRuntime.jsxs(react.HStack, { spacing: 2, fontSize: "xs", color: "gray.500", wrap: "wrap", children: [
2320
- footer.environmentLabel ? /* @__PURE__ */ jsxRuntime.jsx(react.Text, { fontWeight: "semibold", children: footer.environmentLabel }) : null,
2321
- footer.version ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { children: [
2322
- "v",
2323
- footer.version
2324
- ] }) : null,
2325
- footer.commit ? /* @__PURE__ */ jsxRuntime.jsxs(react.Text, { fontFamily: "mono", children: [
2326
- "#",
2327
- footer.commit.slice(-7)
2328
- ] }) : null
2329
- ] })
2548
+ (footer.version || footer.commit || footer.commitFull || footer.environmentLabel || footer.envNode) && /* @__PURE__ */ jsxRuntime.jsx(
2549
+ CommitCopyButton,
2550
+ {
2551
+ envNode: footer.envNode,
2552
+ environmentLabel: footer.environmentLabel,
2553
+ commit: footer.commitFull ?? footer.commit,
2554
+ version: footer.version,
2555
+ commitDate: footer.commitDate,
2556
+ commitAuthor: footer.commitAuthor,
2557
+ commitLog: footer.commitLog,
2558
+ onDiagnosticsClick: footer.onDiagnosticsClick
2559
+ }
2560
+ )
2330
2561
  ] })
2331
2562
  }
2332
2563
  )
@@ -2750,6 +2981,7 @@ exports.BrandIcon = BrandIcon;
2750
2981
  exports.BrandLogo = BrandLogo;
2751
2982
  exports.BreadcrumbBar = BreadcrumbBar;
2752
2983
  exports.Card = Card;
2984
+ exports.CommitCopyButton = CommitCopyButton;
2753
2985
  exports.Footer = Footer;
2754
2986
  exports.GlobalUiIdProvider = GlobalUiIdProvider;
2755
2987
  exports.Header = Header;