@mohasinac/appkit 4.52.4 → 4.52.5

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "stampVersion": 1,
3
- "version": "4.52.4",
4
- "head": "8c2a4fbdf7c75e4d979047ff6bd10114078a2562",
3
+ "version": "4.52.5",
4
+ "head": "7b24e76346651ca12f0a941e0d1ca87bb173a242",
5
5
  "branch": "main",
6
6
  "dirty": true,
7
- "builtAt": "2026-10-15T02:06:48.669Z"
7
+ "builtAt": "2026-10-16T16:23:26.992Z"
8
8
  }
@@ -101,8 +101,6 @@ export interface AppLayoutShellProps {
101
101
  * every offset above them reads.
102
102
  */
103
103
  showBottomNav?: boolean;
104
- /** When provided, renders a tour-start icon button in the title bar. Null in Patch 1. */
105
- onTourStart?: () => void;
106
104
  /**
107
105
  * Override className for the main content wrapper div.
108
106
  * When set, replaces the default `w-full px-4 py-6 …`
@@ -128,4 +126,4 @@ export interface AppLayoutShellProps {
128
126
  };
129
127
  };
130
128
  }
131
- export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, onTourStart, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
129
+ export declare function AppLayoutShell({ children, navItems, sidebarItems, sidebarSections, sidebarPrimaryActions, sidebarTitle, hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle, suppressDashboardNav, hideSidebarToggle, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar, sidebarProfileLabels, eventBannerSlot, showBottomNav, contentClassName, lightBackground, darkBackground, }: AppLayoutShellProps): React.JSX.Element;
@@ -127,7 +127,7 @@ function SidebarContent({ sidebarItems, sidebarSections, sidebarPrimaryActions,
127
127
  ].join(" "), children: action.label }, `${action.href}-${action.label}`))) })), isAuthenticated && (_jsx(CollapsibleNavGroup, { title: labels.sectionTitle, hasActive: !!activeProfileLink, children: _jsxs(Ul, { spacing: "2xs", children: [_jsx(Li, { children: _jsx(TextLink, { href: profileHref, variant: "none", className: activeProfileLink?.href === profileHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === profileHref ? "page" : undefined, children: labels.profile }) }), userOrdersHref && (_jsx(Li, { children: _jsx(TextLink, { href: userOrdersHref, variant: "none", className: activeProfileLink?.href === userOrdersHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userOrdersHref ? "page" : undefined, children: labels.orders }) })), userWishlistHref && (_jsx(Li, { children: _jsx(TextLink, { href: userWishlistHref, variant: "none", className: activeProfileLink?.href === userWishlistHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userWishlistHref ? "page" : undefined, children: labels.wishlist }) })), userSettingsHref && (_jsx(Li, { children: _jsx(TextLink, { href: userSettingsHref, variant: "none", className: activeProfileLink?.href === userSettingsHref ? navItemActiveClass : navItemClass, "aria-current": activeProfileLink?.href === userSettingsHref ? "page" : undefined, children: labels.settings }) }))] }) })), isAuthenticated && user?.stats && (_jsxs(Div, { layout: "grid", gap: "2", className: "grid-cols-2", children: [user.stats.totalOrders != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.totalOrders }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Orders" })] })), user.stats.reviewsCount != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.reviewsCount }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Reviews" })] })), user.stats.auctionsWon != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.auctionsWon }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Auctions Won" })] })), user.stats.itemsSold != null && (_jsxs(Div, { className: CLS_STAT_BOX, children: [_jsx(Text, { className: "leading-none", color: "primary", size: "lg", weight: "bold", children: user.stats.itemsSold }), _jsx(Text, { className: CLS_STAT_LABEL, children: "Items Sold" })] }))] })), isAuthenticated && isAdminOrSeller && (adminHref || resolvedStoreHref) && (_jsx(CollapsibleNavGroup, { title: labels.dashboardSectionTitle, hasActive: !!activeDashboardLink, children: _jsxs(Ul, { spacing: "2xs", children: [adminHref && role === "admin" && (_jsx(Li, { children: _jsx(TextLink, { href: adminHref, variant: "none", className: activeDashboardLink?.href === adminHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === adminHref ? "page" : undefined, children: labels.adminDashboard }) })), resolvedStoreHref && isAdminOrSeller && (_jsx(Li, { children: _jsx(TextLink, { href: resolvedStoreHref, variant: "none", className: activeDashboardLink?.href === resolvedStoreHref ? navItemActiveClass : navItemClass, "aria-current": activeDashboardLink?.href === resolvedStoreHref ? "page" : undefined, children: labels.storeDashboard }) }))] }) })), normalizedSections.map((section, sectionIndex) => (_jsx(CollapsibleSidebarSection, { section: section, navItemClass: navItemClass, navItemActiveClass: navItemActiveClass, activeHref: activeHref }, `sidebar-section-${sectionIndex}`))), (sidebarLocaleSlot || showThemeToggleInSidebar || (isAuthenticated && onLogout)) && (_jsxs(Stack, { border: "default", className: "border-t border-[var(--appkit-color-border-subtle)]", padding: "t-md", gap: "3", children: [sidebarLocaleSlot, showThemeToggleInSidebar && (_jsxs("button", { type: "button", onClick: toggleTheme, className: "flex w-full items-center justify-end gap-[var(--appkit-space-3)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[length:var(--appkit-text-sm)] text-[var(--appkit-color-text-muted)] transition-colors hover:bg-primary-surface hover:text-primary-800", children: [_jsx(Span, { "aria-hidden": "true", children: theme === "dark" ? "☀️" : "🌙" }), activeThemeName] })), isAuthenticated && onLogout && (_jsx("button", { type: "button", onClick: () => { onLogout(); onAfterLogout(); }, className: CLS_LOGOUT_BTN, children: labels.logout }))] }))] }));
128
128
  }
129
129
  // ─── Main component ────────────────────────────────────────────────────────────
130
- export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, onTourStart, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
130
+ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarSections, sidebarPrimaryActions, sidebarTitle = "Navigation", hiddenNavItems, user, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, cartHref, wishlistHref, messagesHref, unreadMessageCount, userId, profileHref, loginHref, registerHref, homeHref, shopHref, footer, searchSlot, searchSlotRenderer, titleBarNavSlot, titleBarNotificationSlot, titleBarDevSlot, titleBarPromoStripText, showThemeToggle = false, suppressDashboardNav = false, hideSidebarToggle = false, onLogout, adminHref, storeHref, sellerHref, userOrdersHref, userWishlistHref, userSettingsHref, sidebarLocaleSlot, showThemeToggleInSidebar = false, sidebarProfileLabels, eventBannerSlot, showBottomNav = true, contentClassName, lightBackground = DEFAULT_LIGHT_BG, darkBackground = DEFAULT_DARK_BG, }) {
131
131
  // QueryClientProvider used to be created here so AppLayoutShell could stand
132
132
  // alone. The real cause of the 2026-06-10/11 "No QueryClient set" prod crash
133
133
  // turned out to be a peer-dep duplicate of @tanstack/query-core under
@@ -194,7 +194,7 @@ export function AppLayoutShell({ children, navItems, sidebarItems = [], sidebarS
194
194
  opacity: darkBackground.overlay?.opacity ?? 0,
195
195
  },
196
196
  };
197
- return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, messagesHref: messagesHref, unreadMessageCount: unreadMessageCount, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onTourStart: onTourStart, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content",
197
+ return (_jsx(_Fragment, { children: _jsxs(Stack, { className: "min-h-screen w-full overflow-x-clip transition-colors duration-300", children: [_jsx(BackgroundRenderer, { mode: theme === "dark" ? "dark" : "light", lightMode: normalizedLightBackground, darkMode: normalizedDarkBackground }), _jsxs(Div, { ref: headerRef, className: "sticky top-0 z-50 w-full", children: [_jsx(TitleBar, { onToggleSidebar: handleTogglePublicSidebar, sidebarOpen: sidebarOpen, onSearchToggle: () => setSearchOpen((prev) => !prev), searchOpen: searchOpen, brandName: brandName, brandShortName: brandShortName, siteLogoUrl: siteLogoUrl, logoHref: logoHref, promotionsHref: promotionsHref, cartHref: cartHref, wishlistHref: wishlistHref, messagesHref: messagesHref, unreadMessageCount: unreadMessageCount, userId: userId, profileHref: profileHref, loginHref: loginHref, registerHref: registerHref, user: user, navSlot: titleBarNavSlot, notificationSlot: titleBarNotificationSlot, devSlot: titleBarDevSlot, promoStripText: titleBarPromoStripText, isDark: theme === "dark", onToggleTheme: showThemeToggle ? toggleTheme : undefined, onBeforeToggleDashboardNav: handleBeforeDashboardNavToggle, suppressDashboardNav: suppressDashboardNav, hideSidebarToggle: hideSidebarToggle }), _jsx(NavbarWithSettings, { navItems: navItems, hiddenNavItems: hiddenNavItems, permissions: authUser?.permissions }), searchOpen && (searchSlotRenderer ? searchSlotRenderer(() => setSearchOpen(false)) : searchSlot), eventBannerSlot] }), _jsx(AutoBreadcrumbs, {}), _jsxs(Div, { layout: "flex", className: "relative w-full flex-1 overflow-x-clip", children: [_jsx(SidebarLayout, { isOpen: sidebarOpen, ariaLabel: "Secondary navigation", header: user ? (_jsx(SidebarUserHeader, { user: user, onClose: () => setSidebarOpen(false) })) : (_jsx(SidebarGuestHeader, { sidebarTitle: sidebarTitle, onClose: () => setSidebarOpen(false) })), onClose: () => setSidebarOpen(false), children: sidebarContent }), _jsx(Main, { id: "main-content",
198
198
  // No bottom-chrome clearance here on purpose. FooterLayout is a
199
199
  // SIBLING rendered after <Main>, so a margin here only gaps
200
200
  // main-to-footer — it never clears the bottom of the page, which is
@@ -55,8 +55,6 @@ export interface TitleBarLayoutProps {
55
55
  onToggleDashboardNav?: () => void;
56
56
  /** Hide the public sidebar toggle button when nested layouts own navigation. */
57
57
  hideSidebarToggle?: boolean;
58
- /** When provided, renders a tour-start icon button before the theme toggle. Null in Patch 1. */
59
- onTourStart?: () => void;
60
58
  id?: string;
61
59
  className?: string;
62
60
  }
@@ -76,4 +74,4 @@ export interface TitleBarLayoutProps {
76
74
  *
77
75
  * Receives all domain data as props — zero domain imports.
78
76
  */
79
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, messagesHref, unreadMessageCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
77
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, messagesHref, unreadMessageCount, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
@@ -39,11 +39,10 @@ const countBadge = "appkit-hand-badge absolute -top-0.5 flex items-center justif
39
39
  *
40
40
  * Receives all domain data as props — zero domain imports.
41
41
  */
42
- export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, messagesHref, unreadMessageCount = 0, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
42
+ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen, brandName, brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, messagesHref, unreadMessageCount = 0, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
43
43
  // ── Element builders ────────────────────────────────────────────────────────
44
44
  const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-surface transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx(Icon, { name: "deals", size: "md", filled: true }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
45
45
  const themeBtn = onToggleTheme ? (_jsx(IconButton, { size: "touch", variant: "ghost", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, icon: _jsx(Icon, { name: isDark ? "themeLight" : "themeDark", size: "lg" }) })) : null;
46
- const tourBtn = onTourStart ? (_jsx(IconButton, { size: "touch", variant: "ghost", "aria-label": "Start product tour", onClick: onTourStart, className: iconBtn, icon: _jsx(Icon, { name: "help", size: "lg" }) })) : null;
47
46
  const hamburgerBtn = !hideSidebarToggle ? (_jsx(IconButton, { size: "touch", variant: "ghost", "aria-label": sidebarOpen ? "Close menu" : hasDashboardNav ? "Open dashboard navigation" : "Open menu", "aria-expanded": sidebarOpen, "aria-controls": "secondary-sidebar", onClick: hasDashboardNav && onToggleDashboardNav ? onToggleDashboardNav : onToggleSidebar, className: iconBtn, icon: _jsx(Icon, { name: sidebarOpen ? "close" : "menu", size: "lg" }) })) : null;
48
47
  // Compare is always lg+ (desktop-only feature, less critical on mobile)
49
48
  const compareEl = compareHref ? (_jsx(Link, { href: compareHref, "aria-label": "Compare items", className: `${iconBtn} hidden lg:flex`, children: _jsx(Icon, { name: "compare", size: "lg" }) })) : null;
@@ -67,7 +66,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
67
66
  // ── Render ───────────────────────────────────────────────────────────────────
68
67
  return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", reverse: "hand", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandShortName ?? brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", reverse: "hand", children: [devSlot, navSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), messagesEl && _jsx(Div, { className: "hidden lg:flex", children: messagesEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
69
68
  ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
70
- : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, _jsx(Div, { className: "hidden sm:flex", children: promotionsEl }), _jsx(Div, { className: "hidden sm:flex", children: tourBtn }), themeBtn, hamburgerBtn] })] }), hasTb2 && (
69
+ : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, _jsx(Div, { className: "hidden sm:flex", children: promotionsEl }), themeBtn, hamburgerBtn] })] }), hasTb2 && (
71
70
  // `justify="end"` is CORRECT alongside `reverse="hand"` — do not
72
71
  // "fix" it to "start". `row-reverse` inverts the main axis, so
73
72
  // main-end IS the left edge: flex-end already packs this row left in
@@ -0,0 +1,2 @@
1
+ import type { AuthoredCase } from "./_types";
2
+ export declare const authored: Record<string, AuthoredCase>;
@@ -0,0 +1,85 @@
1
+ /*
2
+ * WHY: Authored six-part procedures for the design-ux/header-controls-and-messages page.
3
+ * WHAT: 6 case(s), keyed by full checklist id.
4
+ *
5
+ * @tag domain:tester
6
+ * @tag layer:seed
7
+ * @tag pattern:none
8
+ * @tag access:server-only
9
+ * @tag consumers:seed-data/authored/index.ts
10
+ * @tag sideEffects:none
11
+ */
12
+ export const authored = {
13
+ "checklist-design-ux-header-controls-and-messages-no-help-or-tour-icon": {
14
+ roles: ["guest"],
15
+ startPage: "/products",
16
+ steps: [
17
+ "Open /products at a maximised window.",
18
+ "Read every icon in the top header row, right of the logo.",
19
+ "Repeat at 1024px, 768px and 320px.",
20
+ ],
21
+ expectedBehaviour: "The tour control was removed outright, along with the prop that fed it, so there is no width at which it can reappear.",
22
+ expectedUiState: "No '?' icon anywhere in the header at any of the four widths. The right-hand cluster is search, Today's Deals (640px and up), theme toggle and hamburger — plus the signed-in icons at 1024px and up. The removed control previously sat between the theme toggle and the hamburger.",
23
+ endResult: "Nothing persists; this is a pure chrome check. The hamburger must still be present and fully visible at 320px.",
24
+ },
25
+ "checklist-design-ux-header-controls-and-messages-tour-does-not-autostart": {
26
+ roles: ["guest", "buyer"],
27
+ startPage: "/",
28
+ steps: [
29
+ "Signed out, load /, then /products, then a product detail page.",
30
+ "Wait a few seconds on each without clicking anything.",
31
+ "Sign in and repeat the same three pages.",
32
+ ],
33
+ expectedBehaviour: "Removing the header button removed the only call to `startTour`. Nothing else should be able to begin a tour, on any route or for any role.",
34
+ expectedUiState: "No tour popover, spotlight, highlight ring or step counter appears at any point, signed in or out. A tour appearing means something still calls startTour and the removal is incomplete — record which page it fired on.",
35
+ endResult: "No tour state is written. Reloading any of the pages produces no tour either.",
36
+ },
37
+ "checklist-design-ux-header-controls-and-messages-messages-icon-visible-when-signed-in": {
38
+ roles: ["buyer"],
39
+ startPage: "/messages",
40
+ steps: [
41
+ "Sign in as any buyer.",
42
+ "At a window 1024px or wider, look at the header beside the notification bell.",
43
+ "Click the messages icon.",
44
+ ],
45
+ expectedBehaviour: "The header renders the messages control only when `siteSettings.chat.enabled` is true — and renders nothing at all when it is not, rather than a disabled icon. The flag is now on, so the control should exist and route to the inbox.",
46
+ expectedUiState: "A messages/inbox icon is present beside the notification bell and carries an unread badge when there are unread messages. Clicking it navigates to /messages, which loads — not a 404 and not /unauthorized.",
47
+ endResult: "Navigation only; nothing is written. Going back returns to the previous page with the icon still present.",
48
+ },
49
+ "checklist-design-ux-header-controls-and-messages-messages-icon-on-mobile-row": {
50
+ roles: ["buyer"],
51
+ startPage: "/messages",
52
+ steps: [
53
+ "Still signed in, narrow the window to 320px.",
54
+ "Look at BOTH header rows — the main row and the secondary row beneath it.",
55
+ "Tap the messages icon.",
56
+ ],
57
+ expectedBehaviour: "Several signed-in icons are hidden from the main row below 1024px and carried on a secondary row instead, so the control moves rather than disappearing.",
58
+ expectedUiState: "The messages icon is present and fully visible in the secondary header row at 320px — not clipped at the edge and not absent. Tapping it opens /messages.",
59
+ endResult: "Nothing persists. Widening back past 1024px moves the icon to the main row again.",
60
+ },
61
+ "checklist-design-ux-header-controls-and-messages-messages-absent-for-guests": {
62
+ roles: ["guest"],
63
+ startPage: "/products",
64
+ steps: [
65
+ "Sign out completely.",
66
+ "Reload /products.",
67
+ "Inspect both header rows at a desktop width and again at 320px.",
68
+ ],
69
+ expectedBehaviour: "A message inbox is per-user, so enabling the feature site-wide must not expose the control to visitors who have no inbox.",
70
+ expectedUiState: "No messages icon in either header row at either width. The guest header shows search, deals, theme, hamburger and the sign-in affordances only.",
71
+ endResult: "No state. Signing back in brings the icon back, which also confirms the gate is on the session and not on the page.",
72
+ },
73
+ "checklist-design-ux-header-controls-and-messages-messages-inbox-loads": {
74
+ roles: ["buyer"],
75
+ startPage: "/messages",
76
+ steps: [
77
+ "Signed in, navigate directly to /messages.",
78
+ "Wait for the page to settle.",
79
+ "Reload once.",
80
+ ],
81
+ expectedBehaviour: "The messaging feature has been dormant behind a disabled flag, so this is the first real exercise of the page. It must handle having no conversations as a normal state.",
82
+ expectedUiState: "The inbox shell renders with either a list of conversations or an explicit empty state saying there are none. It must NOT be a crash, an error boundary, a redirect to /unauthorized, or a spinner that never resolves. Note the exact empty-state wording so a later run can tell 'empty' from 'failed to load'.",
83
+ endResult: "Reloading shows the same state. Visiting /messages signed out should send you to sign-in rather than rendering an empty inbox.",
84
+ },
85
+ };
@@ -89,6 +89,7 @@ import { authored as a_design_ux__back_to_top_button } from "./design-ux__back-t
89
89
  import { authored as a_design_ux__rich_text_readability } from "./design-ux__rich-text-readability.js";
90
90
  import { authored as a_design_ux__action_bar_placement } from "./design-ux__action-bar-placement.js";
91
91
  import { authored as a_design_ux__nav_reachability } from "./design-ux__nav-reachability.js";
92
+ import { authored as a_design_ux__header_controls_and_messages } from "./design-ux__header-controls-and-messages.js";
92
93
  import { authored as a_design_ux__feature_badge_picker } from "./design-ux__feature-badge-picker.js";
93
94
  import { authored as a_design_ux__dashboard_layout } from "./design-ux__dashboard-layout.js";
94
95
  import { authored as a_design_ux__footer_theme } from "./design-ux__footer-theme.js";
@@ -215,6 +216,7 @@ export const AUTHORED_CASES = {
215
216
  ...a_design_ux__rich_text_readability,
216
217
  ...a_design_ux__action_bar_placement,
217
218
  ...a_design_ux__nav_reachability,
219
+ ...a_design_ux__header_controls_and_messages,
218
220
  ...a_design_ux__feature_badge_picker,
219
221
  ...a_design_ux__dashboard_layout,
220
222
  ...a_design_ux__footer_theme,
@@ -3892,6 +3892,49 @@ const rawTesterChecklistItems = [
3892
3892
  },
3893
3893
  ],
3894
3894
  },
3895
+ {
3896
+ pageKey: "header-controls-and-messages",
3897
+ pageLabel: "Header Controls — Tour Removed, Messages Enabled",
3898
+ href: "/products",
3899
+ cases: [
3900
+ {
3901
+ key: "no-help-or-tour-icon",
3902
+ label: "The header has NO '?' help / product-tour icon at any window width",
3903
+ description: "Removed 2026-10-15. BEFORE: a '?' icon sat between the theme toggle and the hamburger; it started the product tour (aria-label 'Start product tour'). It was one control, not two — the help glyph WAS the tour button. AFTER: check the header at 320px, 768px, 1024px and 1440px. There must be no '?' icon at any of them. The remaining right-hand controls are search, Today's Deals (sm and up), theme and hamburger — plus the signed-in icons on lg and up.",
3904
+ href: "/products",
3905
+ },
3906
+ {
3907
+ key: "tour-does-not-autostart",
3908
+ label: "No product tour starts on its own, and no tour overlay can be triggered from the header",
3909
+ description: "Removing the button removed the only way to start a tour. Load several pages signed out and signed in and confirm no tour popover, spotlight or step overlay ever appears. If a tour does appear, something still calls startTour and the removal was incomplete.",
3910
+ href: "/",
3911
+ },
3912
+ {
3913
+ key: "messages-icon-visible-when-signed-in",
3914
+ label: "A signed-in user sees a messages/inbox icon in the header that opens the inbox",
3915
+ description: "Enabled 2026-10-15. BEFORE: siteSettings.chat.enabled was absent, and the header renders nothing at all without it — not a disabled icon, nothing — so there was no way into the inbox. AFTER: sign in and look at the header on a desktop width (lg and up). A messages icon must be present beside the notification bell, and clicking it must open /messages, not 404.",
3916
+ href: "/messages",
3917
+ },
3918
+ {
3919
+ key: "messages-icon-on-mobile-row",
3920
+ label: "On mobile the messages icon appears in the secondary header row, not only on desktop",
3921
+ description: "The header hides several signed-in icons below lg and carries them on a second row instead. Signed in at 320–768px, confirm the messages icon is reachable in that secondary row and opens the inbox.",
3922
+ href: "/messages",
3923
+ },
3924
+ {
3925
+ key: "messages-absent-for-guests",
3926
+ label: "A signed-OUT visitor does not see a messages icon",
3927
+ description: "Messages are per-user, so the control should not appear for a guest even with chat enabled. Sign out, reload, and confirm no messages icon is present in either header row.",
3928
+ href: "/products",
3929
+ },
3930
+ {
3931
+ key: "messages-inbox-loads",
3932
+ label: "The /messages inbox page loads for a signed-in user and shows either conversations or a proper empty state",
3933
+ description: "The feature was dormant behind a flag, so this is the first real exercise of the page. Open /messages signed in. It must render the inbox shell with either a conversation list or an explicit empty state — never a crash, a redirect to /unauthorized, or an endless spinner.",
3934
+ href: "/messages",
3935
+ },
3936
+ ],
3937
+ },
3895
3938
  {
3896
3939
  pageKey: "nav-reachability",
3897
3940
  pageLabel: "Navigation — Every Item Reachable at Every Width",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.52.4",
3
+ "version": "4.52.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"