@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/dist/index.d.cts CHANGED
@@ -228,7 +228,26 @@ interface ShellFooterValue {
228
228
  commitLog?: string;
229
229
  version?: string;
230
230
  releaseName?: string;
231
+ /**
232
+ * Environment label rendered as a colored chip in the sidebar
233
+ * footer (e.g., 'production' = red, 'staging' = orange,
234
+ * 'local'/'development' = green).
235
+ */
231
236
  environmentLabel?: string;
237
+ /**
238
+ * Optional pre-rendered environment chip element. When supplied,
239
+ * <CommitCopyButton> renders this instead of building a default chip
240
+ * from `environmentLabel`. Use this when the app already has an
241
+ * <EnvironmentBanner> component it wants to drop in unchanged.
242
+ */
243
+ envNode?: ReactNode;
244
+ /**
245
+ * When set, <CommitCopyButton> renders a diagnostics icon next to
246
+ * the version that, when clicked, fires this handler. The shell
247
+ * does not navigate or open anything — apps wire whatever they want
248
+ * (modal, route, console dump).
249
+ */
250
+ onDiagnosticsClick?: () => void;
232
251
  }
233
252
  /** Props for the top-level ShellProvider. */
234
253
  interface ShellProviderProps {
@@ -245,7 +264,7 @@ interface ShellProviderProps {
245
264
  organization?: ShellOrganizationValue;
246
265
  /** If provided, UserMenu shows account switcher + add-account button. */
247
266
  linkedAccounts?: ShellLinkedAccountsValue;
248
- /** If provided, shell renders <SupportRequestButton /> in the header. */
267
+ /** Drives the modal submission flow used by app-supplied support widgets. */
249
268
  support?: ShellSupportValue;
250
269
  /** Optional element rendered in the header (typically <DevNotesMenu /> from @the-portland-company/devnotes). */
251
270
  devNotesMenu?: ReactNode;
@@ -441,13 +460,16 @@ interface HeaderProps {
441
460
  * 2. headerActions (app-supplied React node, e.g., page action buttons).
442
461
  * 3. headerIconsSlot (app-supplied React node, e.g., FuelPopover,
443
462
  * NewTextBellPopup, SystemAlertsMenu, NavigationToggleMenu,
444
- * ImportProgressIndicator).
445
- * 4. DevNotesMenu (from useShellChrome().devNotesMenu).
446
- * 5. UserMenu (avatar + name/email VStack + chevron).
463
+ * ImportProgressIndicator, DevNotesMenu, SupportRequestAction).
464
+ * 4. UserMenu (avatar + name/email VStack + chevron).
447
465
  *
448
466
  * NOTE: app-specific widgets stay in the app. The shell only renders
449
- * what the app passes through `headerActions` + `headerIconsSlot` +
450
- * `devNotesMenu`.
467
+ * what the app passes through `headerActions` + `headerIconsSlot`. As of
468
+ * v0.3.1, the shell no longer renders a separate `devNotesMenu` slot
469
+ * after `headerIconsSlot` — apps must inline DevNotesMenu inside
470
+ * `headerIconsSlot` so they have full control of icon ordering. The
471
+ * `devNotesMenu` prop on <ShellChrome> is still accepted for backward
472
+ * compat but is ignored.
451
473
  */
452
474
  declare function Header({ onOpen, id }: HeaderProps): react_jsx_runtime.JSX.Element;
453
475
 
@@ -591,6 +613,50 @@ declare function BrandIcon({ name, size, className, style, alt, basePath, }: Bra
591
613
  */
592
614
  declare function SupportRequestButton(): react_jsx_runtime.JSX.Element | null;
593
615
 
616
+ interface CommitCopyButtonProps {
617
+ /**
618
+ * Pre-rendered environment chip (e.g., the app's own `EnvironmentBanner`).
619
+ * Takes precedence over `environmentLabel`. Click anywhere on the chip
620
+ * copies the full commit SHA.
621
+ */
622
+ envNode?: ReactNode;
623
+ /**
624
+ * Environment label (e.g., 'staging', 'production', 'local'). When
625
+ * `envNode` is not supplied, the component renders a default chip
626
+ * keyed off this label with appropriate background/border color.
627
+ */
628
+ environmentLabel?: string;
629
+ /** Full git commit SHA. Last 4 characters are rendered; click copies the full SHA. */
630
+ commit?: string;
631
+ /** App version, e.g., '2.34.1'. Renders as `v{version}`. */
632
+ version?: string;
633
+ /** ISO date string of the commit. Formatted as 'Mon D'. */
634
+ commitDate?: string;
635
+ /** Commit author name (e.g., 'Leland Smith'). First name is shown. */
636
+ commitAuthor?: string;
637
+ /** Full commit message body — shown as a tooltip over the env chip. */
638
+ commitLog?: string;
639
+ /**
640
+ * Optional click handler for the diagnostics icon. When unset, the
641
+ * icon is not rendered.
642
+ */
643
+ onDiagnosticsClick?: () => void;
644
+ }
645
+ /**
646
+ * Footer commit info pill. Ported from
647
+ * `react/app/src/layouts/AuthenticatedLayout.tsx::CommitCopyButton`
648
+ * (lines 3017–3284, 2026-05-13).
649
+ *
650
+ * Visual: two stacked rows.
651
+ * Row 1: [env chip] | {last4 SHA} | v{version} | [diagnostics icon]
652
+ * Row 2: | {Mon D} | {firstName} | (only when date or author present)
653
+ *
654
+ * Clicking the env chip copies the full SHA via `useClipboard`, with a
655
+ * brief "Copied" toast that fades up. Hovering the env chip shows the
656
+ * full commit log in a tooltip.
657
+ */
658
+ declare function CommitCopyButton({ envNode, environmentLabel, commit, version, commitDate, commitAuthor, commitLog, onDiagnosticsClick, }: CommitCopyButtonProps): react_jsx_runtime.JSX.Element;
659
+
594
660
  interface ShellChromeProps {
595
661
  children: ReactNode;
596
662
  /** Drives <OrgPill /> in the sidebar. */
@@ -722,4 +788,4 @@ declare function registerShellPrecache(opts?: RegisterShellPrecacheOptions): Pro
722
788
  */
723
789
  declare function buildSpeculationRules(currentApp: string, registry: AppRegistryEntry[]): string | null;
724
790
 
725
- export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
791
+ export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
package/dist/index.d.ts CHANGED
@@ -228,7 +228,26 @@ interface ShellFooterValue {
228
228
  commitLog?: string;
229
229
  version?: string;
230
230
  releaseName?: string;
231
+ /**
232
+ * Environment label rendered as a colored chip in the sidebar
233
+ * footer (e.g., 'production' = red, 'staging' = orange,
234
+ * 'local'/'development' = green).
235
+ */
231
236
  environmentLabel?: string;
237
+ /**
238
+ * Optional pre-rendered environment chip element. When supplied,
239
+ * <CommitCopyButton> renders this instead of building a default chip
240
+ * from `environmentLabel`. Use this when the app already has an
241
+ * <EnvironmentBanner> component it wants to drop in unchanged.
242
+ */
243
+ envNode?: ReactNode;
244
+ /**
245
+ * When set, <CommitCopyButton> renders a diagnostics icon next to
246
+ * the version that, when clicked, fires this handler. The shell
247
+ * does not navigate or open anything — apps wire whatever they want
248
+ * (modal, route, console dump).
249
+ */
250
+ onDiagnosticsClick?: () => void;
232
251
  }
233
252
  /** Props for the top-level ShellProvider. */
234
253
  interface ShellProviderProps {
@@ -245,7 +264,7 @@ interface ShellProviderProps {
245
264
  organization?: ShellOrganizationValue;
246
265
  /** If provided, UserMenu shows account switcher + add-account button. */
247
266
  linkedAccounts?: ShellLinkedAccountsValue;
248
- /** If provided, shell renders <SupportRequestButton /> in the header. */
267
+ /** Drives the modal submission flow used by app-supplied support widgets. */
249
268
  support?: ShellSupportValue;
250
269
  /** Optional element rendered in the header (typically <DevNotesMenu /> from @the-portland-company/devnotes). */
251
270
  devNotesMenu?: ReactNode;
@@ -441,13 +460,16 @@ interface HeaderProps {
441
460
  * 2. headerActions (app-supplied React node, e.g., page action buttons).
442
461
  * 3. headerIconsSlot (app-supplied React node, e.g., FuelPopover,
443
462
  * NewTextBellPopup, SystemAlertsMenu, NavigationToggleMenu,
444
- * ImportProgressIndicator).
445
- * 4. DevNotesMenu (from useShellChrome().devNotesMenu).
446
- * 5. UserMenu (avatar + name/email VStack + chevron).
463
+ * ImportProgressIndicator, DevNotesMenu, SupportRequestAction).
464
+ * 4. UserMenu (avatar + name/email VStack + chevron).
447
465
  *
448
466
  * NOTE: app-specific widgets stay in the app. The shell only renders
449
- * what the app passes through `headerActions` + `headerIconsSlot` +
450
- * `devNotesMenu`.
467
+ * what the app passes through `headerActions` + `headerIconsSlot`. As of
468
+ * v0.3.1, the shell no longer renders a separate `devNotesMenu` slot
469
+ * after `headerIconsSlot` — apps must inline DevNotesMenu inside
470
+ * `headerIconsSlot` so they have full control of icon ordering. The
471
+ * `devNotesMenu` prop on <ShellChrome> is still accepted for backward
472
+ * compat but is ignored.
451
473
  */
452
474
  declare function Header({ onOpen, id }: HeaderProps): react_jsx_runtime.JSX.Element;
453
475
 
@@ -591,6 +613,50 @@ declare function BrandIcon({ name, size, className, style, alt, basePath, }: Bra
591
613
  */
592
614
  declare function SupportRequestButton(): react_jsx_runtime.JSX.Element | null;
593
615
 
616
+ interface CommitCopyButtonProps {
617
+ /**
618
+ * Pre-rendered environment chip (e.g., the app's own `EnvironmentBanner`).
619
+ * Takes precedence over `environmentLabel`. Click anywhere on the chip
620
+ * copies the full commit SHA.
621
+ */
622
+ envNode?: ReactNode;
623
+ /**
624
+ * Environment label (e.g., 'staging', 'production', 'local'). When
625
+ * `envNode` is not supplied, the component renders a default chip
626
+ * keyed off this label with appropriate background/border color.
627
+ */
628
+ environmentLabel?: string;
629
+ /** Full git commit SHA. Last 4 characters are rendered; click copies the full SHA. */
630
+ commit?: string;
631
+ /** App version, e.g., '2.34.1'. Renders as `v{version}`. */
632
+ version?: string;
633
+ /** ISO date string of the commit. Formatted as 'Mon D'. */
634
+ commitDate?: string;
635
+ /** Commit author name (e.g., 'Leland Smith'). First name is shown. */
636
+ commitAuthor?: string;
637
+ /** Full commit message body — shown as a tooltip over the env chip. */
638
+ commitLog?: string;
639
+ /**
640
+ * Optional click handler for the diagnostics icon. When unset, the
641
+ * icon is not rendered.
642
+ */
643
+ onDiagnosticsClick?: () => void;
644
+ }
645
+ /**
646
+ * Footer commit info pill. Ported from
647
+ * `react/app/src/layouts/AuthenticatedLayout.tsx::CommitCopyButton`
648
+ * (lines 3017–3284, 2026-05-13).
649
+ *
650
+ * Visual: two stacked rows.
651
+ * Row 1: [env chip] | {last4 SHA} | v{version} | [diagnostics icon]
652
+ * Row 2: | {Mon D} | {firstName} | (only when date or author present)
653
+ *
654
+ * Clicking the env chip copies the full SHA via `useClipboard`, with a
655
+ * brief "Copied" toast that fades up. Hovering the env chip shows the
656
+ * full commit log in a tooltip.
657
+ */
658
+ declare function CommitCopyButton({ envNode, environmentLabel, commit, version, commitDate, commitAuthor, commitLog, onDiagnosticsClick, }: CommitCopyButtonProps): react_jsx_runtime.JSX.Element;
659
+
594
660
  interface ShellChromeProps {
595
661
  children: ReactNode;
596
662
  /** Drives <OrgPill /> in the sidebar. */
@@ -722,4 +788,4 @@ declare function registerShellPrecache(opts?: RegisterShellPrecacheOptions): Pro
722
788
  */
723
789
  declare function buildSpeculationRules(currentApp: string, registry: AppRegistryEntry[]): string | null;
724
790
 
725
- export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
791
+ export { AppLayout, type AppRegistryEntry, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, type CommitCopyButtonProps, Footer, GlobalUiIdProvider, Header, HeaderTabBar, type HeaderTabBarItem, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, type ShellActiveRoleConfig, type ShellAuthStatus, type ShellAuthValue, ShellChrome, ShellChromeContext, type ShellChromeProps, type ShellChromeValue, ShellFooterContext, type ShellFooterLink, type ShellFooterValue, type ShellLinkedAccount, ShellLinkedAccountsContext, type ShellLinkedAccountsValue, type ShellModeConfig, ShellModeContext, type ShellModeValue, type ShellNavItem, ShellNavItemsContext, type ShellNavItemsValue, ShellNavigationPreferenceContext, type ShellNavigationPreferenceValue, ShellOrgContext, type ShellOrganization, type ShellOrganizationValue, ShellProvider, type ShellProviderProps, ShellRoleContext, type ShellRoleValue, type ShellSlots, type ShellSupportContext, ShellSupportProviderContext, type ShellSupportValue, type ShellUser, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { extendTheme, Tooltip, ChakraProvider, useColorModeValue, Flex, Menu, MenuButton, HStack, Avatar, VStack, Text, Box, Portal, MenuList, MenuItem, MenuDivider, Badge, IconButton, Link as Link$1, Collapse, Skeleton, Button, Image, useDisclosure, useToast, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, Textarea, ModalFooter, useClipboard } from '@chakra-ui/react';
1
+ import { extendTheme, Tooltip, ChakraProvider, useColorModeValue, Flex, Menu, MenuButton, HStack, Avatar, VStack, Text, Box, Portal, MenuList, MenuItem, MenuDivider, Badge, IconButton, Link as Link$1, Collapse, Skeleton, Button, useClipboard, Icon, Image, useDisclosure, useToast, Modal, ModalOverlay, ModalContent, ModalHeader, ModalCloseButton, ModalBody, Textarea, ModalFooter } from '@chakra-ui/react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import clsx2 from 'clsx';
4
4
  import { Link, useLocation, useNavigate, NavLink } from 'react-router-dom';
@@ -1444,8 +1444,7 @@ function Header({ onOpen, id }) {
1444
1444
  breadcrumbs,
1445
1445
  headerActions,
1446
1446
  headerIconsSlot,
1447
- brandLogoSrc,
1448
- devNotesMenu
1447
+ brandLogoSrc
1449
1448
  } = useShellChrome();
1450
1449
  const mobileNavBg = useColorModeValue("white", "gray.900");
1451
1450
  const mobileNavBorderColor = useColorModeValue("gray.200", "gray.700");
@@ -1521,8 +1520,7 @@ function Header({ onOpen, id }) {
1521
1520
  },
1522
1521
  children: [
1523
1522
  headerActions,
1524
- headerIconsSlot,
1525
- devNotesMenu
1523
+ headerIconsSlot
1526
1524
  ]
1527
1525
  }
1528
1526
  ),
@@ -2178,6 +2176,237 @@ function ModePill() {
2178
2176
  ] }) });
2179
2177
  }
2180
2178
  var ModePill_default = ModePill;
2179
+ function ActivityIcon() {
2180
+ return /* @__PURE__ */ jsx(
2181
+ "svg",
2182
+ {
2183
+ width: "12",
2184
+ height: "12",
2185
+ viewBox: "0 0 24 24",
2186
+ fill: "none",
2187
+ stroke: "currentColor",
2188
+ strokeWidth: "2",
2189
+ strokeLinecap: "round",
2190
+ strokeLinejoin: "round",
2191
+ "aria-hidden": "true",
2192
+ children: /* @__PURE__ */ jsx("polyline", { points: "22 12 18 12 15 21 9 3 6 12 2 12" })
2193
+ }
2194
+ );
2195
+ }
2196
+ function defaultEnvChip(label) {
2197
+ if (!label) return null;
2198
+ const lower = label.toLowerCase();
2199
+ if (lower.includes("prod")) {
2200
+ return { color: "#ef4444", bg: "#fee2e2", border: "#fecaca", name: label };
2201
+ }
2202
+ if (lower.includes("stag")) {
2203
+ return { color: "#f97316", bg: "#ffedd5", border: "#fed7aa", name: label };
2204
+ }
2205
+ if (lower.includes("local") || lower.includes("dev")) {
2206
+ return { color: "#22c55e", bg: "#dcfce7", border: "#bbf7d0", name: label };
2207
+ }
2208
+ return { color: "#6b7280", bg: "#f3f4f6", border: "#e5e7eb", name: label };
2209
+ }
2210
+ var PLACEHOLDER_SHA_VALUES = /* @__PURE__ */ new Set(["unknown", "n/a", "null", "undefined", ""]);
2211
+ function isMeaningfulCommit(value) {
2212
+ if (typeof value !== "string") return false;
2213
+ const trimmed = value.trim();
2214
+ if (!trimmed) return false;
2215
+ return !PLACEHOLDER_SHA_VALUES.has(trimmed.toLowerCase());
2216
+ }
2217
+ function isMeaningfulAuthor(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 getFirstName(value) {
2224
+ if (!value) return "";
2225
+ const stripped = value.replace(/<[^>]+>/g, "").trim();
2226
+ if (!stripped) return "";
2227
+ const [firstToken] = stripped.split(/\s+/);
2228
+ return firstToken || stripped;
2229
+ }
2230
+ function CommitCopyButton({
2231
+ envNode,
2232
+ environmentLabel,
2233
+ commit,
2234
+ version = "",
2235
+ commitDate = "",
2236
+ commitAuthor = "",
2237
+ commitLog = "",
2238
+ onDiagnosticsClick
2239
+ }) {
2240
+ const safeCommit = isMeaningfulCommit(commit) ? commit.trim() : "";
2241
+ const { hasCopied, onCopy } = useClipboard(safeCommit);
2242
+ const shortCommit = safeCommit ? safeCommit.slice(-4) : "";
2243
+ const copiedAnimation = keyframes`
2244
+ 0% { opacity: 0; transform: translateY(6px); }
2245
+ 20% { opacity: 1; transform: translateY(0); }
2246
+ 100% { opacity: 0; transform: translateY(-8px); }
2247
+ `;
2248
+ const formattedDate = useMemo(() => {
2249
+ if (!commitDate) return "";
2250
+ const parsed = new Date(commitDate);
2251
+ if (Number.isNaN(parsed.getTime())) return commitDate;
2252
+ return parsed.toLocaleDateString(void 0, { month: "short", day: "numeric" });
2253
+ }, [commitDate]);
2254
+ const metaLine = useMemo(() => {
2255
+ const firstName = isMeaningfulAuthor(commitAuthor) ? getFirstName(commitAuthor) : "";
2256
+ return [formattedDate, firstName].filter(Boolean).join(" | ");
2257
+ }, [formattedDate, commitAuthor]);
2258
+ const normalizedCommitLog = useMemo(() => {
2259
+ if (commitLog?.trim()) {
2260
+ return commitLog.replace(/\r\n/g, "\n").replace(/\\n/g, "\n");
2261
+ }
2262
+ return "";
2263
+ }, [commitLog]);
2264
+ const tooltipContent = normalizedCommitLog.length > 0 ? /* @__PURE__ */ jsx(
2265
+ Box,
2266
+ {
2267
+ maxW: "600px",
2268
+ width: "min(600px, 90vw)",
2269
+ maxH: "80vh",
2270
+ whiteSpace: "pre-wrap",
2271
+ overflowY: "auto",
2272
+ overflowX: "auto",
2273
+ fontSize: "xs",
2274
+ fontFamily: "mono",
2275
+ wordBreak: "break-word",
2276
+ children: normalizedCommitLog
2277
+ }
2278
+ ) : void 0;
2279
+ const envChip = useMemo(() => {
2280
+ if (envNode) return envNode;
2281
+ const chip = defaultEnvChip(environmentLabel);
2282
+ if (!chip) return null;
2283
+ return /* @__PURE__ */ jsx(
2284
+ Box,
2285
+ {
2286
+ as: "span",
2287
+ display: "inline-flex",
2288
+ alignItems: "center",
2289
+ px: "1.5",
2290
+ py: "0.5",
2291
+ borderWidth: "1px",
2292
+ borderColor: chip.border,
2293
+ bg: chip.bg,
2294
+ color: chip.color,
2295
+ borderRadius: "md",
2296
+ fontSize: "2xs",
2297
+ fontWeight: "600",
2298
+ lineHeight: "1",
2299
+ textTransform: "capitalize",
2300
+ "aria-label": `Environment: ${chip.name}`,
2301
+ children: chip.name
2302
+ }
2303
+ );
2304
+ }, [envNode, environmentLabel]);
2305
+ const showEnv = Boolean(envChip);
2306
+ return /* @__PURE__ */ jsxs(
2307
+ VStack,
2308
+ {
2309
+ justify: "center",
2310
+ align: "center",
2311
+ borderRadius: "md",
2312
+ py: 0.5,
2313
+ px: 1,
2314
+ position: "relative",
2315
+ spacing: 0,
2316
+ textAlign: "center",
2317
+ w: "full",
2318
+ children: [
2319
+ /* @__PURE__ */ jsxs(Flex, { justify: "center", align: "center", gap: 2, whiteSpace: "nowrap", children: [
2320
+ showEnv ? /* @__PURE__ */ jsxs(Fragment, { children: [
2321
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(
2322
+ Tooltip,
2323
+ {
2324
+ label: tooltipContent,
2325
+ isDisabled: !tooltipContent,
2326
+ hasArrow: true,
2327
+ placement: "top-start",
2328
+ openDelay: 150,
2329
+ closeOnClick: false,
2330
+ shouldWrapChildren: true,
2331
+ modifiers: [
2332
+ { name: "preventOverflow", options: { padding: 16 } },
2333
+ { name: "offset", options: { offset: [0, 12] } }
2334
+ ],
2335
+ children: /* @__PURE__ */ jsx(
2336
+ Button,
2337
+ {
2338
+ variant: "unstyled",
2339
+ minW: "auto",
2340
+ h: "auto",
2341
+ p: 0,
2342
+ lineHeight: "1",
2343
+ color: "inherit",
2344
+ onClick: onCopy,
2345
+ "aria-label": "Copy commit ID",
2346
+ isDisabled: !safeCommit,
2347
+ children: envChip
2348
+ }
2349
+ )
2350
+ }
2351
+ ) }),
2352
+ /* @__PURE__ */ jsx(Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" })
2353
+ ] }) : null,
2354
+ shortCommit ? /* @__PURE__ */ jsx(Text, { fontSize: "2xs", color: hasCopied ? "green.500" : "gray.400", flexShrink: 0, children: shortCommit }) : null,
2355
+ version ? /* @__PURE__ */ jsxs(Fragment, { children: [
2356
+ shortCommit ? /* @__PURE__ */ jsx(Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }) : null,
2357
+ /* @__PURE__ */ jsxs(Text, { fontSize: "2xs", color: "gray.400", flexShrink: 0, children: [
2358
+ "v",
2359
+ version
2360
+ ] })
2361
+ ] }) : null,
2362
+ onDiagnosticsClick ? /* @__PURE__ */ jsxs(Fragment, { children: [
2363
+ /* @__PURE__ */ jsx(Text, { fontSize: "2xs", color: "gray.300", flexShrink: 0, children: "|" }),
2364
+ /* @__PURE__ */ jsx(Tooltip, { label: "Diagnostics", hasArrow: true, placement: "top", children: /* @__PURE__ */ jsx(
2365
+ Button,
2366
+ {
2367
+ variant: "unstyled",
2368
+ minW: "auto",
2369
+ h: "auto",
2370
+ p: 0,
2371
+ lineHeight: "1",
2372
+ color: "gray.400",
2373
+ _hover: { color: "gray.600" },
2374
+ onClick: (e) => {
2375
+ e.stopPropagation();
2376
+ onDiagnosticsClick();
2377
+ },
2378
+ "aria-label": "Diagnostics",
2379
+ children: /* @__PURE__ */ jsx(Icon, { as: ActivityIcon, boxSize: 3 })
2380
+ }
2381
+ ) })
2382
+ ] }) : null
2383
+ ] }),
2384
+ metaLine ? /* @__PURE__ */ jsxs(Text, { fontSize: "2xs", color: "gray.500", textAlign: "center", whiteSpace: "nowrap", children: [
2385
+ "| ",
2386
+ metaLine,
2387
+ " |"
2388
+ ] }) : null,
2389
+ hasCopied && /* @__PURE__ */ jsx(
2390
+ Box,
2391
+ {
2392
+ position: "absolute",
2393
+ bottom: "100%",
2394
+ mb: "6px",
2395
+ px: "2",
2396
+ py: "1",
2397
+ fontSize: "xs",
2398
+ bg: "gray.700",
2399
+ color: "white",
2400
+ borderRadius: "md",
2401
+ animation: `${copiedAnimation} 0.9s ease-out`,
2402
+ pointerEvents: "none",
2403
+ children: "Copied"
2404
+ }
2405
+ )
2406
+ ]
2407
+ }
2408
+ );
2409
+ }
2181
2410
  function Sidebar({ onClose, id }) {
2182
2411
  const { brandLogoSrc } = useShellChrome();
2183
2412
  const navItems = useShellNavItems()?.items ?? [];
@@ -2310,17 +2539,19 @@ function Sidebar({ onClose, id }) {
2310
2539
  }
2311
2540
  )
2312
2541
  ] }, `${l.label}-${idx}`)) }),
2313
- (footer.version || footer.commit || footer.environmentLabel) && /* @__PURE__ */ jsxs(HStack, { spacing: 2, fontSize: "xs", color: "gray.500", wrap: "wrap", children: [
2314
- footer.environmentLabel ? /* @__PURE__ */ jsx(Text, { fontWeight: "semibold", children: footer.environmentLabel }) : null,
2315
- footer.version ? /* @__PURE__ */ jsxs(Text, { children: [
2316
- "v",
2317
- footer.version
2318
- ] }) : null,
2319
- footer.commit ? /* @__PURE__ */ jsxs(Text, { fontFamily: "mono", children: [
2320
- "#",
2321
- footer.commit.slice(-7)
2322
- ] }) : null
2323
- ] })
2542
+ (footer.version || footer.commit || footer.commitFull || footer.environmentLabel || footer.envNode) && /* @__PURE__ */ jsx(
2543
+ CommitCopyButton,
2544
+ {
2545
+ envNode: footer.envNode,
2546
+ environmentLabel: footer.environmentLabel,
2547
+ commit: footer.commitFull ?? footer.commit,
2548
+ version: footer.version,
2549
+ commitDate: footer.commitDate,
2550
+ commitAuthor: footer.commitAuthor,
2551
+ commitLog: footer.commitLog,
2552
+ onDiagnosticsClick: footer.onDiagnosticsClick
2553
+ }
2554
+ )
2324
2555
  ] })
2325
2556
  }
2326
2557
  )
@@ -2739,6 +2970,6 @@ function buildSpeculationRules(currentApp, registry) {
2739
2970
  return JSON.stringify(payload);
2740
2971
  }
2741
2972
 
2742
- export { AppLayout, BrandIcon, BrandLogo, BreadcrumbBar, Card, Footer, GlobalUiIdProvider, Header, HeaderTabBar, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, ShellChrome, ShellChromeContext, ShellFooterContext, ShellLinkedAccountsContext, ShellModeContext, ShellNavItemsContext, ShellNavigationPreferenceContext, ShellOrgContext, ShellProvider, ShellRoleContext, ShellSupportProviderContext, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
2973
+ export { AppLayout, BrandIcon, BrandLogo, BreadcrumbBar, Card, CommitCopyButton, Footer, GlobalUiIdProvider, Header, HeaderTabBar, ModePill, NavItem, OrgPill, OrgPill as OrgSelector, PageContainer, PageHeader, ShellChrome, ShellChromeContext, ShellFooterContext, ShellLinkedAccountsContext, ShellModeContext, ShellNavItemsContext, ShellNavigationPreferenceContext, ShellOrgContext, ShellProvider, ShellRoleContext, ShellSupportProviderContext, Sidebar, SupportRequestButton, UserMenu, buildSpeculationRules, getOrganizationBadgeLetters, getOrganizationBrandColor, getUserDisplayName, hexToRgba, lightenHexColor, normalizeHexColor, politogyTheme, registerShellPrecache, useAppRegistry, useCurrentApp, useShellAuth, useShellChrome, useShellFooter, useShellLinkedAccounts, useShellMode, useShellNavItems, useShellNavigationPreference, useShellOrganization, useShellRole, useShellSupport };
2743
2974
  //# sourceMappingURL=index.js.map
2744
2975
  //# sourceMappingURL=index.js.map