@almadar/ui 4.22.4 → 4.24.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.
@@ -21617,6 +21617,7 @@ var init_DashboardLayout = __esm({
21617
21617
  init_Typography();
21618
21618
  init_Icon();
21619
21619
  init_useAuthContext();
21620
+ init_useEventBus();
21620
21621
  init_useTranslate();
21621
21622
  DashboardLayout = ({
21622
21623
  appName = "{{APP_TITLE}}",
@@ -21624,11 +21625,29 @@ var init_DashboardLayout = __esm({
21624
21625
  navItems = [],
21625
21626
  user: userProp,
21626
21627
  headerActions,
21627
- showSearch = true,
21628
+ showSearch = false,
21629
+ searchEvent,
21630
+ onSearchSubmit,
21631
+ notifications,
21632
+ notificationClickEvent,
21633
+ onNotificationClick,
21634
+ showThemeToggle = true,
21628
21635
  sidebarFooter,
21629
21636
  onSignOut: onSignOutProp,
21630
21637
  children
21631
21638
  }) => {
21639
+ const eventBus = useEventBus();
21640
+ const searchEnabled = showSearch || Boolean(searchEvent) || Boolean(onSearchSubmit);
21641
+ const notificationsEnabled = Array.isArray(notifications);
21642
+ const unreadCount = notificationsEnabled ? notifications.filter((n) => n.read !== true).length : 0;
21643
+ const handleSearchSubmit = (value) => {
21644
+ if (searchEvent) eventBus.emit(`UI:${searchEvent}`, { value });
21645
+ if (onSearchSubmit) onSearchSubmit(value);
21646
+ };
21647
+ const handleNotificationClick = () => {
21648
+ if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
21649
+ if (onNotificationClick) onNotificationClick();
21650
+ };
21632
21651
  const [sidebarOpen, setSidebarOpen] = React127.useState(false);
21633
21652
  const [userMenuOpen, setUserMenuOpen] = React127.useState(false);
21634
21653
  const location = reactRouterDom.useLocation();
@@ -21709,17 +21728,7 @@ var init_DashboardLayout = __esm({
21709
21728
  ))
21710
21729
  }
21711
21730
  ),
21712
- sidebarFooter || /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: /* @__PURE__ */ jsxRuntime.jsxs(
21713
- reactRouterDom.Link,
21714
- {
21715
- to: "/settings",
21716
- className: "flex items-center gap-3 px-3 py-2 text-sm text-muted-foreground dark:text-muted-foreground rounded-lg hover:bg-muted dark:hover:bg-muted",
21717
- children: [
21718
- /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Settings, { className: "h-5 w-5" }),
21719
- t("common.settings")
21720
- ]
21721
- }
21722
- ) })
21731
+ sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
21723
21732
  ]
21724
21733
  }
21725
21734
  ),
@@ -21746,32 +21755,40 @@ var init_DashboardLayout = __esm({
21746
21755
  children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Menu, { className: "h-5 w-5" })
21747
21756
  }
21748
21757
  ),
21749
- showSearch && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
21758
+ searchEnabled && /* @__PURE__ */ jsxRuntime.jsx(Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxRuntime.jsxs(Box, { className: "relative", children: [
21750
21759
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground dark:text-muted-foreground" }),
21751
21760
  /* @__PURE__ */ jsxRuntime.jsx(
21752
21761
  Input,
21753
21762
  {
21754
21763
  type: "search",
21755
21764
  placeholder: t("common.search"),
21756
- className: "pl-10 w-full"
21765
+ className: "pl-10 w-full",
21766
+ onKeyDown: (e) => {
21767
+ if (e.key === "Enter") {
21768
+ handleSearchSubmit(e.target.value);
21769
+ }
21770
+ }
21757
21771
  }
21758
21772
  )
21759
21773
  ] }) }),
21760
21774
  /* @__PURE__ */ jsxRuntime.jsxs(HStack, { align: "center", gap: "xs", children: [
21761
21775
  headerActions,
21762
- /* @__PURE__ */ jsxRuntime.jsx(ThemeToggle, {}),
21763
- /* @__PURE__ */ jsxRuntime.jsxs(
21776
+ showThemeToggle && /* @__PURE__ */ jsxRuntime.jsx(ThemeToggle, {}),
21777
+ notificationsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
21764
21778
  Button,
21765
21779
  {
21766
21780
  variant: "ghost",
21767
21781
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
21782
+ onClick: handleNotificationClick,
21783
+ "aria-label": t("common.notifications"),
21768
21784
  children: [
21769
21785
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Bell, { className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
21770
- /* @__PURE__ */ jsxRuntime.jsx(
21786
+ unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
21771
21787
  Box,
21772
21788
  {
21773
21789
  as: "span",
21774
- className: "absolute top-1 right-1 w-2 h-2 bg-error rounded-full"
21790
+ className: "absolute -top-0.5 -right-0.5 min-w-[18px] h-[18px] px-1 bg-error rounded-full text-[10px] font-semibold text-white flex items-center justify-center",
21791
+ children: unreadCount > 99 ? "99+" : unreadCount
21775
21792
  }
21776
21793
  )
21777
21794
  ]
@@ -21836,17 +21853,6 @@ var init_DashboardLayout = __esm({
21836
21853
  }
21837
21854
  )
21838
21855
  ] }),
21839
- /* @__PURE__ */ jsxRuntime.jsxs(
21840
- reactRouterDom.Link,
21841
- {
21842
- to: "/settings",
21843
- className: "flex items-center gap-2 px-4 py-2 text-sm text-foreground dark:text-foreground hover:bg-muted dark:hover:bg-muted",
21844
- children: [
21845
- /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Settings, { className: "h-4 w-4" }),
21846
- t("common.settings")
21847
- ]
21848
- }
21849
- ),
21850
21856
  /* @__PURE__ */ jsxRuntime.jsxs(
21851
21857
  Button,
21852
21858
  {
@@ -52767,6 +52773,7 @@ function prepareSchemaForPreview(input) {
52767
52773
  // runtime/OrbPreview.tsx
52768
52774
  init_logger();
52769
52775
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
52776
+ var navLog = createLogger("almadar:runtime:navigation");
52770
52777
  function normalizeChild(child) {
52771
52778
  if (typeof child === "string") return child;
52772
52779
  if (child === null || typeof child !== "object" || Array.isArray(child)) {
@@ -52842,6 +52849,19 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52842
52849
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
52843
52850
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
52844
52851
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
52852
+ const prevTraitNamesRef = React127.useRef("");
52853
+ React127.useEffect(() => {
52854
+ const traitNames = traits2.map((b) => b.trait?.name ?? "").filter(Boolean).sort().join(",");
52855
+ if (prevTraitNamesRef.current && prevTraitNamesRef.current !== traitNames) {
52856
+ navLog.info("page:trait-set-changed", {
52857
+ from: prevTraitNamesRef.current,
52858
+ to: traitNames,
52859
+ action: "clearAll-slots"
52860
+ });
52861
+ uiSlots.clearAll();
52862
+ }
52863
+ prevTraitNamesRef.current = traitNames;
52864
+ }, [traits2, uiSlots]);
52845
52865
  const initSentRef = React127.useRef(false);
52846
52866
  React127.useEffect(() => {
52847
52867
  if (!orbitalNames?.length) {
@@ -52856,6 +52876,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52856
52876
  }, 5e3);
52857
52877
  return () => clearTimeout(fallback);
52858
52878
  }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
52879
+ const orbitalsKey = (orbitalNames ?? []).slice().sort().join(",");
52880
+ React127.useEffect(() => {
52881
+ initSentRef.current = false;
52882
+ }, [orbitalsKey]);
52859
52883
  React127.useEffect(() => {
52860
52884
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
52861
52885
  initSentRef.current = true;
@@ -53096,8 +53120,19 @@ function OrbPreview({
53096
53120
  }, [initialPageName, currentPage]);
53097
53121
  const handleNavigate = React127.useCallback((path) => {
53098
53122
  const match = pages.find(({ page }) => page.path === path);
53123
+ navLog.info("handleNavigate", {
53124
+ path,
53125
+ matched: match?.page.name ?? null,
53126
+ availablePaths: pages.map((p2) => p2.page.path)
53127
+ });
53099
53128
  if (match) {
53100
53129
  setCurrentPage(match.page.name);
53130
+ if (typeof window !== "undefined") {
53131
+ const url = new URL(window.location.href);
53132
+ url.searchParams.set("page", path);
53133
+ window.history.pushState({}, "", url.toString());
53134
+ window.dispatchEvent(new PopStateEvent("popstate"));
53135
+ }
53101
53136
  }
53102
53137
  }, [pages]);
53103
53138
  if (!parseResult.ok) {
@@ -53109,18 +53144,30 @@ function OrbPreview({
53109
53144
  const containerRef = React127.useRef(null);
53110
53145
  React127.useEffect(() => {
53111
53146
  const el = containerRef.current;
53112
- if (!el || pages.length <= 1) return;
53147
+ if (!el) return;
53148
+ if (pages.length <= 1) {
53149
+ navLog.info("interceptor:skipped", { reason: "single-page schema", pageCount: pages.length });
53150
+ return;
53151
+ }
53113
53152
  const handler = (e) => {
53114
53153
  const anchor = e.target.closest("a");
53115
53154
  if (!anchor) return;
53116
53155
  const href = anchor.getAttribute("href") ?? anchor.getAttribute("to") ?? "";
53117
- if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return;
53156
+ navLog.info("click:intercepted", {
53157
+ href,
53158
+ anchorText: anchor.textContent?.trim().slice(0, 40)
53159
+ });
53160
+ if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) {
53161
+ navLog.info("click:skipped", { href, reason: "external/empty/hash" });
53162
+ return;
53163
+ }
53118
53164
  e.preventDefault();
53119
53165
  e.stopPropagation();
53120
53166
  e.stopImmediatePropagation();
53121
53167
  handleNavigate(href);
53122
53168
  };
53123
53169
  el.addEventListener("click", handler, true);
53170
+ navLog.info("interceptor:installed", { pageCount: pages.length, paths: pages.map((p2) => p2.page.path) });
53124
53171
  return () => el.removeEventListener("click", handler, true);
53125
53172
  }, [pages, handleNavigate]);
53126
53173
  return /* @__PURE__ */ jsxRuntime.jsxs(
package/dist/avl/index.js CHANGED
@@ -3,7 +3,7 @@ import { Html, RoundedBox, OrbitControls as OrbitControls$1, Grid as Grid$1, Sta
3
3
  import * as React127 from 'react';
4
4
  import React127__default, { createContext, useContext, useRef, useState, useCallback, useMemo, useEffect, Suspense, useLayoutEffect, useReducer, lazy, useId, forwardRef, useImperativeHandle, Component } from 'react';
5
5
  import * as LucideIcons from 'lucide-react';
6
- import { Loader2, ChevronDown, X, Check, Copy, AlertTriangle, Info, AlertCircle, CheckCircle, List, Printer, ChevronRight, ChevronLeft, Code, FileText, WrapText, Trash2, Settings, Menu as Menu$1, Search, Bell, LogOut, ZoomOut, ZoomIn, Download, FileQuestion, Inbox, XCircle, Filter, Plus, Pause, Play, RotateCcw, Package, Calendar, Pencil, Eye, MoreHorizontal, Image as Image$1, Upload, Minus, ArrowLeft, HelpCircle, ChevronUp, Eraser, Star, TrendingUp, TrendingDown, ArrowUp, ArrowDown, MoreVertical, Sun, Moon, Circle, Clock, CheckCircle2, ArrowRight, FileWarning, SkipForward, Bug, Send, Wrench, User, Tag, DollarSign, Zap, Sword, Move, Heart, Shield } from 'lucide-react';
6
+ import { Loader2, ChevronDown, X, Check, Copy, AlertTriangle, Info, AlertCircle, CheckCircle, List, Printer, ChevronRight, ChevronLeft, Code, FileText, WrapText, Trash2, Menu as Menu$1, Search, Bell, LogOut, ZoomOut, ZoomIn, Download, FileQuestion, Inbox, XCircle, Filter, Plus, Pause, Play, RotateCcw, Package, Calendar, Pencil, Eye, MoreHorizontal, Image as Image$1, Upload, Minus, ArrowLeft, HelpCircle, ChevronUp, Eraser, Star, TrendingUp, TrendingDown, ArrowUp, ArrowDown, MoreVertical, Sun, Moon, Circle, Clock, CheckCircle2, ArrowRight, FileWarning, SkipForward, Bug, Send, Wrench, User, Tag, DollarSign, Zap, Sword, Move, Heart, Shield } from 'lucide-react';
7
7
  import { evaluate, createMinimalContext } from '@almadar/evaluator';
8
8
  import { getPatternDefinition, getComponentForPattern as getComponentForPattern$1, isEntityAwarePattern } from '@almadar/patterns';
9
9
  import { createPortal } from 'react-dom';
@@ -21571,6 +21571,7 @@ var init_DashboardLayout = __esm({
21571
21571
  init_Typography();
21572
21572
  init_Icon();
21573
21573
  init_useAuthContext();
21574
+ init_useEventBus();
21574
21575
  init_useTranslate();
21575
21576
  DashboardLayout = ({
21576
21577
  appName = "{{APP_TITLE}}",
@@ -21578,11 +21579,29 @@ var init_DashboardLayout = __esm({
21578
21579
  navItems = [],
21579
21580
  user: userProp,
21580
21581
  headerActions,
21581
- showSearch = true,
21582
+ showSearch = false,
21583
+ searchEvent,
21584
+ onSearchSubmit,
21585
+ notifications,
21586
+ notificationClickEvent,
21587
+ onNotificationClick,
21588
+ showThemeToggle = true,
21582
21589
  sidebarFooter,
21583
21590
  onSignOut: onSignOutProp,
21584
21591
  children
21585
21592
  }) => {
21593
+ const eventBus = useEventBus();
21594
+ const searchEnabled = showSearch || Boolean(searchEvent) || Boolean(onSearchSubmit);
21595
+ const notificationsEnabled = Array.isArray(notifications);
21596
+ const unreadCount = notificationsEnabled ? notifications.filter((n) => n.read !== true).length : 0;
21597
+ const handleSearchSubmit = (value) => {
21598
+ if (searchEvent) eventBus.emit(`UI:${searchEvent}`, { value });
21599
+ if (onSearchSubmit) onSearchSubmit(value);
21600
+ };
21601
+ const handleNotificationClick = () => {
21602
+ if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
21603
+ if (onNotificationClick) onNotificationClick();
21604
+ };
21586
21605
  const [sidebarOpen, setSidebarOpen] = useState(false);
21587
21606
  const [userMenuOpen, setUserMenuOpen] = useState(false);
21588
21607
  const location = useLocation();
@@ -21663,17 +21682,7 @@ var init_DashboardLayout = __esm({
21663
21682
  ))
21664
21683
  }
21665
21684
  ),
21666
- sidebarFooter || /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: /* @__PURE__ */ jsxs(
21667
- Link,
21668
- {
21669
- to: "/settings",
21670
- className: "flex items-center gap-3 px-3 py-2 text-sm text-muted-foreground dark:text-muted-foreground rounded-lg hover:bg-muted dark:hover:bg-muted",
21671
- children: [
21672
- /* @__PURE__ */ jsx(Settings, { className: "h-5 w-5" }),
21673
- t("common.settings")
21674
- ]
21675
- }
21676
- ) })
21685
+ sidebarFooter && /* @__PURE__ */ jsx(Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
21677
21686
  ]
21678
21687
  }
21679
21688
  ),
@@ -21700,32 +21709,40 @@ var init_DashboardLayout = __esm({
21700
21709
  children: /* @__PURE__ */ jsx(Menu$1, { className: "h-5 w-5" })
21701
21710
  }
21702
21711
  ),
21703
- showSearch && /* @__PURE__ */ jsx(Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
21712
+ searchEnabled && /* @__PURE__ */ jsx(Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxs(Box, { className: "relative", children: [
21704
21713
  /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground dark:text-muted-foreground" }),
21705
21714
  /* @__PURE__ */ jsx(
21706
21715
  Input,
21707
21716
  {
21708
21717
  type: "search",
21709
21718
  placeholder: t("common.search"),
21710
- className: "pl-10 w-full"
21719
+ className: "pl-10 w-full",
21720
+ onKeyDown: (e) => {
21721
+ if (e.key === "Enter") {
21722
+ handleSearchSubmit(e.target.value);
21723
+ }
21724
+ }
21711
21725
  }
21712
21726
  )
21713
21727
  ] }) }),
21714
21728
  /* @__PURE__ */ jsxs(HStack, { align: "center", gap: "xs", children: [
21715
21729
  headerActions,
21716
- /* @__PURE__ */ jsx(ThemeToggle, {}),
21717
- /* @__PURE__ */ jsxs(
21730
+ showThemeToggle && /* @__PURE__ */ jsx(ThemeToggle, {}),
21731
+ notificationsEnabled && /* @__PURE__ */ jsxs(
21718
21732
  Button,
21719
21733
  {
21720
21734
  variant: "ghost",
21721
21735
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
21736
+ onClick: handleNotificationClick,
21737
+ "aria-label": t("common.notifications"),
21722
21738
  children: [
21723
21739
  /* @__PURE__ */ jsx(Bell, { className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
21724
- /* @__PURE__ */ jsx(
21740
+ unreadCount > 0 && /* @__PURE__ */ jsx(
21725
21741
  Box,
21726
21742
  {
21727
21743
  as: "span",
21728
- className: "absolute top-1 right-1 w-2 h-2 bg-error rounded-full"
21744
+ className: "absolute -top-0.5 -right-0.5 min-w-[18px] h-[18px] px-1 bg-error rounded-full text-[10px] font-semibold text-white flex items-center justify-center",
21745
+ children: unreadCount > 99 ? "99+" : unreadCount
21729
21746
  }
21730
21747
  )
21731
21748
  ]
@@ -21790,17 +21807,6 @@ var init_DashboardLayout = __esm({
21790
21807
  }
21791
21808
  )
21792
21809
  ] }),
21793
- /* @__PURE__ */ jsxs(
21794
- Link,
21795
- {
21796
- to: "/settings",
21797
- className: "flex items-center gap-2 px-4 py-2 text-sm text-foreground dark:text-foreground hover:bg-muted dark:hover:bg-muted",
21798
- children: [
21799
- /* @__PURE__ */ jsx(Settings, { className: "h-4 w-4" }),
21800
- t("common.settings")
21801
- ]
21802
- }
21803
- ),
21804
21810
  /* @__PURE__ */ jsxs(
21805
21811
  Button,
21806
21812
  {
@@ -52721,6 +52727,7 @@ function prepareSchemaForPreview(input) {
52721
52727
  // runtime/OrbPreview.tsx
52722
52728
  init_logger();
52723
52729
  var xOrbitalLog2 = createLogger("almadar:runtime:cross-orbital");
52730
+ var navLog = createLogger("almadar:runtime:navigation");
52724
52731
  function normalizeChild(child) {
52725
52732
  if (typeof child === "string") return child;
52726
52733
  if (child === null || typeof child !== "object" || Array.isArray(child)) {
@@ -52796,6 +52803,19 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52796
52803
  }, [bridge.connected, bridge.sendEvent, orbitalNames, uiSlots, onNavigate, embeddedTraits]);
52797
52804
  const opts = orbitalNames ? { onEventProcessed, navigate: onNavigate, traitConfigsByName, orbitalsByTrait, embeddedTraits } : { navigate: onNavigate, persistence, traitConfigsByName, orbitalsByTrait, embeddedTraits };
52798
52805
  const { sendEvent } = useTraitStateMachine(traits2, uiSlots, opts);
52806
+ const prevTraitNamesRef = useRef("");
52807
+ useEffect(() => {
52808
+ const traitNames = traits2.map((b) => b.trait?.name ?? "").filter(Boolean).sort().join(",");
52809
+ if (prevTraitNamesRef.current && prevTraitNamesRef.current !== traitNames) {
52810
+ navLog.info("page:trait-set-changed", {
52811
+ from: prevTraitNamesRef.current,
52812
+ to: traitNames,
52813
+ action: "clearAll-slots"
52814
+ });
52815
+ uiSlots.clearAll();
52816
+ }
52817
+ prevTraitNamesRef.current = traitNames;
52818
+ }, [traits2, uiSlots]);
52799
52819
  const initSentRef = useRef(false);
52800
52820
  useEffect(() => {
52801
52821
  if (!orbitalNames?.length) {
@@ -52810,6 +52830,10 @@ function TraitInitializer({ traits: traits2, orbitalNames, onNavigate, onLocalFa
52810
52830
  }, 5e3);
52811
52831
  return () => clearTimeout(fallback);
52812
52832
  }, [traits2, orbitalNames, sendEvent, onLocalFallback]);
52833
+ const orbitalsKey = (orbitalNames ?? []).slice().sort().join(",");
52834
+ useEffect(() => {
52835
+ initSentRef.current = false;
52836
+ }, [orbitalsKey]);
52813
52837
  useEffect(() => {
52814
52838
  if (!bridge.connected || !orbitalNames?.length || initSentRef.current) return;
52815
52839
  initSentRef.current = true;
@@ -53050,8 +53074,19 @@ function OrbPreview({
53050
53074
  }, [initialPageName, currentPage]);
53051
53075
  const handleNavigate = useCallback((path) => {
53052
53076
  const match = pages.find(({ page }) => page.path === path);
53077
+ navLog.info("handleNavigate", {
53078
+ path,
53079
+ matched: match?.page.name ?? null,
53080
+ availablePaths: pages.map((p2) => p2.page.path)
53081
+ });
53053
53082
  if (match) {
53054
53083
  setCurrentPage(match.page.name);
53084
+ if (typeof window !== "undefined") {
53085
+ const url = new URL(window.location.href);
53086
+ url.searchParams.set("page", path);
53087
+ window.history.pushState({}, "", url.toString());
53088
+ window.dispatchEvent(new PopStateEvent("popstate"));
53089
+ }
53055
53090
  }
53056
53091
  }, [pages]);
53057
53092
  if (!parseResult.ok) {
@@ -53063,18 +53098,30 @@ function OrbPreview({
53063
53098
  const containerRef = useRef(null);
53064
53099
  useEffect(() => {
53065
53100
  const el = containerRef.current;
53066
- if (!el || pages.length <= 1) return;
53101
+ if (!el) return;
53102
+ if (pages.length <= 1) {
53103
+ navLog.info("interceptor:skipped", { reason: "single-page schema", pageCount: pages.length });
53104
+ return;
53105
+ }
53067
53106
  const handler = (e) => {
53068
53107
  const anchor = e.target.closest("a");
53069
53108
  if (!anchor) return;
53070
53109
  const href = anchor.getAttribute("href") ?? anchor.getAttribute("to") ?? "";
53071
- if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) return;
53110
+ navLog.info("click:intercepted", {
53111
+ href,
53112
+ anchorText: anchor.textContent?.trim().slice(0, 40)
53113
+ });
53114
+ if (!href || href.startsWith("http") || href.startsWith("mailto:") || href.startsWith("#")) {
53115
+ navLog.info("click:skipped", { href, reason: "external/empty/hash" });
53116
+ return;
53117
+ }
53072
53118
  e.preventDefault();
53073
53119
  e.stopPropagation();
53074
53120
  e.stopImmediatePropagation();
53075
53121
  handleNavigate(href);
53076
53122
  };
53077
53123
  el.addEventListener("click", handler, true);
53124
+ navLog.info("interceptor:installed", { pageCount: pages.length, paths: pages.map((p2) => p2.page.path) });
53078
53125
  return () => el.removeEventListener("click", handler, true);
53079
53126
  }, [pages, handleNavigate]);
53080
53127
  return /* @__PURE__ */ jsxs(
@@ -17041,6 +17041,7 @@ var init_DashboardLayout = __esm({
17041
17041
  init_Typography();
17042
17042
  init_Icon();
17043
17043
  init_useAuthContext();
17044
+ init_useEventBus();
17044
17045
  init_useTranslate();
17045
17046
  exports.DashboardLayout = ({
17046
17047
  appName = "{{APP_TITLE}}",
@@ -17048,11 +17049,29 @@ var init_DashboardLayout = __esm({
17048
17049
  navItems = [],
17049
17050
  user: userProp,
17050
17051
  headerActions,
17051
- showSearch = true,
17052
+ showSearch = false,
17053
+ searchEvent,
17054
+ onSearchSubmit,
17055
+ notifications,
17056
+ notificationClickEvent,
17057
+ onNotificationClick,
17058
+ showThemeToggle = true,
17052
17059
  sidebarFooter,
17053
17060
  onSignOut: onSignOutProp,
17054
17061
  children
17055
17062
  }) => {
17063
+ const eventBus = useEventBus();
17064
+ const searchEnabled = showSearch || Boolean(searchEvent) || Boolean(onSearchSubmit);
17065
+ const notificationsEnabled = Array.isArray(notifications);
17066
+ const unreadCount = notificationsEnabled ? notifications.filter((n) => n.read !== true).length : 0;
17067
+ const handleSearchSubmit = (value) => {
17068
+ if (searchEvent) eventBus.emit(`UI:${searchEvent}`, { value });
17069
+ if (onSearchSubmit) onSearchSubmit(value);
17070
+ };
17071
+ const handleNotificationClick = () => {
17072
+ if (notificationClickEvent) eventBus.emit(`UI:${notificationClickEvent}`, {});
17073
+ if (onNotificationClick) onNotificationClick();
17074
+ };
17056
17075
  const [sidebarOpen, setSidebarOpen] = React110.useState(false);
17057
17076
  const [userMenuOpen, setUserMenuOpen] = React110.useState(false);
17058
17077
  const location = reactRouterDom.useLocation();
@@ -17137,17 +17156,7 @@ var init_DashboardLayout = __esm({
17137
17156
  ))
17138
17157
  }
17139
17158
  ),
17140
- sidebarFooter || /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "p-4 border-t border-border dark:border-border", children: /* @__PURE__ */ jsxRuntime.jsxs(
17141
- reactRouterDom.Link,
17142
- {
17143
- to: "/settings",
17144
- className: "flex items-center gap-3 px-3 py-2 text-sm text-muted-foreground dark:text-muted-foreground rounded-lg hover:bg-muted dark:hover:bg-muted",
17145
- children: [
17146
- /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Settings, { className: "h-5 w-5" }),
17147
- t("common.settings")
17148
- ]
17149
- }
17150
- ) })
17159
+ sidebarFooter && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "p-4 border-t border-border dark:border-border", children: sidebarFooter })
17151
17160
  ]
17152
17161
  }
17153
17162
  ),
@@ -17174,32 +17183,40 @@ var init_DashboardLayout = __esm({
17174
17183
  children: /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Menu, { className: "h-5 w-5" })
17175
17184
  }
17176
17185
  ),
17177
- showSearch && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "relative", children: [
17186
+ searchEnabled && /* @__PURE__ */ jsxRuntime.jsx(exports.Box, { className: "hidden sm:block flex-1 max-w-md", children: /* @__PURE__ */ jsxRuntime.jsxs(exports.Box, { className: "relative", children: [
17178
17187
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground dark:text-muted-foreground" }),
17179
17188
  /* @__PURE__ */ jsxRuntime.jsx(
17180
17189
  exports.Input,
17181
17190
  {
17182
17191
  type: "search",
17183
17192
  placeholder: t("common.search"),
17184
- className: "pl-10 w-full"
17193
+ className: "pl-10 w-full",
17194
+ onKeyDown: (e) => {
17195
+ if (e.key === "Enter") {
17196
+ handleSearchSubmit(e.target.value);
17197
+ }
17198
+ }
17185
17199
  }
17186
17200
  )
17187
17201
  ] }) }),
17188
17202
  /* @__PURE__ */ jsxRuntime.jsxs(exports.HStack, { align: "center", gap: "xs", children: [
17189
17203
  headerActions,
17190
- /* @__PURE__ */ jsxRuntime.jsx(exports.ThemeToggle, {}),
17191
- /* @__PURE__ */ jsxRuntime.jsxs(
17204
+ showThemeToggle && /* @__PURE__ */ jsxRuntime.jsx(exports.ThemeToggle, {}),
17205
+ notificationsEnabled && /* @__PURE__ */ jsxRuntime.jsxs(
17192
17206
  exports.Button,
17193
17207
  {
17194
17208
  variant: "ghost",
17195
17209
  className: "relative p-2 rounded-full hover:bg-muted dark:hover:bg-muted",
17210
+ onClick: handleNotificationClick,
17211
+ "aria-label": t("common.notifications"),
17196
17212
  children: [
17197
17213
  /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Bell, { className: "h-5 w-5 text-muted-foreground dark:text-muted-foreground" }),
17198
- /* @__PURE__ */ jsxRuntime.jsx(
17214
+ unreadCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
17199
17215
  exports.Box,
17200
17216
  {
17201
17217
  as: "span",
17202
- className: "absolute top-1 right-1 w-2 h-2 bg-error rounded-full"
17218
+ className: "absolute -top-0.5 -right-0.5 min-w-[18px] h-[18px] px-1 bg-error rounded-full text-[10px] font-semibold text-white flex items-center justify-center",
17219
+ children: unreadCount > 99 ? "99+" : unreadCount
17203
17220
  }
17204
17221
  )
17205
17222
  ]
@@ -17264,17 +17281,6 @@ var init_DashboardLayout = __esm({
17264
17281
  }
17265
17282
  )
17266
17283
  ] }),
17267
- /* @__PURE__ */ jsxRuntime.jsxs(
17268
- reactRouterDom.Link,
17269
- {
17270
- to: "/settings",
17271
- className: "flex items-center gap-2 px-4 py-2 text-sm text-foreground dark:text-foreground hover:bg-muted dark:hover:bg-muted",
17272
- children: [
17273
- /* @__PURE__ */ jsxRuntime.jsx(LucideIcons.Settings, { className: "h-4 w-4" }),
17274
- t("common.settings")
17275
- ]
17276
- }
17277
- ),
17278
17284
  /* @__PURE__ */ jsxRuntime.jsxs(
17279
17285
  exports.Button,
17280
17286
  {