@petrarca/sonnet-shell 0.1.4 → 0.1.6

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.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { RouteObject } from 'react-router-dom';
3
- import { LucideIcon } from 'lucide-react';
4
3
  import * as React from 'react';
5
4
  import React__default, { ReactNode } from 'react';
5
+ import { LucideIcon } from 'lucide-react';
6
6
 
7
7
  interface SidePaneOptions {
8
8
  /** Content to render inside the side pane. */
@@ -22,6 +22,13 @@ interface SidePaneOptions {
22
22
  * Omit or pass Infinity for unlimited (fills remaining space up to viewport).
23
23
  */
24
24
  maxWidth?: number;
25
+ /**
26
+ * When true, the pane expands to fill the entire content area (flex-1),
27
+ * collapsing the main route content to zero width. Use for full-screen
28
+ * tool views (schema viewer, form editors). The drag handle is hidden.
29
+ * Toggle via sidePane.setFullWidth(boolean) from inside the pane content.
30
+ */
31
+ fullWidth?: boolean;
25
32
  }
26
33
  interface SidePaneState {
27
34
  /** Currently open pane options, or null when closed. */
@@ -29,6 +36,8 @@ interface SidePaneState {
29
36
  open: (opts: SidePaneOptions) => void;
30
37
  close: () => void;
31
38
  toggle: (opts: SidePaneOptions) => void;
39
+ /** Expand or collapse the pane to fill the full content area. */
40
+ setFullWidth: (fullWidth: boolean) => void;
32
41
  }
33
42
  declare const SidePaneContext: React.Context<SidePaneState | null>;
34
43
  /**
@@ -144,15 +153,41 @@ interface ServiceModule {
144
153
  */
145
154
  commands?: ModuleCommand[];
146
155
  /**
147
- * Side pane sizing constraints for modules that render a persistent inline
148
- * panel via sidePane.open(). Declared here so the module definition is
156
+ * Side pane sizing constraints and content factory for modules that render
157
+ * a persistent inline panel. Declared here so the module definition is
149
158
  * self-documenting; passed through to sidePane.open() at call time.
150
159
  *
151
160
  * When a module has no SubNavPanel links (navigation: []) and declares
152
161
  * sidePane, the shell treats icon-rail clicks as toggle actions on the
153
162
  * pane rather than route navigations.
163
+ *
164
+ * `contentFactory` is called by the shell to produce the pane content when
165
+ * the user toggles the pane via the icon rail. If omitted, the pane opens
166
+ * empty and the module must call sidePane.open() with content explicitly.
167
+ */
168
+ sidePane?: Pick<SidePaneOptions, "defaultWidth" | "minWidth" | "maxWidth"> & {
169
+ contentFactory?: () => React__default.ReactNode;
170
+ /**
171
+ * Whether to collapse the SubNavPanel when this pane opens via the icon rail.
172
+ * Default: true — prevents a prior module's SubNav from lingering while the pane is shown.
173
+ * Set to false if the pane is intended to supplement the current nav context.
174
+ *
175
+ * Design limitation: a module may not define both `sidePane` and `navigation` items.
176
+ * Modules with navigation use the SubNavPanel exclusively; modules with `sidePane`
177
+ * toggle the SidePane via the icon rail. A module defining both results in undefined
178
+ * shell behavior.
179
+ */
180
+ collapseSubNav?: boolean;
181
+ };
182
+ /**
183
+ * Content area layout mode for this module's routes.
184
+ *
185
+ * - `"default"` (default): routes render inside `ScrollArea > main.p-6`
186
+ * - `"full"`: routes render directly in a `h-full w-full overflow-hidden`
187
+ * container — no scroll wrapper, no padding. Use for graph viewers,
188
+ * canvases, or any route that manages its own scroll and height.
154
189
  */
155
- sidePane?: Pick<SidePaneOptions, "defaultWidth" | "minWidth" | "maxWidth">;
190
+ layout?: "default" | "full";
156
191
  }
157
192
 
158
193
  /**
@@ -466,11 +501,12 @@ type SidePaneOpenFn = (opts: SidePaneOptions) => void;
466
501
  type SidePaneCloseFn = () => void;
467
502
  type SidePaneIsOpenFn = () => boolean;
468
503
  type SidePaneActiveModuleIdFn = () => string | null;
504
+ type SidePaneSetFullWidthFn = (fullWidth: boolean) => void;
469
505
  /**
470
506
  * Called by the shell during initialization to inject side pane controls.
471
507
  * Not part of the public module API -- only the shell calls this.
472
508
  */
473
- declare function initSidePane(openFn: SidePaneOpenFn, closeFn: SidePaneCloseFn, isOpenFn: SidePaneIsOpenFn, activeModuleIdFn: SidePaneActiveModuleIdFn): void;
509
+ declare function initSidePane(openFn: SidePaneOpenFn, closeFn: SidePaneCloseFn, isOpenFn: SidePaneIsOpenFn, activeModuleIdFn: SidePaneActiveModuleIdFn, setFullWidthFn?: SidePaneSetFullWidthFn): void;
474
510
  declare const sidePane: {
475
511
  /** Open the side pane with the given content and sizing options. */
476
512
  open(opts: SidePaneOptions): void;
@@ -488,6 +524,14 @@ declare const sidePane: {
488
524
  isOpen(): boolean;
489
525
  /** Returns the moduleId of the currently open pane, or null. */
490
526
  activeModuleId(): string | null;
527
+ /**
528
+ * Expand or collapse the pane to fill the full content area.
529
+ *
530
+ * When true the pane takes flex-1 and hides the main route content.
531
+ * Use for full-screen tool views (schema viewer, form editors).
532
+ * Call setFullWidth(false) to restore normal inline sizing.
533
+ */
534
+ setFullWidth(fullWidth: boolean): void;
491
535
  };
492
536
  interface FullscreenOptions {
493
537
  /** React content to render inside the fullscreen overlay. */
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  // src/AppShell.tsx
2
- import { useState, useEffect as useEffect3, useCallback as useCallback2, useRef as useRef2, useMemo as useMemo3 } from "react";
2
+ import {
3
+ useState,
4
+ useEffect as useEffect3,
5
+ useCallback as useCallback2,
6
+ useRef as useRef2,
7
+ useMemo as useMemo3
8
+ } from "react";
3
9
  import { Outlet, useLocation as useLocation2, useNavigate as useNavigate2 } from "react-router-dom";
4
10
  import { TooltipProvider } from "@petrarca/sonnet-ui";
5
11
  import { ScrollArea as ScrollArea2 } from "@petrarca/sonnet-ui";
@@ -59,7 +65,7 @@ function ConfirmDialog({
59
65
  // src/TopBar.tsx
60
66
  import { jsx as jsx2 } from "react/jsx-runtime";
61
67
  function TopBar({ children }) {
62
- return /* @__PURE__ */ jsx2("header", { className: "h-12 shrink-0 border-b border-blue-200 flex items-center justify-between px-3 bg-blue-100/70 z-20", children });
68
+ return /* @__PURE__ */ jsx2("header", { className: "h-12 shrink-0 border-b border-border flex items-center justify-between px-3 bg-background z-20", children });
63
69
  }
64
70
 
65
71
  // src/IconRail.tsx
@@ -88,7 +94,7 @@ function IconRail({
88
94
  }) {
89
95
  const { mainModules, bottomModules } = useShellModules();
90
96
  const isActive = (id) => activeServiceId === id || activeSidePaneModuleId === id;
91
- return /* @__PURE__ */ jsxs2("nav", { className: "w-12 shrink-0 border-r bg-muted/30 flex flex-col items-center py-2 gap-1", children: [
97
+ return /* @__PURE__ */ jsxs2("nav", { className: "w-14 shrink-0 border-r bg-muted flex flex-col items-center py-2 gap-1", children: [
92
98
  /* @__PURE__ */ jsx3("div", { className: "flex-1 flex flex-col items-center gap-1 overflow-y-auto", children: mainModules.map((service) => /* @__PURE__ */ jsx3(
93
99
  RailIcon,
94
100
  {
@@ -120,11 +126,11 @@ function RailIcon({ service, isActive, onClick }) {
120
126
  size: "compact",
121
127
  onClick,
122
128
  className: cn(
123
- "h-9 w-9 rounded-lg transition-colors",
129
+ "h-10 w-10 rounded-lg transition-colors",
124
130
  isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-foreground"
125
131
  ),
126
132
  children: [
127
- /* @__PURE__ */ jsx3(Icon, { className: "h-[18px] w-[18px]" }),
133
+ /* @__PURE__ */ jsx3(Icon, { className: "h-5 w-5" }),
128
134
  /* @__PURE__ */ jsx3("span", { className: "sr-only", children: service.label })
129
135
  ]
130
136
  }
@@ -302,11 +308,13 @@ var _sidePaneOpen = null;
302
308
  var _sidePaneClose = null;
303
309
  var _sidePaneIsOpen = null;
304
310
  var _sidePaneActiveModuleId = null;
305
- function initSidePane(openFn, closeFn, isOpenFn, activeModuleIdFn) {
311
+ var _sidePaneSetFullWidth = null;
312
+ function initSidePane(openFn, closeFn, isOpenFn, activeModuleIdFn, setFullWidthFn) {
306
313
  _sidePaneOpen = openFn;
307
314
  _sidePaneClose = closeFn;
308
315
  _sidePaneIsOpen = isOpenFn;
309
316
  _sidePaneActiveModuleId = activeModuleIdFn;
317
+ _sidePaneSetFullWidth = setFullWidthFn ?? null;
310
318
  }
311
319
  var sidePane = {
312
320
  /** Open the side pane with the given content and sizing options. */
@@ -350,6 +358,20 @@ var sidePane = {
350
358
  /** Returns the moduleId of the currently open pane, or null. */
351
359
  activeModuleId() {
352
360
  return _sidePaneActiveModuleId ? _sidePaneActiveModuleId() : null;
361
+ },
362
+ /**
363
+ * Expand or collapse the pane to fill the full content area.
364
+ *
365
+ * When true the pane takes flex-1 and hides the main route content.
366
+ * Use for full-screen tool views (schema viewer, form editors).
367
+ * Call setFullWidth(false) to restore normal inline sizing.
368
+ */
369
+ setFullWidth(fullWidth) {
370
+ if (_sidePaneSetFullWidth) {
371
+ _sidePaneSetFullWidth(fullWidth);
372
+ } else {
373
+ warnLog2("sidePane.setFullWidth: shell not initialized yet.");
374
+ }
353
375
  }
354
376
  };
355
377
  var _fullscreenEnter = null;
@@ -447,7 +469,7 @@ function SubNavPanel({
447
469
  }
448
470
  return /* @__PURE__ */ jsxs3("aside", { className: "w-52 shrink-0 border-r bg-background flex flex-col", children: [
449
471
  /* @__PURE__ */ jsxs3("div", { className: "h-10 shrink-0 flex items-center justify-between px-4", children: [
450
- /* @__PURE__ */ jsx4("span", { className: "heading-compact text-muted-foreground uppercase tracking-wider", children: service.label }),
472
+ /* @__PURE__ */ jsx4("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: service.label }),
451
473
  /* @__PURE__ */ jsxs3(Tooltip2, { children: [
452
474
  /* @__PURE__ */ jsx4(TooltipTrigger2, { asChild: true, children: /* @__PURE__ */ jsx4(
453
475
  "button",
@@ -461,7 +483,7 @@ function SubNavPanel({
461
483
  ] })
462
484
  ] }),
463
485
  /* @__PURE__ */ jsx4(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsx4("nav", { className: "px-2 pb-4", children: mergedNavigation.map((group, gi) => /* @__PURE__ */ jsxs3("div", { className: cn2(gi > 0 && "mt-4"), children: [
464
- group.heading && /* @__PURE__ */ jsx4("h6", { className: "heading-meta px-2 mb-1", children: group.heading }),
486
+ group.heading && /* @__PURE__ */ jsx4("h6", { className: "text-xs font-medium font-mono uppercase tracking-wider text-muted-foreground px-2 mb-1", children: group.heading }),
465
487
  /* @__PURE__ */ jsx4("ul", { className: "space-y-0.5", children: group.links.map((link) => {
466
488
  const isActive = pathname === link.path;
467
489
  return /* @__PURE__ */ jsx4("li", { children: /* @__PURE__ */ jsx4(
@@ -498,28 +520,56 @@ function useSidePaneState() {
498
520
 
499
521
  // src/SidePane.tsx
500
522
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
523
+ function DragHandle({
524
+ separatorProps,
525
+ onPointerDown,
526
+ onDoubleClick
527
+ }) {
528
+ return /* @__PURE__ */ jsx5(
529
+ "div",
530
+ {
531
+ ...separatorProps,
532
+ onPointerDown,
533
+ onDoubleClick,
534
+ className: cn3(
535
+ "absolute right-0 top-0 h-full w-1 cursor-col-resize",
536
+ "hover:bg-primary/40 active:bg-primary/60 transition-colors"
537
+ )
538
+ }
539
+ );
540
+ }
501
541
  var DEFAULT_WIDTH = 320;
502
542
  var DEFAULT_MIN = 240;
503
543
  var DEFAULT_MAX = 800;
504
544
  function SidePane() {
505
545
  const { pane, close } = useSidePaneState();
546
+ if (!pane) return null;
547
+ return /* @__PURE__ */ jsx5(SidePaneOpen, { pane, onClose: close });
548
+ }
549
+ function SidePaneOpen({
550
+ pane,
551
+ onClose
552
+ }) {
506
553
  const { panelWidth, handlePointerDown, handleDoubleClick, separatorProps } = useResizablePanel({
507
- defaultWidth: pane?.defaultWidth ?? DEFAULT_WIDTH,
508
- minWidth: pane?.minWidth ?? DEFAULT_MIN,
509
- maxWidth: pane?.maxWidth ?? DEFAULT_MAX,
554
+ defaultWidth: pane.defaultWidth ?? DEFAULT_WIDTH,
555
+ minWidth: pane.minWidth ?? DEFAULT_MIN,
556
+ maxWidth: pane.maxWidth ?? DEFAULT_MAX,
510
557
  direction: "right-edge"
511
558
  });
512
- if (!pane) return null;
559
+ const isFullWidth = pane.fullWidth === true;
513
560
  return /* @__PURE__ */ jsxs4(
514
561
  "div",
515
562
  {
516
- className: "relative flex shrink-0 flex-col border-r bg-background",
517
- style: { width: panelWidth },
563
+ className: cn3(
564
+ "relative flex flex-col border-r bg-background",
565
+ isFullWidth ? "flex-1" : "shrink-0"
566
+ ),
567
+ style: isFullWidth ? void 0 : { width: panelWidth },
518
568
  children: [
519
569
  /* @__PURE__ */ jsx5("div", { className: "flex h-10 shrink-0 items-center justify-end border-b px-2", children: /* @__PURE__ */ jsx5(
520
570
  "button",
521
571
  {
522
- onClick: close,
572
+ onClick: onClose,
523
573
  className: cn3(
524
574
  "flex h-6 w-6 items-center justify-center rounded text-muted-foreground",
525
575
  "hover:bg-accent hover:text-foreground transition-colors",
@@ -530,16 +580,12 @@ function SidePane() {
530
580
  }
531
581
  ) }),
532
582
  /* @__PURE__ */ jsx5("div", { className: "flex-1 overflow-auto", children: pane.content }),
533
- /* @__PURE__ */ jsx5(
534
- "div",
583
+ !isFullWidth && /* @__PURE__ */ jsx5(
584
+ DragHandle,
535
585
  {
536
- ...separatorProps,
586
+ separatorProps,
537
587
  onPointerDown: handlePointerDown,
538
- onDoubleClick: handleDoubleClick,
539
- className: cn3(
540
- "absolute right-0 top-0 h-full w-1 cursor-col-resize",
541
- "hover:bg-primary/40 active:bg-primary/60 transition-colors"
542
- )
588
+ onDoubleClick: handleDoubleClick
543
589
  }
544
590
  )
545
591
  ]
@@ -677,15 +723,18 @@ function selectSidePaneModule(service, current, setSidePaneState, navigate) {
677
723
  } else {
678
724
  setSidePaneState({
679
725
  moduleId: service.id,
680
- content: null,
726
+ content: service.sidePane?.contentFactory?.() ?? null,
681
727
  defaultWidth: service.sidePane?.defaultWidth,
682
728
  minWidth: service.sidePane?.minWidth,
683
729
  maxWidth: service.sidePane?.maxWidth
684
730
  });
685
- navigate(service.basePath);
731
+ if (service.routes.length > 0) {
732
+ navigate(service.basePath);
733
+ }
686
734
  }
687
735
  }
688
- function selectNavModule(service, navigate, setSubNavCollapsed) {
736
+ function selectNavModule(service, navigate, setSubNavCollapsed, closeSidePane) {
737
+ closeSidePane();
689
738
  setSubNavCollapsed(false);
690
739
  const target = service.navigation[0]?.links[0]?.path ?? service.basePath;
691
740
  navigate(target);
@@ -702,7 +751,8 @@ function useShellApiInit(deps) {
702
751
  r.handleSidePaneOpen,
703
752
  r.handleSidePaneClose,
704
753
  r.handleSidePaneIsOpen,
705
- r.handleSidePaneActiveModuleId
754
+ r.handleSidePaneActiveModuleId,
755
+ r.handleSidePaneSetFullWidth
706
756
  );
707
757
  initDialog(
708
758
  (opts) => new Promise((resolve) => r.setDialogState({ opts, resolve }))
@@ -753,13 +803,17 @@ function useSidePaneState2() {
753
803
  () => sidePaneState?.moduleId ?? null,
754
804
  [sidePaneState]
755
805
  );
806
+ const handleSetFullWidth = useCallback2((fullWidth) => {
807
+ setSidePaneState((prev) => prev ? { ...prev, fullWidth } : prev);
808
+ }, []);
756
809
  return {
757
810
  sidePaneState,
758
811
  setSidePaneState,
759
812
  handleOpen,
760
813
  handleClose,
761
814
  handleIsOpen,
762
- handleActiveModuleId
815
+ handleActiveModuleId,
816
+ handleSetFullWidth
763
817
  };
764
818
  }
765
819
  function usePanelState() {
@@ -838,6 +892,7 @@ function AppShell({ registry }) {
838
892
  handleSidePaneClose: sidePane2.handleClose,
839
893
  handleSidePaneIsOpen: sidePane2.handleIsOpen,
840
894
  handleSidePaneActiveModuleId: sidePane2.handleActiveModuleId,
895
+ handleSidePaneSetFullWidth: sidePane2.handleSetFullWidth,
841
896
  setDialogState: dialog2.setDialogState,
842
897
  featureLookup
843
898
  });
@@ -852,17 +907,27 @@ function AppShell({ registry }) {
852
907
  sidePane2.setSidePaneState,
853
908
  navigate
854
909
  );
910
+ if (service.sidePane?.collapseSubNav !== false) {
911
+ setSubNavCollapsed(true);
912
+ }
855
913
  } else {
856
- selectNavModule(service, navigate, setSubNavCollapsed);
914
+ selectNavModule(
915
+ service,
916
+ navigate,
917
+ setSubNavCollapsed,
918
+ sidePane2.handleClose
919
+ );
920
+ setSelectedServiceId(serviceId);
857
921
  }
858
- setSelectedServiceId(serviceId);
859
922
  },
860
923
  [
861
924
  modules,
862
925
  sidePane2.sidePaneState,
863
926
  sidePane2.setSidePaneState,
927
+ sidePane2.handleClose,
864
928
  navigate,
865
- setSelectedServiceId
929
+ setSelectedServiceId,
930
+ setSubNavCollapsed
866
931
  ]
867
932
  );
868
933
  const sidePaneContextValue = useMemo3(
@@ -876,7 +941,8 @@ function AppShell({ registry }) {
876
941
  } else {
877
942
  sidePane2.setSidePaneState(opts);
878
943
  }
879
- }
944
+ },
945
+ setFullWidth: sidePane2.handleSetFullWidth
880
946
  }),
881
947
  [sidePane2]
882
948
  );
@@ -889,26 +955,17 @@ function AppShell({ registry }) {
889
955
  onOpenChange: setCommandMenuOpen
890
956
  }
891
957
  ),
892
- /* @__PURE__ */ jsxs6("div", { className: "flex flex-1 overflow-hidden", children: [
893
- /* @__PURE__ */ jsx7(
894
- IconRail,
895
- {
896
- activeServiceId: activeService?.id ?? null,
897
- activeSidePaneModuleId: sidePane2.sidePaneState?.moduleId ?? null,
898
- onServiceSelect: handleServiceSelect
899
- }
900
- ),
901
- activeService && activeService.navigation.length > 0 && /* @__PURE__ */ jsx7(
902
- SubNavPanel,
903
- {
904
- service: activeService,
905
- collapsed: subNavCollapsed,
906
- onToggleCollapse: () => setSubNavCollapsed((c) => !c)
907
- }
908
- ),
909
- /* @__PURE__ */ jsx7(SidePane, {}),
910
- /* @__PURE__ */ jsx7("div", { className: "flex-1 min-w-0 overflow-hidden", children: /* @__PURE__ */ jsx7(ScrollArea2, { className: "h-full", children: /* @__PURE__ */ jsx7("main", { className: "p-6", children: /* @__PURE__ */ jsx7(Outlet, {}) }) }) })
911
- ] }),
958
+ /* @__PURE__ */ jsx7(
959
+ ShellBody,
960
+ {
961
+ activeService,
962
+ sidePaneFullWidth: sidePane2.sidePaneState?.fullWidth,
963
+ sidePaneModuleId: sidePane2.sidePaneState?.moduleId ?? null,
964
+ subNavCollapsed,
965
+ onToggleSubNav: () => setSubNavCollapsed((c) => !c),
966
+ onServiceSelect: handleServiceSelect
967
+ }
968
+ ),
912
969
  dialog2.dialogState && /* @__PURE__ */ jsx7(
913
970
  ConfirmDialog,
914
971
  {
@@ -970,6 +1027,41 @@ function SlideOverPanel({
970
1027
  }
971
1028
  );
972
1029
  }
1030
+ function ShellBody({
1031
+ activeService,
1032
+ sidePaneFullWidth,
1033
+ sidePaneModuleId,
1034
+ subNavCollapsed,
1035
+ onToggleSubNav,
1036
+ onServiceSelect
1037
+ }) {
1038
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-1 overflow-hidden", children: [
1039
+ /* @__PURE__ */ jsx7(
1040
+ IconRail,
1041
+ {
1042
+ activeServiceId: activeService?.id ?? null,
1043
+ activeSidePaneModuleId: sidePaneModuleId,
1044
+ onServiceSelect
1045
+ }
1046
+ ),
1047
+ activeService && activeService.navigation.length > 0 && /* @__PURE__ */ jsx7(
1048
+ SubNavPanel,
1049
+ {
1050
+ service: activeService,
1051
+ collapsed: subNavCollapsed,
1052
+ onToggleCollapse: onToggleSubNav
1053
+ }
1054
+ ),
1055
+ /* @__PURE__ */ jsx7(SidePane, {}),
1056
+ sidePaneFullWidth !== true && /* @__PURE__ */ jsx7("div", { className: "flex-1 min-w-0 h-full overflow-hidden", children: /* @__PURE__ */ jsx7(ContentArea, { layout: activeService?.layout }) })
1057
+ ] });
1058
+ }
1059
+ function ContentArea({ layout }) {
1060
+ if (layout === "full") {
1061
+ return /* @__PURE__ */ jsx7("div", { className: "h-full w-full overflow-hidden", children: /* @__PURE__ */ jsx7(Outlet, {}) });
1062
+ }
1063
+ return /* @__PURE__ */ jsx7(ScrollArea2, { className: "h-full", children: /* @__PURE__ */ jsx7("main", { className: "p-6", children: /* @__PURE__ */ jsx7(Outlet, {}) }) });
1064
+ }
973
1065
  function PanelContent({ state }) {
974
1066
  return /* @__PURE__ */ jsxs6(Fragment2, { children: [
975
1067
  state.title ? /* @__PURE__ */ jsxs6(SheetHeader, { children: [
@@ -1028,10 +1120,10 @@ function SearchTrigger() {
1028
1120
  {
1029
1121
  variant: "outline",
1030
1122
  size: "sm",
1031
- className: "h-7 gap-2 text-xs text-muted-foreground w-52 justify-start",
1123
+ className: "h-8 gap-2 text-sm text-muted-foreground w-56 justify-start",
1032
1124
  onClick: () => navigation.openCommandMenu(),
1033
1125
  children: [
1034
- /* @__PURE__ */ jsx11(Search, { className: "h-3.5 w-3.5" }),
1126
+ /* @__PURE__ */ jsx11(Search, { className: "h-4 w-4" }),
1035
1127
  /* @__PURE__ */ jsx11("span", { children: "Search..." }),
1036
1128
  /* @__PURE__ */ jsxs9("kbd", { className: "ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground", children: [
1037
1129
  /* @__PURE__ */ jsx11("span", { className: "text-xs", children: "\u2318" }),
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/AppShell.tsx","../src/ConfirmDialog.tsx","../src/TopBar.tsx","../src/IconRail.tsx","../src/shellModules.ts","../src/SubNavPanel.tsx","../src/hooks.ts","../src/api.ts","../src/eventBus.ts","../src/SidePane.tsx","../src/sidePaneState.ts","../src/CommandMenu.tsx","../src/shellConfig.ts","../src/RootLayout.tsx","../src/ShellConfigProvider.tsx","../src/PlaceholderPage.tsx","../src/SearchTrigger.tsx","../src/UserMenu.tsx","../src/createModuleRegistry.ts","../src/OverviewCard.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useRef, useMemo } from \"react\";\nimport { Outlet, useLocation, useNavigate } from \"react-router-dom\";\nimport { TooltipProvider } from \"@petrarca/sonnet-ui\";\nimport { ScrollArea } from \"@petrarca/sonnet-ui\";\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetBody,\n} from \"@petrarca/sonnet-ui\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\nimport TopBar from \"./TopBar\";\nimport IconRail from \"./IconRail\";\nimport SubNavPanel from \"./SubNavPanel\";\nimport SidePane from \"./SidePane\";\nimport CommandMenu from \"./CommandMenu\";\nimport {\n initNavigation,\n initFeatureNav,\n initPanel,\n initDialog,\n initFullscreen,\n initSidePane,\n type PanelOptions,\n type ConfirmOptions,\n type FullscreenOptions,\n} from \"./api\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\nimport { ShellModulesContext } from \"./shellModules\";\nimport { useShellConfig } from \"./shellConfig\";\nimport { SidePaneContext, type SidePaneOptions } from \"./sidePaneState\";\n\n// Maps PanelOptions.width tokens to Tailwind width classes.\nconst PANEL_WIDTH_CLASS: Record<string, string> = {\n narrow: \"w-[480px]\",\n default: \"w-[640px]\",\n wide: \"w-[800px]\",\n half: \"w-[50vw]\",\n full: \"w-screen\",\n // legacy aliases\n lg: \"w-[640px]\",\n \"1/3\": \"w-[33vw]\",\n \"1/2\": \"w-[50vw]\",\n};\n\n// -- Helpers extracted to reduce AppShell complexity --\n\nfunction isSidePaneModule(service: {\n sidePane?: unknown;\n navigation: unknown[];\n}): boolean {\n return !!service.sidePane && service.navigation.length === 0;\n}\n\nfunction selectSidePaneModule(\n service: {\n id: string;\n basePath: string;\n sidePane?: { defaultWidth?: number; minWidth?: number; maxWidth?: number };\n },\n current: SidePaneOptions | null,\n setSidePaneState: (s: SidePaneOptions | null) => void,\n navigate: (path: string) => void,\n): void {\n if (current?.moduleId === service.id) {\n setSidePaneState(null);\n } else {\n setSidePaneState({\n moduleId: service.id,\n content: null,\n defaultWidth: service.sidePane?.defaultWidth,\n minWidth: service.sidePane?.minWidth,\n maxWidth: service.sidePane?.maxWidth,\n });\n navigate(service.basePath);\n }\n}\n\nfunction selectNavModule(\n service: {\n basePath: string;\n navigation: Array<{ links: Array<{ path: string }> }>;\n },\n navigate: (path: string) => void,\n setSubNavCollapsed: (v: boolean) => void,\n): void {\n setSubNavCollapsed(false);\n const target = service.navigation[0]?.links[0]?.path ?? service.basePath;\n navigate(target);\n}\n\n// Wire all imperative shell APIs once on mount.\nfunction useShellApiInit(deps: {\n navigate: ReturnType<typeof useNavigate>;\n openCommandMenu: () => void;\n handlePanelOpen: (opts: PanelOptions) => void;\n handlePanelClose: () => void;\n handleFullscreenEnter: (opts: FullscreenOptions) => void;\n handleFullscreenExit: () => void;\n handleSidePaneOpen: (opts: SidePaneOptions) => void;\n handleSidePaneClose: () => void;\n handleSidePaneIsOpen: () => boolean;\n handleSidePaneActiveModuleId: () => string | null;\n setDialogState: React.Dispatch<React.SetStateAction<DialogState | null>>;\n featureLookup: (featureId: string) => string | null;\n}) {\n const stableRefs = useRef(deps);\n useEffect(() => {\n const r = stableRefs.current;\n initNavigation(r.navigate, r.openCommandMenu);\n initFeatureNav(r.featureLookup);\n initPanel(r.handlePanelOpen, r.handlePanelClose);\n initFullscreen(r.handleFullscreenEnter, r.handleFullscreenExit);\n initSidePane(\n r.handleSidePaneOpen,\n r.handleSidePaneClose,\n r.handleSidePaneIsOpen,\n r.handleSidePaneActiveModuleId,\n );\n initDialog(\n (opts: ConfirmOptions) =>\n new Promise<boolean>((resolve) => r.setDialogState({ opts, resolve })),\n );\n }, []);\n}\n\ninterface AppShellProps {\n registry: ModuleRegistry;\n}\n\ninterface DialogState {\n opts: ConfirmOptions;\n resolve: (confirmed: boolean) => void;\n}\n\n/**\n * AppShell -- the main layout skeleton.\n *\n * Owns the panel (slide-over) -- modules request it via panel.open(),\n * the shell renders the provided content inside a Sheet.\n *\n * Receives its module registry as a prop so it has no dependency on\n * app-specific module lists.\n */\n// Hook: track which service is active based on URL.\nfunction useActiveService(modules: ModuleRegistry[\"modules\"]) {\n const location = useLocation();\n const serviceByBasePath = useMemo(\n () =>\n new Map(\n modules\n .filter((m) => !m.hidden && m.basePath !== \"/\")\n .map((m) => [m.basePath, m.id]),\n ),\n [modules],\n );\n const resolveServiceFromPath = useCallback(\n (pathname: string): string | null => {\n for (const [basePath, id] of serviceByBasePath) {\n if (pathname.startsWith(basePath)) return id;\n }\n return null;\n },\n [serviceByBasePath],\n );\n const [selectedServiceId, setSelectedServiceId] = useState<string | null>(\n () => resolveServiceFromPath(location.pathname),\n );\n useEffect(\n () => setSelectedServiceId(resolveServiceFromPath(location.pathname)),\n [location.pathname, resolveServiceFromPath],\n );\n const activeService = selectedServiceId\n ? (modules.find((s) => s.id === selectedServiceId) ?? null)\n : null;\n return { activeService, selectedServiceId, setSelectedServiceId };\n}\n\n// Hook: side pane state and handlers.\nfunction useSidePaneState() {\n const [sidePaneState, setSidePaneState] = useState<SidePaneOptions | null>(\n null,\n );\n const handleOpen = useCallback(\n (opts: SidePaneOptions) => setSidePaneState(opts),\n [],\n );\n const handleClose = useCallback(() => setSidePaneState(null), []);\n const handleIsOpen = useCallback(\n () => sidePaneState !== null,\n [sidePaneState],\n );\n const handleActiveModuleId = useCallback(\n () => sidePaneState?.moduleId ?? null,\n [sidePaneState],\n );\n return {\n sidePaneState,\n setSidePaneState,\n handleOpen,\n handleClose,\n handleIsOpen,\n handleActiveModuleId,\n };\n}\n\n// Hook: slide-over panel state and handlers.\nfunction usePanelState() {\n const [panelState, setPanelState] = useState<PanelOptions | null>(null);\n const panelOnCloseRef = useRef<(() => void) | undefined>(undefined);\n const handleOpen = useCallback(\n (opts: PanelOptions) => setPanelState(opts),\n [],\n );\n const handleClose = useCallback(() => {\n panelOnCloseRef.current = panelState?.onClose;\n setPanelState(null);\n }, [panelState]);\n const panelWidth =\n PANEL_WIDTH_CLASS[panelState?.width ?? \"default\"] ??\n PANEL_WIDTH_CLASS.default;\n const panelOffsetTop = panelState?.coverage === \"full\" ? \"0px\" : \"3rem\";\n return {\n panelState,\n panelOnCloseRef,\n handleOpen,\n handleClose,\n panelWidth,\n panelOffsetTop,\n };\n}\n\n// Hook: confirm dialog state and handlers.\nfunction useDialogState() {\n const [dialogState, setDialogState] = useState<DialogState | null>(null);\n const handleConfirm = useCallback(() => {\n dialogState?.resolve(true);\n setDialogState(null);\n }, [dialogState]);\n const handleCancel = useCallback(() => {\n dialogState?.resolve(false);\n setDialogState(null);\n }, [dialogState]);\n return { dialogState, setDialogState, handleConfirm, handleCancel };\n}\n\n// Build a stable feature id -> path lookup from the module registry.\nfunction useFeatureLookup(modules: ModuleRegistry[\"modules\"]) {\n return useMemo(() => {\n const map = new Map<string, string>();\n for (const m of modules) {\n for (const group of m.navigation) {\n for (const link of group.links) {\n if (link.id) map.set(link.id, link.path);\n }\n }\n }\n return (featureId: string) => map.get(featureId) ?? null;\n }, [modules]);\n}\n\nexport default function AppShell({ registry }: AppShellProps) {\n const navigate = useNavigate();\n const config = useShellConfig();\n const { modules } = registry;\n\n const { activeService, setSelectedServiceId } = useActiveService(modules);\n const [subNavCollapsed, setSubNavCollapsed] = useState(false);\n const [commandMenuOpen, setCommandMenuOpen] = useState(false);\n const openCommandMenu = useCallback(() => setCommandMenuOpen(true), []);\n const [fullscreenState, setFullscreenState] =\n useState<FullscreenOptions | null>(null);\n const handleFullscreenEnter = useCallback(\n (opts: FullscreenOptions) => setFullscreenState(opts),\n [],\n );\n const handleFullscreenExit = useCallback(() => setFullscreenState(null), []);\n\n const sidePane = useSidePaneState();\n const panel = usePanelState();\n const dialog = useDialogState();\n const featureLookup = useFeatureLookup(modules);\n\n useShellApiInit({\n navigate,\n openCommandMenu,\n handlePanelOpen: panel.handleOpen,\n handlePanelClose: panel.handleClose,\n handleFullscreenEnter,\n handleFullscreenExit,\n handleSidePaneOpen: sidePane.handleOpen,\n handleSidePaneClose: sidePane.handleClose,\n handleSidePaneIsOpen: sidePane.handleIsOpen,\n handleSidePaneActiveModuleId: sidePane.handleActiveModuleId,\n setDialogState: dialog.setDialogState,\n featureLookup,\n });\n\n const handleServiceSelect = useCallback(\n (serviceId: string) => {\n const service = modules.find((s) => s.id === serviceId);\n if (!service) return;\n if (isSidePaneModule(service)) {\n selectSidePaneModule(\n service,\n sidePane.sidePaneState,\n sidePane.setSidePaneState,\n navigate,\n );\n } else {\n selectNavModule(service, navigate, setSubNavCollapsed);\n }\n setSelectedServiceId(serviceId);\n },\n [\n modules,\n sidePane.sidePaneState,\n sidePane.setSidePaneState,\n navigate,\n setSelectedServiceId,\n ],\n );\n\n const sidePaneContextValue = useMemo(\n () => ({\n pane: sidePane.sidePaneState,\n open: sidePane.handleOpen,\n close: sidePane.handleClose,\n toggle: (opts: SidePaneOptions) => {\n if (sidePane.sidePaneState?.moduleId === opts.moduleId) {\n sidePane.setSidePaneState(null);\n } else {\n sidePane.setSidePaneState(opts);\n }\n },\n }),\n [sidePane],\n );\n\n return (\n <ShellModulesContext.Provider value={registry}>\n <SidePaneContext.Provider value={sidePaneContextValue}>\n <TooltipProvider>\n <div className=\"flex h-screen w-screen flex-col overflow-hidden\">\n {/* TopBar */}\n <TopBar>{config.topBar}</TopBar>\n\n {/* CommandMenu (Cmd+K) */}\n <CommandMenu\n open={commandMenuOpen}\n onOpenChange={setCommandMenuOpen}\n />\n\n {/* Body: IconRail + SubNavPanel + SidePane + Content Area */}\n <div className=\"flex flex-1 overflow-hidden\">\n <IconRail\n activeServiceId={activeService?.id ?? null}\n activeSidePaneModuleId={\n sidePane.sidePaneState?.moduleId ?? null\n }\n onServiceSelect={handleServiceSelect}\n />\n\n {activeService && activeService.navigation.length > 0 && (\n <SubNavPanel\n service={activeService}\n collapsed={subNavCollapsed}\n onToggleCollapse={() => setSubNavCollapsed((c) => !c)}\n />\n )}\n\n {/* Side Pane — inline, shrinks the content area */}\n <SidePane />\n\n {/* Content Area */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <ScrollArea className=\"h-full\">\n <main className=\"p-6\">\n <Outlet />\n </main>\n </ScrollArea>\n </div>\n </div>\n\n {/* Confirm Dialog */}\n {dialog.dialogState && (\n <ConfirmDialog\n open={true}\n title={dialog.dialogState.opts.title}\n description={dialog.dialogState.opts.description}\n confirmLabel={dialog.dialogState.opts.confirmLabel}\n cancelLabel={dialog.dialogState.opts.cancelLabel}\n variant={dialog.dialogState.opts.variant}\n onConfirm={dialog.handleConfirm}\n onCancel={dialog.handleCancel}\n />\n )}\n\n {/* Fullscreen overlay -- covers all shell chrome */}\n {fullscreenState && (\n <div className=\"fixed inset-0 z-50 bg-background overflow-auto\">\n {fullscreenState.content}\n </div>\n )}\n\n {/* Slide-over Panel */}\n <SlideOverPanel\n panelState={panel.panelState}\n panelWidth={panel.panelWidth}\n panelOffsetTop={panel.panelOffsetTop}\n panelOnCloseRef={panel.panelOnCloseRef}\n onClose={panel.handleClose}\n />\n </div>\n </TooltipProvider>\n </SidePaneContext.Provider>\n </ShellModulesContext.Provider>\n );\n}\n\n// -- Sub-components extracted to reduce AppShell complexity --\n\ninterface SlideOverPanelProps {\n panelState: PanelOptions | null;\n panelWidth: string;\n panelOffsetTop: string;\n panelOnCloseRef: React.MutableRefObject<(() => void) | undefined>;\n onClose: () => void;\n}\n\nfunction SlideOverPanel({\n panelState,\n panelWidth,\n panelOffsetTop,\n panelOnCloseRef,\n onClose,\n}: SlideOverPanelProps) {\n return (\n <Sheet\n open={panelState !== null}\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <SheetContent\n side=\"right\"\n className={`${panelWidth} max-w-[90vw]`}\n offsetTop={panelOffsetTop}\n onCloseAutoFocus={(e) => {\n if (panelOnCloseRef.current) {\n e.preventDefault();\n panelOnCloseRef.current();\n }\n }}\n {...(!panelState?.description && {\n \"aria-describedby\": undefined,\n })}\n >\n {panelState && <PanelContent state={panelState} />}\n </SheetContent>\n </Sheet>\n );\n}\n\nfunction PanelContent({ state }: { state: PanelOptions }) {\n return (\n <>\n {state.title ? (\n <SheetHeader>\n <SheetTitle>{state.title}</SheetTitle>\n {state.description && (\n <SheetDescription>{state.description}</SheetDescription>\n )}\n </SheetHeader>\n ) : (\n <SheetTitle className=\"sr-only\">Panel</SheetTitle>\n )}\n <SheetBody>{state.content}</SheetBody>\n </>\n );\n}\n","/**\n * Confirm Dialog\n *\n * Reusable confirmation dialog for actions that require user confirmation.\n * Used for discard changes, delete actions, etc.\n *\n * Wired into the shell via `initDialog` in `shell/api.ts` so modules can\n * trigger it imperatively with `dialog.confirm(opts)`.\n *\n * @module components/Common/ConfirmDialog\n */\n\nimport React from \"react\";\nimport { AlertTriangle } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@petrarca/sonnet-ui\";\n\nexport interface ConfirmDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n\n /** Dialog title */\n title: string;\n\n /** Description shown below the title */\n description?: string;\n\n /** Confirm button label (default: \"Confirm\") */\n confirmLabel?: string;\n\n /** Cancel button label (default: \"Cancel\") */\n cancelLabel?: string;\n\n /**\n * Controls confirm button appearance.\n * \"destructive\" for irreversible actions (delete, revoke, etc.)\n */\n variant?: \"default\" | \"destructive\";\n\n /** Callback when user confirms */\n onConfirm: () => void;\n\n /** Callback when user cancels or closes dialog */\n onCancel: () => void;\n}\n\n/**\n * ConfirmDialog Component\n *\n * A reusable confirmation dialog backed by shadcn Dialog primitives.\n */\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n variant = \"default\",\n onConfirm,\n onCancel,\n}: ConfirmDialogProps): React.ReactElement {\n return (\n <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n {description && <DialogDescription>{description}</DialogDescription>}\n </DialogHeader>\n\n {variant === \"destructive\" && (\n <div className=\"flex gap-2 items-start\">\n <AlertTriangle\n size={20}\n className=\"text-destructive flex-shrink-0 mt-0.5\"\n />\n <p className=\"flex-1 text-sm text-muted-foreground\">\n This action cannot be undone.\n </p>\n </div>\n )}\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant={variant} onClick={onConfirm}>\n {confirmLabel}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","import type { ReactNode } from \"react\";\n\n/**\n * TopBar -- persistent header across the entire application.\n *\n * A pure layout container. The shell provides the `<header>` element with\n * its sizing, background, and border. The consumer owns all content via\n * the `children` prop (composed through `ShellConfig.topBar`).\n */\ninterface TopBarProps {\n children?: ReactNode;\n}\n\nexport default function TopBar({ children }: TopBarProps) {\n return (\n <header className=\"h-12 shrink-0 border-b border-blue-200 flex items-center justify-between px-3 bg-blue-100/70 z-20\">\n {children}\n </header>\n );\n}\n","import { cn } from \"@petrarca/sonnet-core\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { Separator } from \"@petrarca/sonnet-ui\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@petrarca/sonnet-ui\";\nimport { useShellModules } from \"./shellModules\";\nimport type { ServiceModule } from \"./types\";\n\ninterface IconRailProps {\n activeServiceId: string | null;\n /** moduleId of the module whose side pane is currently open, or null. */\n activeSidePaneModuleId: string | null;\n onServiceSelect: (serviceId: string) => void;\n}\n\n/**\n * IconRail -- narrow vertical bar with service domain icons.\n *\n * Main services scroll in the center, settings/playground pinned at bottom.\n * Tooltips show service labels on hover.\n */\nexport default function IconRail({\n activeServiceId,\n activeSidePaneModuleId,\n onServiceSelect,\n}: IconRailProps) {\n const { mainModules, bottomModules } = useShellModules();\n\n const isActive = (id: string) =>\n activeServiceId === id || activeSidePaneModuleId === id;\n\n return (\n <nav className=\"w-12 shrink-0 border-r bg-muted/30 flex flex-col items-center py-2 gap-1\">\n {/* Main service icons */}\n <div className=\"flex-1 flex flex-col items-center gap-1 overflow-y-auto\">\n {mainModules.map((service) => (\n <RailIcon\n key={service.id}\n service={service}\n isActive={isActive(service.id)}\n onClick={() => onServiceSelect(service.id)}\n />\n ))}\n </div>\n\n {/* Bottom-pinned icons */}\n <Separator className=\"w-6 my-1\" />\n <div className=\"flex flex-col items-center gap-1\">\n {bottomModules.map((service) => (\n <RailIcon\n key={service.id}\n service={service}\n isActive={isActive(service.id)}\n onClick={() => onServiceSelect(service.id)}\n />\n ))}\n </div>\n </nav>\n );\n}\n\n// Single rail icon button\n\ninterface RailIconProps {\n service: ServiceModule;\n isActive: boolean;\n onClick: () => void;\n}\n\nfunction RailIcon({ service, isActive, onClick }: RailIconProps) {\n const Icon = service.icon;\n\n return (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"compact\"\n onClick={onClick}\n className={cn(\n \"h-9 w-9 rounded-lg transition-colors\",\n isActive\n ? \"bg-accent text-accent-foreground\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon className=\"h-[18px] w-[18px]\" />\n <span className=\"sr-only\">{service.label}</span>\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"right\" sideOffset={8}>\n {service.label}\n </TooltipContent>\n </Tooltip>\n );\n}\n","/**\n * Shell modules context.\n *\n * Provides the module registry to shell components (IconRail,\n * CommandMenu, hooks) without requiring them to import from the\n * app-specific modules/registry.ts. AppShell injects the registry;\n * children consume it via useShellModules().\n */\nimport { createContext, useContext } from \"react\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\n\nexport const ShellModulesContext = createContext<ModuleRegistry | null>(null);\n\n/**\n * Read the module registry from context.\n * Must be called within an AppShell (which provides the context).\n */\nexport function useShellModules(): ModuleRegistry {\n const registry = useContext(ShellModulesContext);\n if (!registry) {\n throw new Error(\"useShellModules must be used within an AppShell\");\n }\n return registry;\n}\n","import { useMemo } from \"react\";\nimport { useLocation, Link } from \"react-router-dom\";\nimport { ChevronsLeft, ChevronsRight } from \"lucide-react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { ScrollArea } from \"@petrarca/sonnet-ui\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@petrarca/sonnet-ui\";\nimport type { ServiceModule, NavGroup } from \"./types\";\nimport { useExtensionPoint } from \"./hooks\";\n\ninterface SubNavPanelProps {\n service: ServiceModule;\n collapsed: boolean;\n onToggleCollapse: () => void;\n}\n\n/**\n * Merge contributed nav links from other modules.\n *\n * Contributions targeting \"<moduleId>.nav\" are appended as an additional\n * nav group after the module's own groups, sorted by contribution order.\n * The contribution mechanism is pre-sorted by createModuleRegistry.\n */\nfunction useMergedNavigation(service: ServiceModule): NavGroup[] {\n const contributions = useExtensionPoint(`${service.id}.nav`);\n return useMemo(() => {\n if (contributions.length === 0) return service.navigation;\n\n const contributed: NavGroup = {\n id: `${service.id}-contributed`,\n links: contributions.map((c) => ({\n id: c.extensionPoint + \".\" + c.label.toLowerCase().replace(/\\s+/g, \"-\"),\n label: c.label,\n path: c.path,\n })),\n };\n\n return [...service.navigation, contributed];\n }, [service, contributions]);\n}\n\n/**\n * SubNavPanel -- contextual secondary navigation for the selected service domain.\n *\n * Shows the service label at top, then grouped nav links. If other modules\n * contribute links via the \"<moduleId>.nav\" extension point, they are\n * appended as an additional group.\n */\nexport default function SubNavPanel({\n service,\n collapsed,\n onToggleCollapse,\n}: SubNavPanelProps) {\n const { pathname } = useLocation();\n const mergedNavigation = useMergedNavigation(service);\n\n if (collapsed) {\n return (\n <aside className=\"shrink-0 border-r bg-background flex flex-col items-center pt-2.5\">\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n className=\"text-muted-foreground/50 hover:text-muted-foreground transition-colors p-1\"\n onClick={onToggleCollapse}\n >\n <ChevronsRight className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">Expand navigation</TooltipContent>\n </Tooltip>\n </aside>\n );\n }\n\n return (\n <aside className=\"w-52 shrink-0 border-r bg-background flex flex-col\">\n {/* Service header with collapse toggle */}\n <div className=\"h-10 shrink-0 flex items-center justify-between px-4\">\n <span className=\"heading-compact text-muted-foreground uppercase tracking-wider\">\n {service.label}\n </span>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n className=\"text-muted-foreground/40 hover:text-muted-foreground transition-colors p-0.5\"\n onClick={onToggleCollapse}\n >\n <ChevronsLeft className=\"h-3.5 w-3.5\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">Collapse navigation</TooltipContent>\n </Tooltip>\n </div>\n\n {/* Nav groups */}\n <ScrollArea className=\"flex-1\">\n <nav className=\"px-2 pb-4\">\n {mergedNavigation.map((group, gi) => (\n <div key={group.id} className={cn(gi > 0 && \"mt-4\")}>\n {group.heading && (\n <h6 className=\"heading-meta px-2 mb-1\">{group.heading}</h6>\n )}\n <ul className=\"space-y-0.5\">\n {group.links.map((link) => {\n const isActive = pathname === link.path;\n return (\n <li key={link.path}>\n <Link\n to={link.path}\n className={cn(\n \"flex items-center rounded-md px-2 py-1.5 text-sm transition-colors\",\n isActive\n ? \"bg-accent text-accent-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground hover:bg-accent/50\",\n )}\n >\n {link.label}\n </Link>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </nav>\n </ScrollArea>\n </aside>\n );\n}\n","/**\n * Shell hooks -- reactive APIs for modules.\n *\n * Import path: @/shell/hooks\n */\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { events } from \"./api\";\nimport { useShellModules } from \"./shellModules\";\nimport type { Contribution, ServiceModule, ShellEventMap } from \"./types\";\n\n/**\n * Get sorted contributions for a named extension point.\n *\n * Returns all contributions registered by any module for the given\n * extension point, sorted by `order` (ascending).\n *\n * @example\n * ```tsx\n * const tabs = useExtensionPoint(\"organization-detail\");\n * ```\n */\nexport function useExtensionPoint(name: string): Contribution[] {\n const { getExtensionPoint } = useShellModules();\n return useMemo(() => getExtensionPoint(name), [getExtensionPoint, name]);\n}\n\n/**\n * Get the list of visible service modules (excludes hidden and bottom-pinned).\n *\n * Useful for building overviews like the home page.\n */\nexport function useMainModules(): ServiceModule[] {\n const { mainModules } = useShellModules();\n return mainModules;\n}\n\n/**\n * Subscribe to a shell event, auto-unsubscribing on unmount.\n *\n * The handler reference is stabilized via useRef so that the\n * subscription is not torn down and recreated on every render.\n *\n * @example\n * ```tsx\n * useShellEvent(\"schema-definition:changed\", (payload) => {\n * queryClient.invalidateQueries({ queryKey: [\"my-key\"] });\n * });\n * ```\n */\nexport function useShellEvent<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n): void {\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n return events.on(key, (payload) => handlerRef.current(payload));\n }, [key]);\n}\n","/**\n * Shell API -- imperative domain capabilities for modules.\n *\n * Import path: @/shell/api\n *\n * Grouped by domain: notification, dialog, navigation, panel, events.\n */\nimport type { ReactNode } from \"react\";\nimport { toast } from \"sonner\";\nimport { warnLog } from \"@petrarca/sonnet-core\";\nimport type { ShellEventMap } from \"./types\";\nimport * as eventBus from \"./eventBus\";\n\n// notification\n\ninterface PromiseOptions<T> {\n loading: string;\n success: string | ((data: T) => string);\n error: string | ((err: unknown) => string);\n}\n\nexport const notification = {\n success(message: string) {\n toast.success(message);\n },\n error(message: string) {\n toast.error(message);\n },\n warning(message: string) {\n toast.warning(message);\n },\n info(message: string) {\n toast.info(message);\n },\n promise<T>(promise: Promise<T>, opts: PromiseOptions<T>) {\n return toast.promise(promise, opts);\n },\n dismiss(id?: string | number) {\n toast.dismiss(id);\n },\n};\n\n// dialog\n\n// The shell mounts a single ConfirmDialog instance in AppShell and injects\n// its open function here via initDialog(). Modules call dialog.confirm()\n// without knowing where the implementation lives -- federation-safe, same\n// pattern as navigation and panel.\n\nexport interface ConfirmOptions {\n title: string;\n description?: string;\n confirmLabel?: string;\n cancelLabel?: string;\n variant?: \"default\" | \"destructive\";\n}\n\ntype DialogConfirmFn = (opts: ConfirmOptions) => Promise<boolean>;\n\nlet _dialogConfirm: DialogConfirmFn | null = null;\n\n/**\n * Called by the shell during initialization to inject the confirm dialog.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initDialog(confirmFn: DialogConfirmFn) {\n _dialogConfirm = confirmFn;\n}\n\nexport const dialog = {\n /**\n * Show a confirmation dialog. Returns true if confirmed, false if cancelled.\n */\n confirm(opts: ConfirmOptions): Promise<boolean> {\n if (_dialogConfirm) {\n return _dialogConfirm(opts);\n }\n warnLog(\"dialog.confirm: shell not initialized yet.\");\n return Promise.resolve(false);\n },\n};\n\n// navigation\n\n// The shell injects the navigate function and command menu opener at startup\n// via initNavigation(). Modules call navigation.goTo() etc. without knowing\n// where the implementation comes from -- works with both monorepo and\n// Module Federation (host provides the injector, remotes consume the API).\n\ntype NavigateFn = (path: string) => void;\ntype CommandMenuFn = () => void;\ntype FeatureLookupFn = (featureId: string) => string | null;\n\nlet _navigate: NavigateFn | null = null;\nlet _openCommandMenu: CommandMenuFn | null = null;\nlet _lookupFeature: FeatureLookupFn | null = null;\n\n/**\n * Called by the shell during initialization to inject router + command menu.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initNavigation(\n navigateFn: NavigateFn,\n openCommandMenuFn: CommandMenuFn,\n) {\n _navigate = navigateFn;\n _openCommandMenu = openCommandMenuFn;\n}\n\n/**\n * Called by the shell during initialization to inject the feature lookup.\n * Resolves a stable feature id (e.g. \"auditing.audit-events\") to a path.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initFeatureNav(lookupFn: FeatureLookupFn) {\n _lookupFeature = lookupFn;\n}\n\nexport const navigation = {\n /** Navigate to a path within the shell. */\n goTo(path: string) {\n if (_navigate) {\n _navigate(path);\n } else {\n warnLog(\"navigation.goTo: shell not initialized yet.\");\n }\n },\n /**\n * Navigate to a feature by its stable identifier (e.g. \"auditing.audit-events\").\n * The path is resolved from the module registry at call time, so it stays\n * correct even if routes are reorganised.\n */\n goToFeature(featureId: string) {\n if (!_navigate || !_lookupFeature) {\n warnLog(\"navigation.goToFeature: shell not initialized yet.\");\n return;\n }\n const path = _lookupFeature(featureId);\n if (path) {\n _navigate(path);\n } else {\n warnLog(\"navigation.goToFeature: unknown feature id '{}'.\", featureId);\n }\n },\n /** Open the Cmd+K command menu. */\n openCommandMenu() {\n if (_openCommandMenu) {\n _openCommandMenu();\n } else {\n warnLog(\"navigation.openCommandMenu: shell not initialized yet.\");\n }\n },\n /** Go back in browser history. */\n back() {\n window.history.back();\n },\n};\n\n// panel\n\n// The shell owns the slide-over panel (Sheet). Modules request it via\n// panel.open() with content to render. The shell renders that content inside\n// the panel. Same injection pattern as navigation -- federation-safe.\n\nexport interface PanelOptions {\n /**\n * Title shown in the panel header. When omitted the header is visually\n * hidden (the content owns its own header) but a screen-reader-only\n * title is still rendered for accessibility.\n */\n title?: string;\n /** React content to render inside the panel */\n content: ReactNode;\n /** Optional description below the title */\n description?: string;\n /**\n * Panel width token.\n *\n * - \"narrow\" -- 480px -- quick actions, simple single-field forms\n * - \"default\" -- 640px -- standard entity forms (default when omitted)\n * - \"wide\" -- 800px -- forms with embedded tables or complex layouts\n * - \"half\" -- 50vw -- split-screen inspection, side-by-side context\n * - \"full\" -- 100vw -- immersive editing, full-width canvases\n *\n * Legacy values \"lg\", \"1/3\", \"1/2\" are kept for backward compatibility\n * and map to \"default\" (640px), 33vw, and \"half\" (50vw) respectively.\n */\n width?:\n | \"narrow\"\n | \"default\"\n | \"wide\"\n | \"half\"\n | \"full\"\n | \"lg\"\n | \"1/3\"\n | \"1/2\";\n /**\n * Vertical coverage of the panel.\n * - \"below-header\" (default): panel starts below the TopBar, keeping it visible.\n * - \"full\": panel covers the full viewport height including the TopBar.\n */\n coverage?: \"below-header\" | \"full\";\n /** Called after the panel finishes closing. Use to restore focus to the triggering element. */\n onClose?: () => void;\n}\n\ntype PanelOpenFn = (opts: PanelOptions) => void;\ntype PanelCloseFn = () => void;\n\nlet _panelOpen: PanelOpenFn | null = null;\nlet _panelClose: PanelCloseFn | null = null;\n\n/**\n * Called by the shell during initialization to inject panel controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initPanel(openFn: PanelOpenFn, closeFn: PanelCloseFn) {\n _panelOpen = openFn;\n _panelClose = closeFn;\n}\n\nexport const panel = {\n /** Open the shell panel with content rendered inside. */\n open(opts: PanelOptions) {\n if (_panelOpen) {\n _panelOpen(opts);\n } else {\n warnLog(\"panel.open: shell not initialized yet.\");\n }\n },\n /** Close the panel. */\n close() {\n if (_panelClose) {\n _panelClose();\n } else {\n warnLog(\"panel.close: shell not initialized yet.\");\n }\n },\n};\n\n// sidePane\n\n// The shell owns the side pane (SidePane component in AppShell). Modules\n// request it via sidePane.open() with content and sizing options. The pane\n// sits inline beside the content area -- the main view shrinks rather than\n// being overlaid. Same injection pattern as panel -- federation-safe.\n\nimport type { SidePaneOptions } from \"./sidePaneState\";\nexport type { SidePaneOptions };\n\ntype SidePaneOpenFn = (opts: SidePaneOptions) => void;\ntype SidePaneCloseFn = () => void;\ntype SidePaneIsOpenFn = () => boolean;\ntype SidePaneActiveModuleIdFn = () => string | null;\n\nlet _sidePaneOpen: SidePaneOpenFn | null = null;\nlet _sidePaneClose: SidePaneCloseFn | null = null;\nlet _sidePaneIsOpen: SidePaneIsOpenFn | null = null;\nlet _sidePaneActiveModuleId: SidePaneActiveModuleIdFn | null = null;\n\n/**\n * Called by the shell during initialization to inject side pane controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initSidePane(\n openFn: SidePaneOpenFn,\n closeFn: SidePaneCloseFn,\n isOpenFn: SidePaneIsOpenFn,\n activeModuleIdFn: SidePaneActiveModuleIdFn,\n) {\n _sidePaneOpen = openFn;\n _sidePaneClose = closeFn;\n _sidePaneIsOpen = isOpenFn;\n _sidePaneActiveModuleId = activeModuleIdFn;\n}\n\nexport const sidePane = {\n /** Open the side pane with the given content and sizing options. */\n open(opts: SidePaneOptions) {\n if (_sidePaneOpen) {\n _sidePaneOpen(opts);\n } else {\n warnLog(\"sidePane.open: shell not initialized yet.\");\n }\n },\n /** Close the side pane. */\n close() {\n if (_sidePaneClose) {\n _sidePaneClose();\n } else {\n warnLog(\"sidePane.close: shell not initialized yet.\");\n }\n },\n /**\n * Toggle the side pane.\n *\n * If the pane is closed, opens it with opts.\n * If the pane is open with the same moduleId, closes it.\n * If the pane is open with a different moduleId, replaces it with opts.\n */\n toggle(opts: SidePaneOptions) {\n if (\n !_sidePaneIsOpen ||\n !_sidePaneActiveModuleId ||\n !_sidePaneOpen ||\n !_sidePaneClose\n ) {\n warnLog(\"sidePane.toggle: shell not initialized yet.\");\n return;\n }\n if (_sidePaneIsOpen() && _sidePaneActiveModuleId() === opts.moduleId) {\n _sidePaneClose();\n } else {\n _sidePaneOpen(opts);\n }\n },\n /** Returns true when the pane is currently open. */\n isOpen(): boolean {\n return _sidePaneIsOpen ? _sidePaneIsOpen() : false;\n },\n /** Returns the moduleId of the currently open pane, or null. */\n activeModuleId(): string | null {\n return _sidePaneActiveModuleId ? _sidePaneActiveModuleId() : null;\n },\n};\n\n// fullscreen\n\n// The shell owns the fullscreen overlay. Modules request it via\n// fullscreen.enter(content) and exit via fullscreen.exit(). The shell renders\n// the content in a fixed inset-0 z-50 overlay, covering all shell chrome.\n// Same injection pattern as panel -- federation-safe.\n\nexport interface FullscreenOptions {\n /** React content to render inside the fullscreen overlay. */\n content: ReactNode;\n}\n\ntype FullscreenEnterFn = (opts: FullscreenOptions) => void;\ntype FullscreenExitFn = () => void;\n\nlet _fullscreenEnter: FullscreenEnterFn | null = null;\nlet _fullscreenExit: FullscreenExitFn | null = null;\n\n/**\n * Called by the shell during initialization to inject fullscreen controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initFullscreen(\n enterFn: FullscreenEnterFn,\n exitFn: FullscreenExitFn,\n) {\n _fullscreenEnter = enterFn;\n _fullscreenExit = exitFn;\n}\n\nexport const fullscreen = {\n /** Enter fullscreen mode, rendering content over all shell chrome. */\n enter(opts: FullscreenOptions) {\n if (_fullscreenEnter) {\n _fullscreenEnter(opts);\n } else {\n warnLog(\"fullscreen.enter: shell not initialized yet.\");\n }\n },\n /** Exit fullscreen mode, restoring the normal shell layout. */\n exit() {\n if (_fullscreenExit) {\n _fullscreenExit();\n } else {\n warnLog(\"fullscreen.exit: shell not initialized yet.\");\n }\n },\n};\n\n// events\n\ntype Unsubscribe = () => void;\n\nexport const events = {\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n ): Unsubscribe {\n return eventBus.on(key as string, handler as (payload: unknown) => void);\n },\n\n /** Subscribe, auto-unsubscribing after the first emission. */\n once<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n ): Unsubscribe {\n return eventBus.once(key as string, handler as (payload: unknown) => void);\n },\n\n /** Emit an event. Handlers run asynchronously (microtask). */\n emit<K extends keyof ShellEventMap>(key: K, payload: ShellEventMap[K]): void {\n eventBus.emit(key as string, payload);\n },\n};\n","/**\n * Shell event bus -- in-memory pub/sub for cross-module communication.\n *\n * The bus is untyped internally (operates on strings and unknown payloads).\n * Type safety is provided by the public API layer in shell/api.ts, which\n * constrains keys and payloads via the ShellEventMap interface.\n *\n * Mirrors the server-side EventBus pattern (fire-and-forget, error isolation)\n * adapted for the browser's single-threaded model.\n */\nimport { devLog, warnLog } from \"@petrarca/sonnet-core\";\n\ntype Handler = (payload: unknown) => void;\ntype Unsubscribe = () => void;\n\nconst listeners = new Map<string, Set<Handler>>();\n\n/** Subscribe to an event key. Returns an unsubscribe function. */\nexport function on(key: string, handler: Handler): Unsubscribe {\n let set = listeners.get(key);\n if (!set) {\n set = new Set();\n listeners.set(key, set);\n }\n set.add(handler);\n devLog(`[shell:event] on \"${key}\" (${set.size} handler(s))`);\n return () => {\n set!.delete(handler);\n if (set!.size === 0) listeners.delete(key);\n devLog(`[shell:event] off \"${key}\" (${set!.size} handler(s))`);\n };\n}\n\n/** Subscribe to an event key, auto-unsubscribing after the first emission. */\nexport function once(key: string, handler: Handler): Unsubscribe {\n const unsubscribe = on(key, (payload) => {\n unsubscribe();\n handler(payload);\n });\n return unsubscribe;\n}\n\n/** Emit an event. Handlers run in microtasks (non-blocking, error-isolated). */\nexport function emit(key: string, payload: unknown): void {\n const set = listeners.get(key);\n const count = set?.size ?? 0;\n devLog(`[shell:event] emit \"${key}\"`, payload, `(${count} handler(s))`);\n if (!set || count === 0) return;\n for (const handler of set) {\n Promise.resolve().then(() => {\n try {\n handler(payload);\n } catch (err) {\n warnLog(`[shell:event] handler error \"${key}\":`, err);\n }\n });\n }\n}\n","/**\n * SidePane -- persistent resizable inline panel.\n *\n * Sits between SubNavPanel and the main content area in the AppShell flex row.\n * When open it takes a fixed pixel width; the content area (flex-1) shrinks to\n * fill the remainder. The main view remains fully visible and interactive.\n *\n * Opened/closed imperatively via sidePane.open() / sidePane.close() / sidePane.toggle()\n * from api.ts. State is provided by SidePaneContext (owned by AppShell).\n */\nimport { X } from \"lucide-react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { useResizablePanel } from \"@petrarca/sonnet-core/hooks\";\nimport { useSidePaneState } from \"./sidePaneState\";\n\nconst DEFAULT_WIDTH = 320;\nconst DEFAULT_MIN = 240;\nconst DEFAULT_MAX = 800;\n\n/**\n * SidePane renders nothing when no pane is open.\n * When open it renders a bordered panel with a drag handle on its right edge.\n * Double-clicking the handle resets to defaultWidth.\n */\nexport default function SidePane() {\n const { pane, close } = useSidePaneState();\n\n const { panelWidth, handlePointerDown, handleDoubleClick, separatorProps } =\n useResizablePanel({\n defaultWidth: pane?.defaultWidth ?? DEFAULT_WIDTH,\n minWidth: pane?.minWidth ?? DEFAULT_MIN,\n maxWidth: pane?.maxWidth ?? DEFAULT_MAX,\n direction: \"right-edge\",\n });\n\n if (!pane) return null;\n\n return (\n <div\n className=\"relative flex shrink-0 flex-col border-r bg-background\"\n style={{ width: panelWidth }}\n >\n {/* Header */}\n <div className=\"flex h-10 shrink-0 items-center justify-end border-b px-2\">\n <button\n onClick={close}\n className={cn(\n \"flex h-6 w-6 items-center justify-center rounded text-muted-foreground\",\n \"hover:bg-accent hover:text-foreground transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n aria-label=\"Close side pane\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-auto\">{pane.content}</div>\n\n {/* Drag handle — right edge, full height */}\n <div\n {...separatorProps}\n onPointerDown={handlePointerDown}\n onDoubleClick={handleDoubleClick}\n className={cn(\n \"absolute right-0 top-0 h-full w-1 cursor-col-resize\",\n \"hover:bg-primary/40 active:bg-primary/60 transition-colors\",\n )}\n />\n </div>\n );\n}\n","/**\n * Side pane state types and context.\n *\n * The side pane is a persistent, resizable inline panel that sits between\n * the SubNavPanel and the main content area. It shrinks the content area\n * rather than overlaying it -- the main view stays visible and interactive.\n *\n * Modules open/close/toggle it via the imperative sidePane API in api.ts.\n * AppShell owns the state and injects it here via SidePaneContext so that\n * SidePane.tsx can read it without coupling to AppShell directly.\n */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\nexport interface SidePaneOptions {\n /** Content to render inside the side pane. */\n content: ReactNode;\n /**\n * Stable key identifying which module/tool owns this pane instance.\n * Used by IconRail to highlight the active pane module and by toggle()\n * to detect whether the same content is already open.\n */\n moduleId: string;\n /** Initial width in pixels. Defaults to 320. */\n defaultWidth?: number;\n /** Minimum draggable width in pixels. Defaults to 240. */\n minWidth?: number;\n /**\n * Maximum draggable width in pixels.\n * Omit or pass Infinity for unlimited (fills remaining space up to viewport).\n */\n maxWidth?: number;\n}\n\nexport interface SidePaneState {\n /** Currently open pane options, or null when closed. */\n pane: SidePaneOptions | null;\n open: (opts: SidePaneOptions) => void;\n close: () => void;\n toggle: (opts: SidePaneOptions) => void;\n}\n\nexport const SidePaneContext = createContext<SidePaneState | null>(null);\n\n/**\n * Read the side pane state from context.\n * Must be called within an AppShell (which provides the context).\n */\nexport function useSidePaneState(): SidePaneState {\n const ctx = useContext(SidePaneContext);\n if (!ctx) {\n throw new Error(\"useSidePaneState must be used within an AppShell\");\n }\n return ctx;\n}\n","import { useEffect, useCallback } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from \"@petrarca/sonnet-ui\";\nimport { useShellModules } from \"./shellModules\";\nimport type { ServiceModule } from \"./types\";\n\ninterface CommandMenuProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\n/**\n * CommandMenu -- Cmd+K command menu.\n *\n * Indexes all navigation items from the service domains so users can\n * quickly jump to any page by typing.\n */\nexport default function CommandMenu({ open, onOpenChange }: CommandMenuProps) {\n const navigate = useNavigate();\n const { mainModules, bottomModules } = useShellModules();\n\n // Global Cmd+K / Ctrl+K shortcut\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n onOpenChange(!open);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [open, onOpenChange]);\n\n const handleSelect = useCallback(\n (path: string) => {\n navigate(path);\n onOpenChange(false);\n },\n [navigate, onOpenChange],\n );\n\n const renderNavGroup = (service: ServiceModule) => {\n if (service.navigation.length === 0) return null;\n const Icon = service.icon;\n return (\n <CommandGroup key={`nav-${service.id}`} heading={service.label}>\n {service.navigation.flatMap((group) =>\n group.links.map((link) => (\n <CommandItem\n key={link.path}\n value={`${service.label} ${group.heading ?? \"\"} ${link.label}`}\n onSelect={() => handleSelect(link.path)}\n >\n <Icon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n <span>{link.label}</span>\n {group.heading && (\n <span className=\"ml-auto text-xs text-muted-foreground\">\n {group.heading}\n </span>\n )}\n </CommandItem>\n )),\n )}\n </CommandGroup>\n );\n };\n\n const renderCommandGroup = (service: ServiceModule) => {\n if (!service.commands?.length) return null;\n const Icon = service.icon;\n return (\n <CommandGroup\n key={`cmd-${service.id}`}\n heading={service.commands[0].group ?? service.label}\n >\n {service.commands.map((cmd) => {\n const CmdIcon = cmd.icon ?? Icon;\n return (\n <CommandItem\n key={cmd.id}\n value={`${service.label} ${cmd.label}`}\n onSelect={() => {\n cmd.action();\n onOpenChange(false);\n }}\n >\n <CmdIcon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n <span>{cmd.label}</span>\n </CommandItem>\n );\n })}\n </CommandGroup>\n );\n };\n\n const allModules = [...mainModules, ...bottomModules];\n\n return (\n <CommandDialog open={open} onOpenChange={onOpenChange}>\n <CommandInput placeholder=\"Search pages, services, actions...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n\n {mainModules.map(renderNavGroup)}\n\n <CommandSeparator />\n\n {bottomModules.map(renderNavGroup)}\n\n {allModules.some((m) => m.commands?.length) && (\n <>\n <CommandSeparator />\n {allModules.map(renderCommandGroup)}\n </>\n )}\n </CommandList>\n </CommandDialog>\n );\n}\n","/**\n * Shell configuration types and context.\n *\n * ShellConfig is intentionally minimal. The shell provides layout chrome\n * (TopBar header, IconRail, SubNavPanel, CommandMenu) but does not own\n * any app-specific content. The consumer composes the TopBar content --\n * logo, environment selector, search trigger, user menu -- via the\n * `topBar` ReactNode slot using building blocks exported from the shell.\n */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\n// Top-level shell configuration.\n\nexport interface ShellConfig {\n /**\n * Content rendered inside the TopBar header. The shell provides the\n * `<header>` element with its sizing and border; the consumer owns\n * everything inside it.\n *\n * Compose this using building blocks like `SearchTrigger` and\n * `UserMenu` (exported from the shell) alongside any app-specific\n * components.\n */\n topBar?: ReactNode;\n}\n\n// Context\n\nexport const ShellConfigContext = createContext<ShellConfig | null>(null);\n\n/**\n * Read the shell configuration from context.\n * Must be called within a ShellConfigProvider.\n */\nexport function useShellConfig(): ShellConfig {\n const config = useContext(ShellConfigContext);\n if (!config) {\n throw new Error(\"useShellConfig must be used within a ShellConfigProvider\");\n }\n return config;\n}\n","import { Toaster } from \"sonner\";\nimport AppShell from \"./AppShell\";\nimport { ShellConfigProvider } from \"./ShellConfigProvider\";\nimport type { ShellConfig } from \"./shellConfig\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\n\n/**\n * RootLayout -- top-level layout that wraps the entire authenticated app.\n * Renders the Toaster for notifications and the AppShell (icon rail + sub-nav + content).\n */\n\ninterface RootLayoutProps {\n config: ShellConfig;\n registry: ModuleRegistry;\n}\n\nexport default function RootLayout({ config, registry }: RootLayoutProps) {\n return (\n <ShellConfigProvider config={config}>\n <Toaster position=\"top-right\" richColors closeButton />\n <AppShell registry={registry} />\n </ShellConfigProvider>\n );\n}\n","/**\n * ShellConfigProvider -- wraps the shell with configuration context.\n */\nimport type { ReactNode } from \"react\";\nimport { ShellConfigContext, type ShellConfig } from \"./shellConfig\";\n\ninterface ShellConfigProviderProps {\n config: ShellConfig;\n children: ReactNode;\n}\n\nexport function ShellConfigProvider({\n config,\n children,\n}: ShellConfigProviderProps) {\n return (\n <ShellConfigContext.Provider value={config}>\n {children}\n </ShellConfigContext.Provider>\n );\n}\n","import { useLocation } from \"react-router-dom\";\n\n/**\n * PlaceholderPage — generic wireframe page for routes not yet implemented.\n * Shows the current path and a placeholder content area.\n */\nexport default function PlaceholderPage() {\n const { pathname } = useLocation();\n\n return (\n <div>\n <h1 className=\"mb-1\">{pathToTitle(pathname)}</h1>\n <p className=\"text-sm text-muted-foreground mb-6\">{pathname}</p>\n\n {/* Wireframe content placeholder */}\n <div className=\"rounded-lg border-2 border-dashed border-border p-12 flex items-center justify-center\">\n <span className=\"text-sm text-muted-foreground\">\n Content area — not yet implemented\n </span>\n </div>\n </div>\n );\n}\n\n/** Convert a path like \"/healthcare/patients\" to \"Patients\" */\nfunction pathToTitle(path: string): string {\n const last = path.split(\"/\").filter(Boolean).pop();\n if (!last) return \"Dashboard\";\n return last\n .split(\"-\")\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(\" \");\n}\n","import { Search } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { navigation } from \"./api\";\n\n/**\n * SearchTrigger -- Cmd+K search button for the TopBar.\n *\n * Opens the shell CommandMenu via the imperative navigation API.\n * Generic building block, no app-specific dependencies.\n */\nexport function SearchTrigger() {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 gap-2 text-xs text-muted-foreground w-52 justify-start\"\n onClick={() => navigation.openCommandMenu()}\n >\n <Search className=\"h-3.5 w-3.5\" />\n <span>Search...</span>\n <kbd className=\"ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground\">\n <span className=\"text-xs\">&#x2318;</span>K\n </kbd>\n </Button>\n );\n}\n","import { LogOut, User } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { Avatar, AvatarFallback } from \"@petrarca/sonnet-ui\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@petrarca/sonnet-ui\";\n\n/**\n * UserMenu -- avatar dropdown for the TopBar.\n *\n * Shows the user's initials in an avatar. The dropdown displays name,\n * email, a profile link, and a sign-out action. Generic building block,\n * no app-specific dependencies.\n */\n\nexport interface UserMenuUser {\n name: string;\n email: string;\n initials: string;\n}\n\ninterface UserMenuProps {\n user: UserMenuUser;\n onSignOut?: () => void;\n}\n\nexport function UserMenu({ user, onSignOut }: UserMenuProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"compact\" className=\"rounded-full\">\n <Avatar className=\"h-7 w-7\">\n <AvatarFallback className=\"text-xs\">{user.initials}</AvatarFallback>\n </Avatar>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-48\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">{user.name}</p>\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <User className=\"mr-2 h-4 w-4\" />\n Profile\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={onSignOut}>\n <LogOut className=\"mr-2 h-4 w-4\" />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","/**\n * Module registry factory.\n *\n * Accepts a list of ServiceModules and returns derived data structures\n * consumed by the shell (icon rail groups, flattened routes, extension\n * point lookup). This is the reusable core; the app-specific module\n * list lives in the consumer (e.g. modules/registry.ts).\n */\nimport type { RouteObject } from \"react-router-dom\";\nimport type { ServiceModule, Contribution } from \"./types\";\n\nexport interface ModuleRegistry {\n /** All modules in registration order. */\n modules: ServiceModule[];\n\n /** Visible modules shown in the main area of the icon rail. */\n mainModules: ServiceModule[];\n\n /** Modules pinned to the bottom of the icon rail. */\n bottomModules: ServiceModule[];\n\n /** All routes from all modules, flattened. */\n allRoutes: RouteObject[];\n\n /** Get contributions for a named extension point, sorted by order. */\n getExtensionPoint: (name: string) => Contribution[];\n}\n\nexport function createModuleRegistry(modules: ServiceModule[]): ModuleRegistry {\n const visible = modules.filter((m) => !m.hidden);\n const mainModules = visible.filter((m) => !m.pinBottom);\n const bottomModules = visible.filter((m) => m.pinBottom);\n const allRoutes = modules.flatMap((m) => m.routes);\n\n // Pre-collect all contributions for O(n) lookup per call.\n const contributionsByPoint = new Map<string, Contribution[]>();\n for (const m of modules) {\n for (const c of m.contributions ?? []) {\n const list = contributionsByPoint.get(c.extensionPoint) ?? [];\n list.push(c);\n contributionsByPoint.set(c.extensionPoint, list);\n }\n }\n // Sort each group once.\n for (const list of contributionsByPoint.values()) {\n list.sort((a, b) => a.order - b.order);\n }\n\n function getExtensionPoint(name: string): Contribution[] {\n return contributionsByPoint.get(name) ?? [];\n }\n\n return { modules, mainModules, bottomModules, allRoutes, getExtensionPoint };\n}\n","/**\n * OverviewCard and FeatureLink -- building blocks for module overview pages.\n *\n * OverviewCard presents a feature with icon, title, description, and optional\n * navigation via a stable feature id.\n *\n * FeatureLink is an inline text link for use in descriptions or prose,\n * also navigating via stable feature id.\n *\n * Part of the shell -- extracts to @petrarca/sonnet-shell.\n */\n\nimport React from \"react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { navigation } from \"./api\";\n\ninterface OverviewCardProps {\n /** Lucide icon component. */\n icon: React.ComponentType<{ className?: string }>;\n /** Card title. */\n title: string;\n /** Description -- plain string or React nodes (e.g. with FeatureLink). */\n description: React.ReactNode;\n /**\n * Stable feature identifier (e.g. \"auditing.audit-events\").\n * Resolved to a path via the shell registry at click time.\n * Omit for non-navigable cards.\n */\n feature?: string;\n /** Additional CSS class names. */\n className?: string;\n}\n\n/**\n * FeatureLink -- inline text link navigating to a feature by stable id.\n *\n * Use inside OverviewCard descriptions or overview page prose to link\n * to a feature without hardcoding paths.\n *\n * @example\n * <FeatureLink feature=\"auditing.audit-log\">Audit Log</FeatureLink>\n */\nexport function FeatureLink({\n feature,\n children,\n}: {\n feature: string;\n children: React.ReactNode;\n}): React.ReactElement {\n return (\n <span\n role=\"link\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n navigation.goToFeature(feature);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n e.stopPropagation();\n navigation.goToFeature(feature);\n }\n }}\n className=\"underline underline-offset-2 hover:text-foreground transition-colors cursor-pointer\"\n >\n {children}\n </span>\n );\n}\n\n/** Feature card for module overview pages. Navigates via the shell API. */\nexport function OverviewCard({\n icon: Icon,\n title,\n description,\n feature,\n className,\n}: OverviewCardProps): React.ReactElement {\n return (\n <button\n onClick={feature ? () => navigation.goToFeature(feature) : undefined}\n disabled={!feature}\n className={cn(\n \"rounded-lg border bg-card p-5 text-left flex flex-col gap-3\",\n \"disabled:cursor-default\",\n feature && \"hover:bg-accent/50 transition-colors\",\n className,\n )}\n >\n <div className=\"h-9 w-9 rounded-md bg-blue-50 flex items-center justify-center shrink-0\">\n <Icon className=\"h-5 w-5 text-blue-600\" />\n </div>\n <div>\n <p className=\"text-sm font-medium mb-1\">{title}</p>\n <p className=\"text-xs text-muted-foreground leading-relaxed\">\n {description}\n </p>\n </div>\n </button>\n );\n}\n"],"mappings":";AAAA,SAAS,UAAU,aAAAA,YAAW,eAAAC,cAAa,UAAAC,SAAQ,WAAAC,gBAAe;AAClE,SAAS,QAAQ,eAAAC,cAAa,eAAAC,oBAAmB;AACjD,SAAS,uBAAuB;AAChC,SAAS,cAAAC,mBAAkB;AAC3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACEP,SAAS,qBAAqB;AAC9B,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiDC,SACE,KADF;AAbD,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf,cAAc;AAAA,EACd,UAAU;AAAA,EACV;AAAA,EACA;AACF,GAA2C;AACzC,SACE,oBAAC,UAAO,MAAY,cAAc,CAAC,WAAW,CAAC,UAAU,SAAS,GAChE,+BAAC,iBACC;AAAA,yBAAC,gBACC;AAAA,0BAAC,eAAa,iBAAM;AAAA,MACnB,eAAe,oBAAC,qBAAmB,uBAAY;AAAA,OAClD;AAAA,IAEC,YAAY,iBACX,qBAAC,SAAI,WAAU,0BACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAM;AAAA,UACN,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,oBAAC,OAAE,WAAU,wCAAuC,2CAEpD;AAAA,OACF;AAAA,IAGF,qBAAC,gBACC;AAAA,0BAAC,UAAO,SAAQ,WAAU,SAAS,UAChC,uBACH;AAAA,MACA,oBAAC,UAAO,SAAkB,SAAS,WAChC,wBACH;AAAA,OACF;AAAA,KACF,GACF;AAEJ;;;ACpFI,gBAAAC,YAAA;AAFW,SAAR,OAAwB,EAAE,SAAS,GAAgB;AACxD,SACE,gBAAAA,KAAC,YAAO,WAAU,qGACf,UACH;AAEJ;;;ACnBA,SAAS,UAAU;AACnB,SAAS,UAAAC,eAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,SAAS,gBAAgB,sBAAsB;;;ACKxD,SAAS,eAAe,kBAAkB;AAGnC,IAAM,sBAAsB,cAAqC,IAAI;AAMrE,SAAS,kBAAkC;AAChD,QAAM,WAAW,WAAW,mBAAmB;AAC/C,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AACA,SAAO;AACT;;;ADQI,SAIM,OAAAC,MAJN,QAAAC,aAAA;AAXW,SAAR,SAA0B;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AACF,GAAkB;AAChB,QAAM,EAAE,aAAa,cAAc,IAAI,gBAAgB;AAEvD,QAAM,WAAW,CAAC,OAChB,oBAAoB,MAAM,2BAA2B;AAEvD,SACE,gBAAAA,MAAC,SAAI,WAAU,4EAEb;AAAA,oBAAAD,KAAC,SAAI,WAAU,2DACZ,sBAAY,IAAI,CAAC,YAChB,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,UAAU,SAAS,QAAQ,EAAE;AAAA,QAC7B,SAAS,MAAM,gBAAgB,QAAQ,EAAE;AAAA;AAAA,MAHpC,QAAQ;AAAA,IAIf,CACD,GACH;AAAA,IAGA,gBAAAA,KAAC,aAAU,WAAU,YAAW;AAAA,IAChC,gBAAAA,KAAC,SAAI,WAAU,oCACZ,wBAAc,IAAI,CAAC,YAClB,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,UAAU,SAAS,QAAQ,EAAE;AAAA,QAC7B,SAAS,MAAM,gBAAgB,QAAQ,EAAE;AAAA;AAAA,MAHpC,QAAQ;AAAA,IAIf,CACD,GACH;AAAA,KACF;AAEJ;AAUA,SAAS,SAAS,EAAE,SAAS,UAAU,QAAQ,GAAkB;AAC/D,QAAM,OAAO,QAAQ;AAErB,SACE,gBAAAC,MAAC,WAAQ,eAAe,GACtB;AAAA,oBAAAD,KAAC,kBAAe,SAAO,MACrB,0BAAAC;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,MAAK;AAAA,QACL;AAAA,QACA,WAAW;AAAA,UACT;AAAA,UACA,WACI,qCACA;AAAA,QACN;AAAA,QAEA;AAAA,0BAAAF,KAAC,QAAK,WAAU,qBAAoB;AAAA,UACpC,gBAAAA,KAAC,UAAK,WAAU,WAAW,kBAAQ,OAAM;AAAA;AAAA;AAAA,IAC3C,GACF;AAAA,IACA,gBAAAA,KAAC,kBAAe,MAAK,SAAQ,YAAY,GACtC,kBAAQ,OACX;AAAA,KACF;AAEJ;;;AE9FA,SAAS,WAAAG,gBAAe;AACxB,SAAS,aAAa,YAAY;AAClC,SAAS,cAAc,qBAAqB;AAC5C,SAAS,MAAAC,WAAU;AACnB,SAAS,kBAAkB;AAC3B,SAAS,WAAAC,UAAS,kBAAAC,iBAAgB,kBAAAC,uBAAsB;;;ACAxD,SAAS,WAAW,SAAS,cAAc;;;ACG3C,SAAS,aAAa;AACtB,SAAS,WAAAC,gBAAe;;;ACCxB,SAAS,QAAQ,eAAe;AAKhC,IAAM,YAAY,oBAAI,IAA0B;AAGzC,SAAS,GAAG,KAAa,SAA+B;AAC7D,MAAI,MAAM,UAAU,IAAI,GAAG;AAC3B,MAAI,CAAC,KAAK;AACR,UAAM,oBAAI,IAAI;AACd,cAAU,IAAI,KAAK,GAAG;AAAA,EACxB;AACA,MAAI,IAAI,OAAO;AACf,SAAO,qBAAqB,GAAG,MAAM,IAAI,IAAI,cAAc;AAC3D,SAAO,MAAM;AACX,QAAK,OAAO,OAAO;AACnB,QAAI,IAAK,SAAS,EAAG,WAAU,OAAO,GAAG;AACzC,WAAO,sBAAsB,GAAG,MAAM,IAAK,IAAI,cAAc;AAAA,EAC/D;AACF;AAGO,SAAS,KAAK,KAAa,SAA+B;AAC/D,QAAM,cAAc,GAAG,KAAK,CAAC,YAAY;AACvC,gBAAY;AACZ,YAAQ,OAAO;AAAA,EACjB,CAAC;AACD,SAAO;AACT;AAGO,SAAS,KAAK,KAAa,SAAwB;AACxD,QAAM,MAAM,UAAU,IAAI,GAAG;AAC7B,QAAM,QAAQ,KAAK,QAAQ;AAC3B,SAAO,uBAAuB,GAAG,KAAK,SAAS,IAAI,KAAK,cAAc;AACtE,MAAI,CAAC,OAAO,UAAU,EAAG;AACzB,aAAW,WAAW,KAAK;AACzB,YAAQ,QAAQ,EAAE,KAAK,MAAM;AAC3B,UAAI;AACF,gBAAQ,OAAO;AAAA,MACjB,SAAS,KAAK;AACZ,gBAAQ,gCAAgC,GAAG,MAAM,GAAG;AAAA,MACtD;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;ADpCO,IAAM,eAAe;AAAA,EAC1B,QAAQ,SAAiB;AACvB,UAAM,QAAQ,OAAO;AAAA,EACvB;AAAA,EACA,MAAM,SAAiB;AACrB,UAAM,MAAM,OAAO;AAAA,EACrB;AAAA,EACA,QAAQ,SAAiB;AACvB,UAAM,QAAQ,OAAO;AAAA,EACvB;AAAA,EACA,KAAK,SAAiB;AACpB,UAAM,KAAK,OAAO;AAAA,EACpB;AAAA,EACA,QAAW,SAAqB,MAAyB;AACvD,WAAO,MAAM,QAAQ,SAAS,IAAI;AAAA,EACpC;AAAA,EACA,QAAQ,IAAsB;AAC5B,UAAM,QAAQ,EAAE;AAAA,EAClB;AACF;AAmBA,IAAI,iBAAyC;AAMtC,SAAS,WAAW,WAA4B;AACrD,mBAAiB;AACnB;AAEO,IAAM,SAAS;AAAA;AAAA;AAAA;AAAA,EAIpB,QAAQ,MAAwC;AAC9C,QAAI,gBAAgB;AAClB,aAAO,eAAe,IAAI;AAAA,IAC5B;AACA,IAAAC,SAAQ,4CAA4C;AACpD,WAAO,QAAQ,QAAQ,KAAK;AAAA,EAC9B;AACF;AAaA,IAAI,YAA+B;AACnC,IAAI,mBAAyC;AAC7C,IAAI,iBAAyC;AAMtC,SAAS,eACd,YACA,mBACA;AACA,cAAY;AACZ,qBAAmB;AACrB;AAOO,SAAS,eAAe,UAA2B;AACxD,mBAAiB;AACnB;AAEO,IAAM,aAAa;AAAA;AAAA,EAExB,KAAK,MAAc;AACjB,QAAI,WAAW;AACb,gBAAU,IAAI;AAAA,IAChB,OAAO;AACL,MAAAA,SAAQ,6CAA6C;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,WAAmB;AAC7B,QAAI,CAAC,aAAa,CAAC,gBAAgB;AACjC,MAAAA,SAAQ,oDAAoD;AAC5D;AAAA,IACF;AACA,UAAM,OAAO,eAAe,SAAS;AACrC,QAAI,MAAM;AACR,gBAAU,IAAI;AAAA,IAChB,OAAO;AACL,MAAAA,SAAQ,oDAAoD,SAAS;AAAA,IACvE;AAAA,EACF;AAAA;AAAA,EAEA,kBAAkB;AAChB,QAAI,kBAAkB;AACpB,uBAAiB;AAAA,IACnB,OAAO;AACL,MAAAA,SAAQ,wDAAwD;AAAA,IAClE;AAAA,EACF;AAAA;AAAA,EAEA,OAAO;AACL,WAAO,QAAQ,KAAK;AAAA,EACtB;AACF;AAqDA,IAAI,aAAiC;AACrC,IAAI,cAAmC;AAMhC,SAAS,UAAU,QAAqB,SAAuB;AACpE,eAAa;AACb,gBAAc;AAChB;AAEO,IAAM,QAAQ;AAAA;AAAA,EAEnB,KAAK,MAAoB;AACvB,QAAI,YAAY;AACd,iBAAW,IAAI;AAAA,IACjB,OAAO;AACL,MAAAA,SAAQ,wCAAwC;AAAA,IAClD;AAAA,EACF;AAAA;AAAA,EAEA,QAAQ;AACN,QAAI,aAAa;AACf,kBAAY;AAAA,IACd,OAAO;AACL,MAAAA,SAAQ,yCAAyC;AAAA,IACnD;AAAA,EACF;AACF;AAiBA,IAAI,gBAAuC;AAC3C,IAAI,iBAAyC;AAC7C,IAAI,kBAA2C;AAC/C,IAAI,0BAA2D;AAMxD,SAAS,aACd,QACA,SACA,UACA,kBACA;AACA,kBAAgB;AAChB,mBAAiB;AACjB,oBAAkB;AAClB,4BAA0B;AAC5B;AAEO,IAAM,WAAW;AAAA;AAAA,EAEtB,KAAK,MAAuB;AAC1B,QAAI,eAAe;AACjB,oBAAc,IAAI;AAAA,IACpB,OAAO;AACL,MAAAA,SAAQ,2CAA2C;AAAA,IACrD;AAAA,EACF;AAAA;AAAA,EAEA,QAAQ;AACN,QAAI,gBAAgB;AAClB,qBAAe;AAAA,IACjB,OAAO;AACL,MAAAA,SAAQ,4CAA4C;AAAA,IACtD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OAAO,MAAuB;AAC5B,QACE,CAAC,mBACD,CAAC,2BACD,CAAC,iBACD,CAAC,gBACD;AACA,MAAAA,SAAQ,6CAA6C;AACrD;AAAA,IACF;AACA,QAAI,gBAAgB,KAAK,wBAAwB,MAAM,KAAK,UAAU;AACpE,qBAAe;AAAA,IACjB,OAAO;AACL,oBAAc,IAAI;AAAA,IACpB;AAAA,EACF;AAAA;AAAA,EAEA,SAAkB;AAChB,WAAO,kBAAkB,gBAAgB,IAAI;AAAA,EAC/C;AAAA;AAAA,EAEA,iBAAgC;AAC9B,WAAO,0BAA0B,wBAAwB,IAAI;AAAA,EAC/D;AACF;AAiBA,IAAI,mBAA6C;AACjD,IAAI,kBAA2C;AAMxC,SAAS,eACd,SACA,QACA;AACA,qBAAmB;AACnB,oBAAkB;AACpB;AAEO,IAAM,aAAa;AAAA;AAAA,EAExB,MAAM,MAAyB;AAC7B,QAAI,kBAAkB;AACpB,uBAAiB,IAAI;AAAA,IACvB,OAAO;AACL,MAAAA,SAAQ,8CAA8C;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAEA,OAAO;AACL,QAAI,iBAAiB;AACnB,sBAAgB;AAAA,IAClB,OAAO;AACL,MAAAA,SAAQ,6CAA6C;AAAA,IACvD;AAAA,EACF;AACF;AAMO,IAAM,SAAS;AAAA;AAAA,EAEpB,GACE,KACA,SACa;AACb,WAAgB,GAAG,KAAe,OAAqC;AAAA,EACzE;AAAA;AAAA,EAGA,KACE,KACA,SACa;AACb,WAAgB,KAAK,KAAe,OAAqC;AAAA,EAC3E;AAAA;AAAA,EAGA,KAAoC,KAAQ,SAAiC;AAC3E,IAAS,KAAK,KAAe,OAAO;AAAA,EACtC;AACF;;;AD3XO,SAAS,kBAAkB,MAA8B;AAC9D,QAAM,EAAE,kBAAkB,IAAI,gBAAgB;AAC9C,SAAO,QAAQ,MAAM,kBAAkB,IAAI,GAAG,CAAC,mBAAmB,IAAI,CAAC;AACzE;AAOO,SAAS,iBAAkC;AAChD,QAAM,EAAE,YAAY,IAAI,gBAAgB;AACxC,SAAO;AACT;AAeO,SAAS,cACd,KACA,SACM;AACN,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,YAAU,MAAM;AACd,WAAO,OAAO,GAAG,KAAK,CAAC,YAAY,WAAW,QAAQ,OAAO,CAAC;AAAA,EAChE,GAAG,CAAC,GAAG,CAAC;AACV;;;ADDQ,SAMM,OAAAC,MANN,QAAAC,aAAA;AApCR,SAAS,oBAAoB,SAAoC;AAC/D,QAAM,gBAAgB,kBAAkB,GAAG,QAAQ,EAAE,MAAM;AAC3D,SAAOC,SAAQ,MAAM;AACnB,QAAI,cAAc,WAAW,EAAG,QAAO,QAAQ;AAE/C,UAAM,cAAwB;AAAA,MAC5B,IAAI,GAAG,QAAQ,EAAE;AAAA,MACjB,OAAO,cAAc,IAAI,CAAC,OAAO;AAAA,QAC/B,IAAI,EAAE,iBAAiB,MAAM,EAAE,MAAM,YAAY,EAAE,QAAQ,QAAQ,GAAG;AAAA,QACtE,OAAO,EAAE;AAAA,QACT,MAAM,EAAE;AAAA,MACV,EAAE;AAAA,IACJ;AAEA,WAAO,CAAC,GAAG,QAAQ,YAAY,WAAW;AAAA,EAC5C,GAAG,CAAC,SAAS,aAAa,CAAC;AAC7B;AASe,SAAR,YAA6B;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,GAAqB;AACnB,QAAM,EAAE,SAAS,IAAI,YAAY;AACjC,QAAM,mBAAmB,oBAAoB,OAAO;AAEpD,MAAI,WAAW;AACb,WACE,gBAAAF,KAAC,WAAM,WAAU,qEACf,0BAAAC,MAACE,UAAA,EACC;AAAA,sBAAAH,KAACI,iBAAA,EAAe,SAAO,MACrB,0BAAAJ;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS;AAAA,UAET,0BAAAA,KAAC,iBAAc,WAAU,WAAU;AAAA;AAAA,MACrC,GACF;AAAA,MACA,gBAAAA,KAACK,iBAAA,EAAe,MAAK,SAAQ,+BAAiB;AAAA,OAChD,GACF;AAAA,EAEJ;AAEA,SACE,gBAAAJ,MAAC,WAAM,WAAU,sDAEf;AAAA,oBAAAA,MAAC,SAAI,WAAU,wDACb;AAAA,sBAAAD,KAAC,UAAK,WAAU,kEACb,kBAAQ,OACX;AAAA,MACA,gBAAAC,MAACE,UAAA,EACC;AAAA,wBAAAH,KAACI,iBAAA,EAAe,SAAO,MACrB,0BAAAJ;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,SAAS;AAAA,YAET,0BAAAA,KAAC,gBAAa,WAAU,eAAc;AAAA;AAAA,QACxC,GACF;AAAA,QACA,gBAAAA,KAACK,iBAAA,EAAe,MAAK,SAAQ,iCAAmB;AAAA,SAClD;AAAA,OACF;AAAA,IAGA,gBAAAL,KAAC,cAAW,WAAU,UACpB,0BAAAA,KAAC,SAAI,WAAU,aACZ,2BAAiB,IAAI,CAAC,OAAO,OAC5B,gBAAAC,MAAC,SAAmB,WAAWK,IAAG,KAAK,KAAK,MAAM,GAC/C;AAAA,YAAM,WACL,gBAAAN,KAAC,QAAG,WAAU,0BAA0B,gBAAM,SAAQ;AAAA,MAExD,gBAAAA,KAAC,QAAG,WAAU,eACX,gBAAM,MAAM,IAAI,CAAC,SAAS;AACzB,cAAM,WAAW,aAAa,KAAK;AACnC,eACE,gBAAAA,KAAC,QACC,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC,IAAI,KAAK;AAAA,YACT,WAAWM;AAAA,cACT;AAAA,cACA,WACI,iDACA;AAAA,YACN;AAAA,YAEC,eAAK;AAAA;AAAA,QACR,KAXO,KAAK,IAYd;AAAA,MAEJ,CAAC,GACH;AAAA,SAvBQ,MAAM,EAwBhB,CACD,GACH,GACF;AAAA,KACF;AAEJ;;;AIrHA,SAAS,SAAS;AAClB,SAAS,MAAAC,WAAU;AACnB,SAAS,yBAAyB;;;ACDlC,SAAS,iBAAAC,gBAAe,cAAAC,mBAAkC;AA8BnD,IAAM,kBAAkBD,eAAoC,IAAI;AAMhE,SAAS,mBAAkC;AAChD,QAAM,MAAMC,YAAW,eAAe;AACtC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AACA,SAAO;AACT;;;ADfI,SAeM,OAAAC,MAfN,QAAAC,aAAA;AAvBJ,IAAM,gBAAgB;AACtB,IAAM,cAAc;AACpB,IAAM,cAAc;AAOL,SAAR,WAA4B;AACjC,QAAM,EAAE,MAAM,MAAM,IAAI,iBAAiB;AAEzC,QAAM,EAAE,YAAY,mBAAmB,mBAAmB,eAAe,IACvE,kBAAkB;AAAA,IAChB,cAAc,MAAM,gBAAgB;AAAA,IACpC,UAAU,MAAM,YAAY;AAAA,IAC5B,UAAU,MAAM,YAAY;AAAA,IAC5B,WAAW;AAAA,EACb,CAAC;AAEH,MAAI,CAAC,KAAM,QAAO;AAElB,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,OAAO,EAAE,OAAO,WAAW;AAAA,MAG3B;AAAA,wBAAAD,KAAC,SAAI,WAAU,6DACb,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAS;AAAA,YACT,WAAWE;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YACF;AAAA,YACA,cAAW;AAAA,YAEX,0BAAAF,KAAC,KAAE,WAAU,eAAc;AAAA;AAAA,QAC7B,GACF;AAAA,QAGA,gBAAAA,KAAC,SAAI,WAAU,wBAAwB,eAAK,SAAQ;AAAA,QAGpD,gBAAAA;AAAA,UAAC;AAAA;AAAA,YACE,GAAG;AAAA,YACJ,eAAe;AAAA,YACf,eAAe;AAAA,YACf,WAAWE;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;;;AExEA,SAAS,aAAAC,YAAW,mBAAmB;AACvC,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8CK,SA8DF,UAzDI,OAAAC,MALF,QAAAC,aAAA;AA/BG,SAAR,YAA6B,EAAE,MAAM,aAAa,GAAqB;AAC5E,QAAM,WAAW,YAAY;AAC7B,QAAM,EAAE,aAAa,cAAc,IAAI,gBAAgB;AAGvD,EAAAC,WAAU,MAAM;AACd,UAAM,gBAAgB,CAAC,MAAqB;AAC1C,UAAI,EAAE,QAAQ,QAAQ,EAAE,WAAW,EAAE,UAAU;AAC7C,UAAE,eAAe;AACjB,qBAAa,CAAC,IAAI;AAAA,MACpB;AAAA,IACF;AACA,aAAS,iBAAiB,WAAW,aAAa;AAClD,WAAO,MAAM,SAAS,oBAAoB,WAAW,aAAa;AAAA,EACpE,GAAG,CAAC,MAAM,YAAY,CAAC;AAEvB,QAAM,eAAe;AAAA,IACnB,CAAC,SAAiB;AAChB,eAAS,IAAI;AACb,mBAAa,KAAK;AAAA,IACpB;AAAA,IACA,CAAC,UAAU,YAAY;AAAA,EACzB;AAEA,QAAM,iBAAiB,CAAC,YAA2B;AACjD,QAAI,QAAQ,WAAW,WAAW,EAAG,QAAO;AAC5C,UAAM,OAAO,QAAQ;AACrB,WACE,gBAAAF,KAAC,gBAAuC,SAAS,QAAQ,OACtD,kBAAQ,WAAW;AAAA,MAAQ,CAAC,UAC3B,MAAM,MAAM,IAAI,CAAC,SACf,gBAAAC;AAAA,QAAC;AAAA;AAAA,UAEC,OAAO,GAAG,QAAQ,KAAK,IAAI,MAAM,WAAW,EAAE,IAAI,KAAK,KAAK;AAAA,UAC5D,UAAU,MAAM,aAAa,KAAK,IAAI;AAAA,UAEtC;AAAA,4BAAAD,KAAC,QAAK,WAAU,sCAAqC;AAAA,YACrD,gBAAAA,KAAC,UAAM,eAAK,OAAM;AAAA,YACjB,MAAM,WACL,gBAAAA,KAAC,UAAK,WAAU,yCACb,gBAAM,SACT;AAAA;AAAA;AAAA,QATG,KAAK;AAAA,MAWZ,CACD;AAAA,IACH,KAjBiB,OAAO,QAAQ,EAAE,EAkBpC;AAAA,EAEJ;AAEA,QAAM,qBAAqB,CAAC,YAA2B;AACrD,QAAI,CAAC,QAAQ,UAAU,OAAQ,QAAO;AACtC,UAAM,OAAO,QAAQ;AACrB,WACE,gBAAAA;AAAA,MAAC;AAAA;AAAA,QAEC,SAAS,QAAQ,SAAS,CAAC,EAAE,SAAS,QAAQ;AAAA,QAE7C,kBAAQ,SAAS,IAAI,CAAC,QAAQ;AAC7B,gBAAM,UAAU,IAAI,QAAQ;AAC5B,iBACE,gBAAAC;AAAA,YAAC;AAAA;AAAA,cAEC,OAAO,GAAG,QAAQ,KAAK,IAAI,IAAI,KAAK;AAAA,cACpC,UAAU,MAAM;AACd,oBAAI,OAAO;AACX,6BAAa,KAAK;AAAA,cACpB;AAAA,cAEA;AAAA,gCAAAD,KAAC,WAAQ,WAAU,sCAAqC;AAAA,gBACxD,gBAAAA,KAAC,UAAM,cAAI,OAAM;AAAA;AAAA;AAAA,YARZ,IAAI;AAAA,UASX;AAAA,QAEJ,CAAC;AAAA;AAAA,MAlBI,OAAO,QAAQ,EAAE;AAAA,IAmBxB;AAAA,EAEJ;AAEA,QAAM,aAAa,CAAC,GAAG,aAAa,GAAG,aAAa;AAEpD,SACE,gBAAAC,MAAC,iBAAc,MAAY,cACzB;AAAA,oBAAAD,KAAC,gBAAa,aAAY,sCAAqC;AAAA,IAC/D,gBAAAC,MAAC,eACC;AAAA,sBAAAD,KAAC,gBAAa,+BAAiB;AAAA,MAE9B,YAAY,IAAI,cAAc;AAAA,MAE/B,gBAAAA,KAAC,oBAAiB;AAAA,MAEjB,cAAc,IAAI,cAAc;AAAA,MAEhC,WAAW,KAAK,CAAC,MAAM,EAAE,UAAU,MAAM,KACxC,gBAAAC,MAAA,YACE;AAAA,wBAAAD,KAAC,oBAAiB;AAAA,QACjB,WAAW,IAAI,kBAAkB;AAAA,SACpC;AAAA,OAEJ;AAAA,KACF;AAEJ;;;ACrHA,SAAS,iBAAAG,gBAAe,cAAAC,mBAAkC;AAmBnD,IAAM,qBAAqBD,eAAkC,IAAI;AAMjE,SAAS,iBAA8B;AAC5C,QAAM,SAASC,YAAW,kBAAkB;AAC5C,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,0DAA0D;AAAA,EAC5E;AACA,SAAO;AACT;;;AZmTY,SAyHR,YAAAC,WAzHQ,OAAAC,MASA,QAAAC,aATA;AAxTZ,IAAM,oBAA4C;AAAA,EAChD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA;AAAA,EAEN,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,OAAO;AACT;AAIA,SAAS,iBAAiB,SAGd;AACV,SAAO,CAAC,CAAC,QAAQ,YAAY,QAAQ,WAAW,WAAW;AAC7D;AAEA,SAAS,qBACP,SAKA,SACA,kBACA,UACM;AACN,MAAI,SAAS,aAAa,QAAQ,IAAI;AACpC,qBAAiB,IAAI;AAAA,EACvB,OAAO;AACL,qBAAiB;AAAA,MACf,UAAU,QAAQ;AAAA,MAClB,SAAS;AAAA,MACT,cAAc,QAAQ,UAAU;AAAA,MAChC,UAAU,QAAQ,UAAU;AAAA,MAC5B,UAAU,QAAQ,UAAU;AAAA,IAC9B,CAAC;AACD,aAAS,QAAQ,QAAQ;AAAA,EAC3B;AACF;AAEA,SAAS,gBACP,SAIA,UACA,oBACM;AACN,qBAAmB,KAAK;AACxB,QAAM,SAAS,QAAQ,WAAW,CAAC,GAAG,MAAM,CAAC,GAAG,QAAQ,QAAQ;AAChE,WAAS,MAAM;AACjB;AAGA,SAAS,gBAAgB,MAatB;AACD,QAAM,aAAaC,QAAO,IAAI;AAC9B,EAAAC,WAAU,MAAM;AACd,UAAM,IAAI,WAAW;AACrB,mBAAe,EAAE,UAAU,EAAE,eAAe;AAC5C,mBAAe,EAAE,aAAa;AAC9B,cAAU,EAAE,iBAAiB,EAAE,gBAAgB;AAC/C,mBAAe,EAAE,uBAAuB,EAAE,oBAAoB;AAC9D;AAAA,MACE,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,MACF,EAAE;AAAA,IACJ;AACA;AAAA,MACE,CAAC,SACC,IAAI,QAAiB,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC,CAAC;AAAA,IACzE;AAAA,EACF,GAAG,CAAC,CAAC;AACP;AAqBA,SAAS,iBAAiB,SAAoC;AAC5D,QAAM,WAAWC,aAAY;AAC7B,QAAM,oBAAoBC;AAAA,IACxB,MACE,IAAI;AAAA,MACF,QACG,OAAO,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,aAAa,GAAG,EAC7C,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC;AAAA,IAClC;AAAA,IACF,CAAC,OAAO;AAAA,EACV;AACA,QAAM,yBAAyBC;AAAA,IAC7B,CAAC,aAAoC;AACnC,iBAAW,CAAC,UAAU,EAAE,KAAK,mBAAmB;AAC9C,YAAI,SAAS,WAAW,QAAQ,EAAG,QAAO;AAAA,MAC5C;AACA,aAAO;AAAA,IACT;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AACA,QAAM,CAAC,mBAAmB,oBAAoB,IAAI;AAAA,IAChD,MAAM,uBAAuB,SAAS,QAAQ;AAAA,EAChD;AACA,EAAAH;AAAA,IACE,MAAM,qBAAqB,uBAAuB,SAAS,QAAQ,CAAC;AAAA,IACpE,CAAC,SAAS,UAAU,sBAAsB;AAAA,EAC5C;AACA,QAAM,gBAAgB,oBACjB,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,iBAAiB,KAAK,OACpD;AACJ,SAAO,EAAE,eAAe,mBAAmB,qBAAqB;AAClE;AAGA,SAASI,oBAAmB;AAC1B,QAAM,CAAC,eAAe,gBAAgB,IAAI;AAAA,IACxC;AAAA,EACF;AACA,QAAM,aAAaD;AAAA,IACjB,CAAC,SAA0B,iBAAiB,IAAI;AAAA,IAChD,CAAC;AAAA,EACH;AACA,QAAM,cAAcA,aAAY,MAAM,iBAAiB,IAAI,GAAG,CAAC,CAAC;AAChE,QAAM,eAAeA;AAAA,IACnB,MAAM,kBAAkB;AAAA,IACxB,CAAC,aAAa;AAAA,EAChB;AACA,QAAM,uBAAuBA;AAAA,IAC3B,MAAM,eAAe,YAAY;AAAA,IACjC,CAAC,aAAa;AAAA,EAChB;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,gBAAgB;AACvB,QAAM,CAAC,YAAY,aAAa,IAAI,SAA8B,IAAI;AACtE,QAAM,kBAAkBJ,QAAiC,MAAS;AAClE,QAAM,aAAaI;AAAA,IACjB,CAAC,SAAuB,cAAc,IAAI;AAAA,IAC1C,CAAC;AAAA,EACH;AACA,QAAM,cAAcA,aAAY,MAAM;AACpC,oBAAgB,UAAU,YAAY;AACtC,kBAAc,IAAI;AAAA,EACpB,GAAG,CAAC,UAAU,CAAC;AACf,QAAM,aACJ,kBAAkB,YAAY,SAAS,SAAS,KAChD,kBAAkB;AACpB,QAAM,iBAAiB,YAAY,aAAa,SAAS,QAAQ;AACjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,iBAAiB;AACxB,QAAM,CAAC,aAAa,cAAc,IAAI,SAA6B,IAAI;AACvE,QAAM,gBAAgBA,aAAY,MAAM;AACtC,iBAAa,QAAQ,IAAI;AACzB,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,WAAW,CAAC;AAChB,QAAM,eAAeA,aAAY,MAAM;AACrC,iBAAa,QAAQ,KAAK;AAC1B,mBAAe,IAAI;AAAA,EACrB,GAAG,CAAC,WAAW,CAAC;AAChB,SAAO,EAAE,aAAa,gBAAgB,eAAe,aAAa;AACpE;AAGA,SAAS,iBAAiB,SAAoC;AAC5D,SAAOD,SAAQ,MAAM;AACnB,UAAM,MAAM,oBAAI,IAAoB;AACpC,eAAW,KAAK,SAAS;AACvB,iBAAW,SAAS,EAAE,YAAY;AAChC,mBAAW,QAAQ,MAAM,OAAO;AAC9B,cAAI,KAAK,GAAI,KAAI,IAAI,KAAK,IAAI,KAAK,IAAI;AAAA,QACzC;AAAA,MACF;AAAA,IACF;AACA,WAAO,CAAC,cAAsB,IAAI,IAAI,SAAS,KAAK;AAAA,EACtD,GAAG,CAAC,OAAO,CAAC;AACd;AAEe,SAAR,SAA0B,EAAE,SAAS,GAAkB;AAC5D,QAAM,WAAWG,aAAY;AAC7B,QAAM,SAAS,eAAe;AAC9B,QAAM,EAAE,QAAQ,IAAI;AAEpB,QAAM,EAAE,eAAe,qBAAqB,IAAI,iBAAiB,OAAO;AACxE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,KAAK;AAC5D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,SAAS,KAAK;AAC5D,QAAM,kBAAkBF,aAAY,MAAM,mBAAmB,IAAI,GAAG,CAAC,CAAC;AACtE,QAAM,CAAC,iBAAiB,kBAAkB,IACxC,SAAmC,IAAI;AACzC,QAAM,wBAAwBA;AAAA,IAC5B,CAAC,SAA4B,mBAAmB,IAAI;AAAA,IACpD,CAAC;AAAA,EACH;AACA,QAAM,uBAAuBA,aAAY,MAAM,mBAAmB,IAAI,GAAG,CAAC,CAAC;AAE3E,QAAMG,YAAWF,kBAAiB;AAClC,QAAMG,SAAQ,cAAc;AAC5B,QAAMC,UAAS,eAAe;AAC9B,QAAM,gBAAgB,iBAAiB,OAAO;AAE9C,kBAAgB;AAAA,IACd;AAAA,IACA;AAAA,IACA,iBAAiBD,OAAM;AAAA,IACvB,kBAAkBA,OAAM;AAAA,IACxB;AAAA,IACA;AAAA,IACA,oBAAoBD,UAAS;AAAA,IAC7B,qBAAqBA,UAAS;AAAA,IAC9B,sBAAsBA,UAAS;AAAA,IAC/B,8BAA8BA,UAAS;AAAA,IACvC,gBAAgBE,QAAO;AAAA,IACvB;AAAA,EACF,CAAC;AAED,QAAM,sBAAsBL;AAAA,IAC1B,CAAC,cAAsB;AACrB,YAAM,UAAU,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,SAAS;AACtD,UAAI,CAAC,QAAS;AACd,UAAI,iBAAiB,OAAO,GAAG;AAC7B;AAAA,UACE;AAAA,UACAG,UAAS;AAAA,UACTA,UAAS;AAAA,UACT;AAAA,QACF;AAAA,MACF,OAAO;AACL,wBAAgB,SAAS,UAAU,kBAAkB;AAAA,MACvD;AACA,2BAAqB,SAAS;AAAA,IAChC;AAAA,IACA;AAAA,MACE;AAAA,MACAA,UAAS;AAAA,MACTA,UAAS;AAAA,MACT;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,QAAM,uBAAuBJ;AAAA,IAC3B,OAAO;AAAA,MACL,MAAMI,UAAS;AAAA,MACf,MAAMA,UAAS;AAAA,MACf,OAAOA,UAAS;AAAA,MAChB,QAAQ,CAAC,SAA0B;AACjC,YAAIA,UAAS,eAAe,aAAa,KAAK,UAAU;AACtD,UAAAA,UAAS,iBAAiB,IAAI;AAAA,QAChC,OAAO;AACL,UAAAA,UAAS,iBAAiB,IAAI;AAAA,QAChC;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAACA,SAAQ;AAAA,EACX;AAEA,SACE,gBAAAT,KAAC,oBAAoB,UAApB,EAA6B,OAAO,UACnC,0BAAAA,KAAC,gBAAgB,UAAhB,EAAyB,OAAO,sBAC/B,0BAAAA,KAAC,mBACC,0BAAAC,MAAC,SAAI,WAAU,mDAEb;AAAA,oBAAAD,KAAC,UAAQ,iBAAO,QAAO;AAAA,IAGvB,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,cAAc;AAAA;AAAA,IAChB;AAAA,IAGA,gBAAAC,MAAC,SAAI,WAAU,+BACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,iBAAiB,eAAe,MAAM;AAAA,UACtC,wBACES,UAAS,eAAe,YAAY;AAAA,UAEtC,iBAAiB;AAAA;AAAA,MACnB;AAAA,MAEC,iBAAiB,cAAc,WAAW,SAAS,KAClD,gBAAAT;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,UACT,WAAW;AAAA,UACX,kBAAkB,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAAA;AAAA,MACtD;AAAA,MAIF,gBAAAA,KAAC,YAAS;AAAA,MAGV,gBAAAA,KAAC,SAAI,WAAU,kCACb,0BAAAA,KAACY,aAAA,EAAW,WAAU,UACpB,0BAAAZ,KAAC,UAAK,WAAU,OACd,0BAAAA,KAAC,UAAO,GACV,GACF,GACF;AAAA,OACF;AAAA,IAGCW,QAAO,eACN,gBAAAX;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,OAAOW,QAAO,YAAY,KAAK;AAAA,QAC/B,aAAaA,QAAO,YAAY,KAAK;AAAA,QACrC,cAAcA,QAAO,YAAY,KAAK;AAAA,QACtC,aAAaA,QAAO,YAAY,KAAK;AAAA,QACrC,SAASA,QAAO,YAAY,KAAK;AAAA,QACjC,WAAWA,QAAO;AAAA,QAClB,UAAUA,QAAO;AAAA;AAAA,IACnB;AAAA,IAID,mBACC,gBAAAX,KAAC,SAAI,WAAU,kDACZ,0BAAgB,SACnB;AAAA,IAIF,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,YAAYU,OAAM;AAAA,QAClB,YAAYA,OAAM;AAAA,QAClB,gBAAgBA,OAAM;AAAA,QACtB,iBAAiBA,OAAM;AAAA,QACvB,SAASA,OAAM;AAAA;AAAA,IACjB;AAAA,KACF,GACF,GACF,GACF;AAEJ;AAYA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,SACE,gBAAAV;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,eAAe;AAAA,MACrB,cAAc,CAAC,SAAS;AACtB,YAAI,CAAC,KAAM,SAAQ;AAAA,MACrB;AAAA,MAEA,0BAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAW,GAAG,UAAU;AAAA,UACxB,WAAW;AAAA,UACX,kBAAkB,CAAC,MAAM;AACvB,gBAAI,gBAAgB,SAAS;AAC3B,gBAAE,eAAe;AACjB,8BAAgB,QAAQ;AAAA,YAC1B;AAAA,UACF;AAAA,UACC,GAAI,CAAC,YAAY,eAAe;AAAA,YAC/B,oBAAoB;AAAA,UACtB;AAAA,UAEC,wBAAc,gBAAAA,KAAC,gBAAa,OAAO,YAAY;AAAA;AAAA,MAClD;AAAA;AAAA,EACF;AAEJ;AAEA,SAAS,aAAa,EAAE,MAAM,GAA4B;AACxD,SACE,gBAAAC,MAAAF,WAAA,EACG;AAAA,UAAM,QACL,gBAAAE,MAAC,eACC;AAAA,sBAAAD,KAAC,cAAY,gBAAM,OAAM;AAAA,MACxB,MAAM,eACL,gBAAAA,KAAC,oBAAkB,gBAAM,aAAY;AAAA,OAEzC,IAEA,gBAAAA,KAAC,cAAW,WAAU,WAAU,mBAAK;AAAA,IAEvC,gBAAAA,KAAC,aAAW,gBAAM,SAAQ;AAAA,KAC5B;AAEJ;;;AaleA,SAAS,eAAe;;;ACgBpB,gBAAAa,YAAA;AALG,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAA6B;AAC3B,SACE,gBAAAA,KAAC,mBAAmB,UAAnB,EAA4B,OAAO,QACjC,UACH;AAEJ;;;ADFI,SACE,OAAAC,MADF,QAAAC,aAAA;AAFW,SAAR,WAA4B,EAAE,QAAQ,SAAS,GAAoB;AACxE,SACE,gBAAAA,MAAC,uBAAoB,QACnB;AAAA,oBAAAD,KAAC,WAAQ,UAAS,aAAY,YAAU,MAAC,aAAW,MAAC;AAAA,IACrD,gBAAAA,KAAC,YAAS,UAAoB;AAAA,KAChC;AAEJ;;;AEvBA,SAAS,eAAAE,oBAAmB;AAUxB,SACE,OAAAC,OADF,QAAAC,aAAA;AAJW,SAAR,kBAAmC;AACxC,QAAM,EAAE,SAAS,IAAIF,aAAY;AAEjC,SACE,gBAAAE,MAAC,SACC;AAAA,oBAAAD,MAAC,QAAG,WAAU,QAAQ,sBAAY,QAAQ,GAAE;AAAA,IAC5C,gBAAAA,MAAC,OAAE,WAAU,sCAAsC,oBAAS;AAAA,IAG5D,gBAAAA,MAAC,SAAI,WAAU,yFACb,0BAAAA,MAAC,UAAK,WAAU,iCAAgC,qDAEhD,GACF;AAAA,KACF;AAEJ;AAGA,SAAS,YAAY,MAAsB;AACzC,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,IAAI;AACjD,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,EACjD,KAAK,GAAG;AACb;;;AChCA,SAAS,cAAc;AACvB,SAAS,UAAAE,eAAc;AAiBjB,gBAAAC,OAEA,QAAAC,aAFA;AARC,SAAS,gBAAgB;AAC9B,SACE,gBAAAA;AAAA,IAACC;AAAA,IAAA;AAAA,MACC,SAAQ;AAAA,MACR,MAAK;AAAA,MACL,WAAU;AAAA,MACV,SAAS,MAAM,WAAW,gBAAgB;AAAA,MAE1C;AAAA,wBAAAF,MAAC,UAAO,WAAU,eAAc;AAAA,QAChC,gBAAAA,MAAC,UAAK,uBAAS;AAAA,QACf,gBAAAC,MAAC,SAAI,WAAU,qKACb;AAAA,0BAAAD,MAAC,UAAK,WAAU,WAAU,oBAAQ;AAAA,UAAO;AAAA,WAC3C;AAAA;AAAA;AAAA,EACF;AAEJ;;;ACzBA,SAAS,QAAQ,YAAY;AAC7B,SAAS,UAAAG,eAAc;AACvB,SAAS,QAAQ,sBAAsB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA2BK,gBAAAC,OAMF,QAAAC,cANE;AANL,SAAS,SAAS,EAAE,MAAM,UAAU,GAAkB;AAC3D,SACE,gBAAAA,OAAC,gBACC;AAAA,oBAAAD,MAAC,uBAAoB,SAAO,MAC1B,0BAAAA,MAACD,SAAA,EAAO,SAAQ,SAAQ,MAAK,WAAU,WAAU,gBAC/C,0BAAAC,MAAC,UAAO,WAAU,WAChB,0BAAAA,MAAC,kBAAe,WAAU,WAAW,eAAK,UAAS,GACrD,GACF,GACF;AAAA,IACA,gBAAAC,OAAC,uBAAoB,OAAM,OAAM,WAAU,QACzC;AAAA,sBAAAD,MAAC,qBAAkB,WAAU,eAC3B,0BAAAC,OAAC,SAAI,WAAU,uBACb;AAAA,wBAAAD,MAAC,OAAE,WAAU,uBAAuB,eAAK,MAAK;AAAA,QAC9C,gBAAAA,MAAC,OAAE,WAAU,iCAAiC,eAAK,OAAM;AAAA,SAC3D,GACF;AAAA,MACA,gBAAAA,MAAC,yBAAsB;AAAA,MACvB,gBAAAC,OAAC,oBACC;AAAA,wBAAAD,MAAC,QAAK,WAAU,gBAAe;AAAA,QAAE;AAAA,SAEnC;AAAA,MACA,gBAAAA,MAAC,yBAAsB;AAAA,MACvB,gBAAAC,OAAC,oBAAiB,UAAU,WAC1B;AAAA,wBAAAD,MAAC,UAAO,WAAU,gBAAe;AAAA,QAAE;AAAA,SAErC;AAAA,OACF;AAAA,KACF;AAEJ;;;ACjCO,SAAS,qBAAqB,SAA0C;AAC7E,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM;AAC/C,QAAM,cAAc,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS;AACtD,QAAM,gBAAgB,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS;AACvD,QAAM,YAAY,QAAQ,QAAQ,CAAC,MAAM,EAAE,MAAM;AAGjD,QAAM,uBAAuB,oBAAI,IAA4B;AAC7D,aAAW,KAAK,SAAS;AACvB,eAAW,KAAK,EAAE,iBAAiB,CAAC,GAAG;AACrC,YAAM,OAAO,qBAAqB,IAAI,EAAE,cAAc,KAAK,CAAC;AAC5D,WAAK,KAAK,CAAC;AACX,2BAAqB,IAAI,EAAE,gBAAgB,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,aAAW,QAAQ,qBAAqB,OAAO,GAAG;AAChD,SAAK,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAAA,EACvC;AAEA,WAAS,kBAAkB,MAA8B;AACvD,WAAO,qBAAqB,IAAI,IAAI,KAAK,CAAC;AAAA,EAC5C;AAEA,SAAO,EAAE,SAAS,aAAa,eAAe,WAAW,kBAAkB;AAC7E;;;ACxCA,SAAS,MAAAE,WAAU;AAqCf,gBAAAC,OA2CE,QAAAC,cA3CF;AARG,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AACF,GAGuB;AACrB,SACE,gBAAAD;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,UAAU;AAAA,MACV,SAAS,CAAC,MAAM;AACd,UAAE,gBAAgB;AAClB,mBAAW,YAAY,OAAO;AAAA,MAChC;AAAA,MACA,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,YAAE,gBAAgB;AAClB,qBAAW,YAAY,OAAO;AAAA,QAChC;AAAA,MACF;AAAA,MACA,WAAU;AAAA,MAET;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,aAAa;AAAA,EAC3B,MAAM;AAAA,EACN;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA0C;AACxC,SACE,gBAAAC;AAAA,IAAC;AAAA;AAAA,MACC,SAAS,UAAU,MAAM,WAAW,YAAY,OAAO,IAAI;AAAA,MAC3D,UAAU,CAAC;AAAA,MACX,WAAWC;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX;AAAA,MACF;AAAA,MAEA;AAAA,wBAAAF,MAAC,SAAI,WAAU,2EACb,0BAAAA,MAAC,QAAK,WAAU,yBAAwB,GAC1C;AAAA,QACA,gBAAAC,OAAC,SACC;AAAA,0BAAAD,MAAC,OAAE,WAAU,4BAA4B,iBAAM;AAAA,UAC/C,gBAAAA,MAAC,OAAE,WAAU,iDACV,uBACH;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":["useEffect","useCallback","useRef","useMemo","useLocation","useNavigate","ScrollArea","jsx","Button","jsx","jsxs","Button","useMemo","cn","Tooltip","TooltipContent","TooltipTrigger","warnLog","warnLog","jsx","jsxs","useMemo","Tooltip","TooltipTrigger","TooltipContent","cn","cn","createContext","useContext","jsx","jsxs","cn","useEffect","jsx","jsxs","useEffect","createContext","useContext","Fragment","jsx","jsxs","useRef","useEffect","useLocation","useMemo","useCallback","useSidePaneState","useNavigate","sidePane","panel","dialog","ScrollArea","jsx","jsx","jsxs","useLocation","jsx","jsxs","Button","jsx","jsxs","Button","Button","jsx","jsxs","cn","jsx","jsxs","cn"]}
1
+ {"version":3,"sources":["../src/AppShell.tsx","../src/ConfirmDialog.tsx","../src/TopBar.tsx","../src/IconRail.tsx","../src/shellModules.ts","../src/SubNavPanel.tsx","../src/hooks.ts","../src/api.ts","../src/eventBus.ts","../src/SidePane.tsx","../src/sidePaneState.ts","../src/CommandMenu.tsx","../src/shellConfig.ts","../src/RootLayout.tsx","../src/ShellConfigProvider.tsx","../src/PlaceholderPage.tsx","../src/SearchTrigger.tsx","../src/UserMenu.tsx","../src/createModuleRegistry.ts","../src/OverviewCard.tsx"],"sourcesContent":["import React, {\n useState,\n useEffect,\n useCallback,\n useRef,\n useMemo,\n} from \"react\";\nimport { Outlet, useLocation, useNavigate } from \"react-router-dom\";\nimport { TooltipProvider } from \"@petrarca/sonnet-ui\";\nimport { ScrollArea } from \"@petrarca/sonnet-ui\";\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n SheetDescription,\n SheetBody,\n} from \"@petrarca/sonnet-ui\";\nimport { ConfirmDialog } from \"./ConfirmDialog\";\nimport TopBar from \"./TopBar\";\nimport IconRail from \"./IconRail\";\nimport SubNavPanel from \"./SubNavPanel\";\nimport SidePane from \"./SidePane\";\nimport CommandMenu from \"./CommandMenu\";\nimport {\n initNavigation,\n initFeatureNav,\n initPanel,\n initDialog,\n initFullscreen,\n initSidePane,\n type PanelOptions,\n type ConfirmOptions,\n type FullscreenOptions,\n} from \"./api\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\nimport { ShellModulesContext } from \"./shellModules\";\nimport { useShellConfig } from \"./shellConfig\";\nimport { SidePaneContext, type SidePaneOptions } from \"./sidePaneState\";\nimport type { ServiceModule } from \"./types\";\n\n// Maps PanelOptions.width tokens to Tailwind width classes.\nconst PANEL_WIDTH_CLASS: Record<string, string> = {\n narrow: \"w-[480px]\",\n default: \"w-[640px]\",\n wide: \"w-[800px]\",\n half: \"w-[50vw]\",\n full: \"w-screen\",\n // legacy aliases\n lg: \"w-[640px]\",\n \"1/3\": \"w-[33vw]\",\n \"1/2\": \"w-[50vw]\",\n};\n\n// -- Helpers extracted to reduce AppShell complexity --\n\nfunction isSidePaneModule(service: {\n sidePane?: unknown;\n navigation: unknown[];\n}): boolean {\n return !!service.sidePane && service.navigation.length === 0;\n}\n\nfunction selectSidePaneModule(\n service: {\n id: string;\n basePath: string;\n routes: unknown[];\n sidePane?: {\n defaultWidth?: number;\n minWidth?: number;\n maxWidth?: number;\n contentFactory?: () => React.ReactNode;\n };\n },\n current: SidePaneOptions | null,\n setSidePaneState: (s: SidePaneOptions | null) => void,\n navigate: (path: string) => void,\n): void {\n if (current?.moduleId === service.id) {\n setSidePaneState(null);\n } else {\n setSidePaneState({\n moduleId: service.id,\n content: service.sidePane?.contentFactory?.() ?? null,\n defaultWidth: service.sidePane?.defaultWidth,\n minWidth: service.sidePane?.minWidth,\n maxWidth: service.sidePane?.maxWidth,\n });\n // Only navigate if the module has routes — pane-only modules\n // (routes: []) have no registered path, navigating would 404.\n if (service.routes.length > 0) {\n navigate(service.basePath);\n }\n }\n}\n\nfunction selectNavModule(\n service: {\n basePath: string;\n navigation: Array<{ links: Array<{ path: string }> }>;\n },\n navigate: (path: string) => void,\n setSubNavCollapsed: (v: boolean) => void,\n closeSidePane: () => void,\n): void {\n closeSidePane();\n setSubNavCollapsed(false);\n const target = service.navigation[0]?.links[0]?.path ?? service.basePath;\n navigate(target);\n}\n\n// Wire all imperative shell APIs once on mount.\nfunction useShellApiInit(deps: {\n navigate: ReturnType<typeof useNavigate>;\n openCommandMenu: () => void;\n handlePanelOpen: (opts: PanelOptions) => void;\n handlePanelClose: () => void;\n handleFullscreenEnter: (opts: FullscreenOptions) => void;\n handleFullscreenExit: () => void;\n handleSidePaneOpen: (opts: SidePaneOptions) => void;\n handleSidePaneClose: () => void;\n handleSidePaneIsOpen: () => boolean;\n handleSidePaneActiveModuleId: () => string | null;\n handleSidePaneSetFullWidth: (fullWidth: boolean) => void;\n setDialogState: React.Dispatch<React.SetStateAction<DialogState | null>>;\n featureLookup: (featureId: string) => string | null;\n}) {\n const stableRefs = useRef(deps);\n useEffect(() => {\n const r = stableRefs.current;\n initNavigation(r.navigate, r.openCommandMenu);\n initFeatureNav(r.featureLookup);\n initPanel(r.handlePanelOpen, r.handlePanelClose);\n initFullscreen(r.handleFullscreenEnter, r.handleFullscreenExit);\n initSidePane(\n r.handleSidePaneOpen,\n r.handleSidePaneClose,\n r.handleSidePaneIsOpen,\n r.handleSidePaneActiveModuleId,\n r.handleSidePaneSetFullWidth,\n );\n initDialog(\n (opts: ConfirmOptions) =>\n new Promise<boolean>((resolve) => r.setDialogState({ opts, resolve })),\n );\n }, []);\n}\n\ninterface AppShellProps {\n registry: ModuleRegistry;\n}\n\ninterface DialogState {\n opts: ConfirmOptions;\n resolve: (confirmed: boolean) => void;\n}\n\n/**\n * AppShell -- the main layout skeleton.\n *\n * Owns the panel (slide-over) -- modules request it via panel.open(),\n * the shell renders the provided content inside a Sheet.\n *\n * Receives its module registry as a prop so it has no dependency on\n * app-specific module lists.\n */\n// Hook: track which service is active based on URL.\nfunction useActiveService(modules: ModuleRegistry[\"modules\"]) {\n const location = useLocation();\n const serviceByBasePath = useMemo(\n () =>\n new Map(\n modules\n .filter((m) => !m.hidden && m.basePath !== \"/\")\n .map((m) => [m.basePath, m.id]),\n ),\n [modules],\n );\n const resolveServiceFromPath = useCallback(\n (pathname: string): string | null => {\n for (const [basePath, id] of serviceByBasePath) {\n if (pathname.startsWith(basePath)) return id;\n }\n return null;\n },\n [serviceByBasePath],\n );\n const [selectedServiceId, setSelectedServiceId] = useState<string | null>(\n () => resolveServiceFromPath(location.pathname),\n );\n useEffect(\n () => setSelectedServiceId(resolveServiceFromPath(location.pathname)),\n [location.pathname, resolveServiceFromPath],\n );\n const activeService = selectedServiceId\n ? (modules.find((s) => s.id === selectedServiceId) ?? null)\n : null;\n return { activeService, selectedServiceId, setSelectedServiceId };\n}\n\n// Hook: side pane state and handlers.\nfunction useSidePaneState() {\n const [sidePaneState, setSidePaneState] = useState<SidePaneOptions | null>(\n null,\n );\n const handleOpen = useCallback(\n (opts: SidePaneOptions) => setSidePaneState(opts),\n [],\n );\n const handleClose = useCallback(() => setSidePaneState(null), []);\n const handleIsOpen = useCallback(\n () => sidePaneState !== null,\n [sidePaneState],\n );\n const handleActiveModuleId = useCallback(\n () => sidePaneState?.moduleId ?? null,\n [sidePaneState],\n );\n const handleSetFullWidth = useCallback((fullWidth: boolean) => {\n setSidePaneState((prev) => (prev ? { ...prev, fullWidth } : prev));\n }, []);\n return {\n sidePaneState,\n setSidePaneState,\n handleOpen,\n handleClose,\n handleIsOpen,\n handleActiveModuleId,\n handleSetFullWidth,\n };\n}\n\n// Hook: slide-over panel state and handlers.\nfunction usePanelState() {\n const [panelState, setPanelState] = useState<PanelOptions | null>(null);\n const panelOnCloseRef = useRef<(() => void) | undefined>(undefined);\n const handleOpen = useCallback(\n (opts: PanelOptions) => setPanelState(opts),\n [],\n );\n const handleClose = useCallback(() => {\n panelOnCloseRef.current = panelState?.onClose;\n setPanelState(null);\n }, [panelState]);\n const panelWidth =\n PANEL_WIDTH_CLASS[panelState?.width ?? \"default\"] ??\n PANEL_WIDTH_CLASS.default;\n const panelOffsetTop = panelState?.coverage === \"full\" ? \"0px\" : \"3rem\";\n return {\n panelState,\n panelOnCloseRef,\n handleOpen,\n handleClose,\n panelWidth,\n panelOffsetTop,\n };\n}\n\n// Hook: confirm dialog state and handlers.\nfunction useDialogState() {\n const [dialogState, setDialogState] = useState<DialogState | null>(null);\n const handleConfirm = useCallback(() => {\n dialogState?.resolve(true);\n setDialogState(null);\n }, [dialogState]);\n const handleCancel = useCallback(() => {\n dialogState?.resolve(false);\n setDialogState(null);\n }, [dialogState]);\n return { dialogState, setDialogState, handleConfirm, handleCancel };\n}\n\n// Build a stable feature id -> path lookup from the module registry.\nfunction useFeatureLookup(modules: ModuleRegistry[\"modules\"]) {\n return useMemo(() => {\n const map = new Map<string, string>();\n for (const m of modules) {\n for (const group of m.navigation) {\n for (const link of group.links) {\n if (link.id) map.set(link.id, link.path);\n }\n }\n }\n return (featureId: string) => map.get(featureId) ?? null;\n }, [modules]);\n}\n\nexport default function AppShell({ registry }: AppShellProps) {\n const navigate = useNavigate();\n const config = useShellConfig();\n const { modules } = registry;\n\n const { activeService, setSelectedServiceId } = useActiveService(modules);\n const [subNavCollapsed, setSubNavCollapsed] = useState(false);\n const [commandMenuOpen, setCommandMenuOpen] = useState(false);\n const openCommandMenu = useCallback(() => setCommandMenuOpen(true), []);\n const [fullscreenState, setFullscreenState] =\n useState<FullscreenOptions | null>(null);\n const handleFullscreenEnter = useCallback(\n (opts: FullscreenOptions) => setFullscreenState(opts),\n [],\n );\n const handleFullscreenExit = useCallback(() => setFullscreenState(null), []);\n\n const sidePane = useSidePaneState();\n const panel = usePanelState();\n const dialog = useDialogState();\n const featureLookup = useFeatureLookup(modules);\n\n useShellApiInit({\n navigate,\n openCommandMenu,\n handlePanelOpen: panel.handleOpen,\n handlePanelClose: panel.handleClose,\n handleFullscreenEnter,\n handleFullscreenExit,\n handleSidePaneOpen: sidePane.handleOpen,\n handleSidePaneClose: sidePane.handleClose,\n handleSidePaneIsOpen: sidePane.handleIsOpen,\n handleSidePaneActiveModuleId: sidePane.handleActiveModuleId,\n handleSidePaneSetFullWidth: sidePane.handleSetFullWidth,\n setDialogState: dialog.setDialogState,\n featureLookup,\n });\n\n const handleServiceSelect = useCallback(\n (serviceId: string) => {\n const service = modules.find((s) => s.id === serviceId);\n if (!service) return;\n if (isSidePaneModule(service)) {\n selectSidePaneModule(\n service,\n sidePane.sidePaneState,\n sidePane.setSidePaneState,\n navigate,\n );\n // Don't change activeService for pane-only modules — the content\n // area still shows the previous route and needs its layout mode.\n // Collapse SubNavPanel by default so it doesn't linger from a nav module.\n // Consumers can opt out via sidePane.collapseSubNav: false.\n if (service.sidePane?.collapseSubNav !== false) {\n setSubNavCollapsed(true);\n }\n } else {\n selectNavModule(\n service,\n navigate,\n setSubNavCollapsed,\n sidePane.handleClose,\n );\n setSelectedServiceId(serviceId);\n }\n },\n [\n modules,\n sidePane.sidePaneState,\n sidePane.setSidePaneState,\n sidePane.handleClose,\n navigate,\n setSelectedServiceId,\n setSubNavCollapsed,\n ],\n );\n\n const sidePaneContextValue = useMemo(\n () => ({\n pane: sidePane.sidePaneState,\n open: sidePane.handleOpen,\n close: sidePane.handleClose,\n toggle: (opts: SidePaneOptions) => {\n if (sidePane.sidePaneState?.moduleId === opts.moduleId) {\n sidePane.setSidePaneState(null);\n } else {\n sidePane.setSidePaneState(opts);\n }\n },\n setFullWidth: sidePane.handleSetFullWidth,\n }),\n [sidePane],\n );\n\n return (\n <ShellModulesContext.Provider value={registry}>\n <SidePaneContext.Provider value={sidePaneContextValue}>\n <TooltipProvider>\n <div className=\"flex h-screen w-screen flex-col overflow-hidden\">\n {/* TopBar */}\n <TopBar>{config.topBar}</TopBar>\n\n {/* CommandMenu (Cmd+K) */}\n <CommandMenu\n open={commandMenuOpen}\n onOpenChange={setCommandMenuOpen}\n />\n\n {/* Body: IconRail + SubNavPanel + SidePane + Content Area */}\n <ShellBody\n activeService={activeService}\n sidePaneFullWidth={sidePane.sidePaneState?.fullWidth}\n sidePaneModuleId={sidePane.sidePaneState?.moduleId ?? null}\n subNavCollapsed={subNavCollapsed}\n onToggleSubNav={() => setSubNavCollapsed((c) => !c)}\n onServiceSelect={handleServiceSelect}\n />\n\n {/* Confirm Dialog */}\n {dialog.dialogState && (\n <ConfirmDialog\n open={true}\n title={dialog.dialogState.opts.title}\n description={dialog.dialogState.opts.description}\n confirmLabel={dialog.dialogState.opts.confirmLabel}\n cancelLabel={dialog.dialogState.opts.cancelLabel}\n variant={dialog.dialogState.opts.variant}\n onConfirm={dialog.handleConfirm}\n onCancel={dialog.handleCancel}\n />\n )}\n\n {/* Fullscreen overlay -- covers all shell chrome */}\n {fullscreenState && (\n <div className=\"fixed inset-0 z-50 bg-background overflow-auto\">\n {fullscreenState.content}\n </div>\n )}\n\n {/* Slide-over Panel */}\n <SlideOverPanel\n panelState={panel.panelState}\n panelWidth={panel.panelWidth}\n panelOffsetTop={panel.panelOffsetTop}\n panelOnCloseRef={panel.panelOnCloseRef}\n onClose={panel.handleClose}\n />\n </div>\n </TooltipProvider>\n </SidePaneContext.Provider>\n </ShellModulesContext.Provider>\n );\n}\n\n// -- Sub-components extracted to reduce AppShell complexity --\n\ninterface SlideOverPanelProps {\n panelState: PanelOptions | null;\n panelWidth: string;\n panelOffsetTop: string;\n panelOnCloseRef: React.MutableRefObject<(() => void) | undefined>;\n onClose: () => void;\n}\n\nfunction SlideOverPanel({\n panelState,\n panelWidth,\n panelOffsetTop,\n panelOnCloseRef,\n onClose,\n}: SlideOverPanelProps) {\n return (\n <Sheet\n open={panelState !== null}\n onOpenChange={(open) => {\n if (!open) onClose();\n }}\n >\n <SheetContent\n side=\"right\"\n className={`${panelWidth} max-w-[90vw]`}\n offsetTop={panelOffsetTop}\n onCloseAutoFocus={(e) => {\n if (panelOnCloseRef.current) {\n e.preventDefault();\n panelOnCloseRef.current();\n }\n }}\n {...(!panelState?.description && {\n \"aria-describedby\": undefined,\n })}\n >\n {panelState && <PanelContent state={panelState} />}\n </SheetContent>\n </Sheet>\n );\n}\n\nfunction ShellBody({\n activeService,\n sidePaneFullWidth,\n sidePaneModuleId,\n subNavCollapsed,\n onToggleSubNav,\n onServiceSelect,\n}: {\n activeService: ServiceModule | null;\n sidePaneFullWidth?: boolean;\n sidePaneModuleId: string | null;\n subNavCollapsed: boolean;\n onToggleSubNav: () => void;\n onServiceSelect: (id: string) => void;\n}) {\n return (\n <div className=\"flex flex-1 overflow-hidden\">\n <IconRail\n activeServiceId={activeService?.id ?? null}\n activeSidePaneModuleId={sidePaneModuleId}\n onServiceSelect={onServiceSelect}\n />\n\n {activeService && activeService.navigation.length > 0 && (\n <SubNavPanel\n service={activeService}\n collapsed={subNavCollapsed}\n onToggleCollapse={onToggleSubNav}\n />\n )}\n\n <SidePane />\n\n {sidePaneFullWidth !== true && (\n <div className=\"flex-1 min-w-0 h-full overflow-hidden\">\n <ContentArea layout={activeService?.layout} />\n </div>\n )}\n </div>\n );\n}\n\nfunction ContentArea({ layout }: { layout?: \"default\" | \"full\" }) {\n if (layout === \"full\") {\n return (\n <div className=\"h-full w-full overflow-hidden\">\n <Outlet />\n </div>\n );\n }\n return (\n <ScrollArea className=\"h-full\">\n <main className=\"p-6\">\n <Outlet />\n </main>\n </ScrollArea>\n );\n}\n\nfunction PanelContent({ state }: { state: PanelOptions }) {\n return (\n <>\n {state.title ? (\n <SheetHeader>\n <SheetTitle>{state.title}</SheetTitle>\n {state.description && (\n <SheetDescription>{state.description}</SheetDescription>\n )}\n </SheetHeader>\n ) : (\n <SheetTitle className=\"sr-only\">Panel</SheetTitle>\n )}\n <SheetBody>{state.content}</SheetBody>\n </>\n );\n}\n","/**\n * Confirm Dialog\n *\n * Reusable confirmation dialog for actions that require user confirmation.\n * Used for discard changes, delete actions, etc.\n *\n * Wired into the shell via `initDialog` in `shell/api.ts` so modules can\n * trigger it imperatively with `dialog.confirm(opts)`.\n *\n * @module components/Common/ConfirmDialog\n */\n\nimport React from \"react\";\nimport { AlertTriangle } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@petrarca/sonnet-ui\";\n\nexport interface ConfirmDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n\n /** Dialog title */\n title: string;\n\n /** Description shown below the title */\n description?: string;\n\n /** Confirm button label (default: \"Confirm\") */\n confirmLabel?: string;\n\n /** Cancel button label (default: \"Cancel\") */\n cancelLabel?: string;\n\n /**\n * Controls confirm button appearance.\n * \"destructive\" for irreversible actions (delete, revoke, etc.)\n */\n variant?: \"default\" | \"destructive\";\n\n /** Callback when user confirms */\n onConfirm: () => void;\n\n /** Callback when user cancels or closes dialog */\n onCancel: () => void;\n}\n\n/**\n * ConfirmDialog Component\n *\n * A reusable confirmation dialog backed by shadcn Dialog primitives.\n */\nexport function ConfirmDialog({\n open,\n title,\n description,\n confirmLabel = \"Confirm\",\n cancelLabel = \"Cancel\",\n variant = \"default\",\n onConfirm,\n onCancel,\n}: ConfirmDialogProps): React.ReactElement {\n return (\n <Dialog open={open} onOpenChange={(isOpen) => !isOpen && onCancel()}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>{title}</DialogTitle>\n {description && <DialogDescription>{description}</DialogDescription>}\n </DialogHeader>\n\n {variant === \"destructive\" && (\n <div className=\"flex gap-2 items-start\">\n <AlertTriangle\n size={20}\n className=\"text-destructive flex-shrink-0 mt-0.5\"\n />\n <p className=\"flex-1 text-sm text-muted-foreground\">\n This action cannot be undone.\n </p>\n </div>\n )}\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={onCancel}>\n {cancelLabel}\n </Button>\n <Button variant={variant} onClick={onConfirm}>\n {confirmLabel}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","import type { ReactNode } from \"react\";\n\n/**\n * TopBar -- persistent header across the entire application.\n *\n * A pure layout container. The shell provides the `<header>` element with\n * its sizing, background, and border. The consumer owns all content via\n * the `children` prop (composed through `ShellConfig.topBar`).\n */\ninterface TopBarProps {\n children?: ReactNode;\n}\n\nexport default function TopBar({ children }: TopBarProps) {\n return (\n <header className=\"h-12 shrink-0 border-b border-border flex items-center justify-between px-3 bg-background z-20\">\n {children}\n </header>\n );\n}\n","import { cn } from \"@petrarca/sonnet-core\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { Separator } from \"@petrarca/sonnet-ui\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@petrarca/sonnet-ui\";\nimport { useShellModules } from \"./shellModules\";\nimport type { ServiceModule } from \"./types\";\n\ninterface IconRailProps {\n activeServiceId: string | null;\n /** moduleId of the module whose side pane is currently open, or null. */\n activeSidePaneModuleId: string | null;\n onServiceSelect: (serviceId: string) => void;\n}\n\n/**\n * IconRail -- narrow vertical bar with service domain icons.\n *\n * Main services scroll in the center, settings/playground pinned at bottom.\n * Tooltips show service labels on hover.\n */\nexport default function IconRail({\n activeServiceId,\n activeSidePaneModuleId,\n onServiceSelect,\n}: IconRailProps) {\n const { mainModules, bottomModules } = useShellModules();\n\n const isActive = (id: string) =>\n activeServiceId === id || activeSidePaneModuleId === id;\n\n return (\n <nav className=\"w-14 shrink-0 border-r bg-muted flex flex-col items-center py-2 gap-1\">\n {/* Main service icons */}\n <div className=\"flex-1 flex flex-col items-center gap-1 overflow-y-auto\">\n {mainModules.map((service) => (\n <RailIcon\n key={service.id}\n service={service}\n isActive={isActive(service.id)}\n onClick={() => onServiceSelect(service.id)}\n />\n ))}\n </div>\n\n {/* Bottom-pinned icons */}\n <Separator className=\"w-6 my-1\" />\n <div className=\"flex flex-col items-center gap-1\">\n {bottomModules.map((service) => (\n <RailIcon\n key={service.id}\n service={service}\n isActive={isActive(service.id)}\n onClick={() => onServiceSelect(service.id)}\n />\n ))}\n </div>\n </nav>\n );\n}\n\n// Single rail icon button\n\ninterface RailIconProps {\n service: ServiceModule;\n isActive: boolean;\n onClick: () => void;\n}\n\nfunction RailIcon({ service, isActive, onClick }: RailIconProps) {\n const Icon = service.icon;\n\n return (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <Button\n variant=\"ghost\"\n size=\"compact\"\n onClick={onClick}\n className={cn(\n \"h-10 w-10 rounded-lg transition-colors\",\n isActive\n ? \"bg-accent text-accent-foreground\"\n : \"text-muted-foreground hover:text-foreground\",\n )}\n >\n <Icon className=\"h-5 w-5\" />\n <span className=\"sr-only\">{service.label}</span>\n </Button>\n </TooltipTrigger>\n <TooltipContent side=\"right\" sideOffset={8}>\n {service.label}\n </TooltipContent>\n </Tooltip>\n );\n}\n","/**\n * Shell modules context.\n *\n * Provides the module registry to shell components (IconRail,\n * CommandMenu, hooks) without requiring them to import from the\n * app-specific modules/registry.ts. AppShell injects the registry;\n * children consume it via useShellModules().\n */\nimport { createContext, useContext } from \"react\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\n\nexport const ShellModulesContext = createContext<ModuleRegistry | null>(null);\n\n/**\n * Read the module registry from context.\n * Must be called within an AppShell (which provides the context).\n */\nexport function useShellModules(): ModuleRegistry {\n const registry = useContext(ShellModulesContext);\n if (!registry) {\n throw new Error(\"useShellModules must be used within an AppShell\");\n }\n return registry;\n}\n","import { useMemo } from \"react\";\nimport { useLocation, Link } from \"react-router-dom\";\nimport { ChevronsLeft, ChevronsRight } from \"lucide-react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { ScrollArea } from \"@petrarca/sonnet-ui\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@petrarca/sonnet-ui\";\nimport type { ServiceModule, NavGroup } from \"./types\";\nimport { useExtensionPoint } from \"./hooks\";\n\ninterface SubNavPanelProps {\n service: ServiceModule;\n collapsed: boolean;\n onToggleCollapse: () => void;\n}\n\n/**\n * Merge contributed nav links from other modules.\n *\n * Contributions targeting \"<moduleId>.nav\" are appended as an additional\n * nav group after the module's own groups, sorted by contribution order.\n * The contribution mechanism is pre-sorted by createModuleRegistry.\n */\nfunction useMergedNavigation(service: ServiceModule): NavGroup[] {\n const contributions = useExtensionPoint(`${service.id}.nav`);\n return useMemo(() => {\n if (contributions.length === 0) return service.navigation;\n\n const contributed: NavGroup = {\n id: `${service.id}-contributed`,\n links: contributions.map((c) => ({\n id: c.extensionPoint + \".\" + c.label.toLowerCase().replace(/\\s+/g, \"-\"),\n label: c.label,\n path: c.path,\n })),\n };\n\n return [...service.navigation, contributed];\n }, [service, contributions]);\n}\n\n/**\n * SubNavPanel -- contextual secondary navigation for the selected service domain.\n *\n * Shows the service label at top, then grouped nav links. If other modules\n * contribute links via the \"<moduleId>.nav\" extension point, they are\n * appended as an additional group.\n */\nexport default function SubNavPanel({\n service,\n collapsed,\n onToggleCollapse,\n}: SubNavPanelProps) {\n const { pathname } = useLocation();\n const mergedNavigation = useMergedNavigation(service);\n\n if (collapsed) {\n return (\n <aside className=\"shrink-0 border-r bg-background flex flex-col items-center pt-2.5\">\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n className=\"text-muted-foreground/50 hover:text-muted-foreground transition-colors p-1\"\n onClick={onToggleCollapse}\n >\n <ChevronsRight className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">Expand navigation</TooltipContent>\n </Tooltip>\n </aside>\n );\n }\n\n return (\n <aside className=\"w-52 shrink-0 border-r bg-background flex flex-col\">\n {/* Service header with collapse toggle */}\n <div className=\"h-10 shrink-0 flex items-center justify-between px-4\">\n <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n {service.label}\n </span>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n className=\"text-muted-foreground/40 hover:text-muted-foreground transition-colors p-0.5\"\n onClick={onToggleCollapse}\n >\n <ChevronsLeft className=\"h-3.5 w-3.5\" />\n </button>\n </TooltipTrigger>\n <TooltipContent side=\"right\">Collapse navigation</TooltipContent>\n </Tooltip>\n </div>\n\n {/* Nav groups */}\n <ScrollArea className=\"flex-1\">\n <nav className=\"px-2 pb-4\">\n {mergedNavigation.map((group, gi) => (\n <div key={group.id} className={cn(gi > 0 && \"mt-4\")}>\n {/* heading-meta style: mono uppercase metadata label */}\n {group.heading && (\n <h6 className=\"text-xs font-medium font-mono uppercase tracking-wider text-muted-foreground px-2 mb-1\">\n {group.heading}\n </h6>\n )}\n <ul className=\"space-y-0.5\">\n {group.links.map((link) => {\n const isActive = pathname === link.path;\n return (\n <li key={link.path}>\n <Link\n to={link.path}\n className={cn(\n \"flex items-center rounded-md px-2 py-1.5 text-sm transition-colors\",\n isActive\n ? \"bg-accent text-accent-foreground font-medium\"\n : \"text-muted-foreground hover:text-foreground hover:bg-accent/50\",\n )}\n >\n {link.label}\n </Link>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </nav>\n </ScrollArea>\n </aside>\n );\n}\n","/**\n * Shell hooks -- reactive APIs for modules.\n *\n * Import path: @/shell/hooks\n */\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { events } from \"./api\";\nimport { useShellModules } from \"./shellModules\";\nimport type { Contribution, ServiceModule, ShellEventMap } from \"./types\";\n\n/**\n * Get sorted contributions for a named extension point.\n *\n * Returns all contributions registered by any module for the given\n * extension point, sorted by `order` (ascending).\n *\n * @example\n * ```tsx\n * const tabs = useExtensionPoint(\"organization-detail\");\n * ```\n */\nexport function useExtensionPoint(name: string): Contribution[] {\n const { getExtensionPoint } = useShellModules();\n return useMemo(() => getExtensionPoint(name), [getExtensionPoint, name]);\n}\n\n/**\n * Get the list of visible service modules (excludes hidden and bottom-pinned).\n *\n * Useful for building overviews like the home page.\n */\nexport function useMainModules(): ServiceModule[] {\n const { mainModules } = useShellModules();\n return mainModules;\n}\n\n/**\n * Subscribe to a shell event, auto-unsubscribing on unmount.\n *\n * The handler reference is stabilized via useRef so that the\n * subscription is not torn down and recreated on every render.\n *\n * @example\n * ```tsx\n * useShellEvent(\"schema-definition:changed\", (payload) => {\n * queryClient.invalidateQueries({ queryKey: [\"my-key\"] });\n * });\n * ```\n */\nexport function useShellEvent<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n): void {\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n return events.on(key, (payload) => handlerRef.current(payload));\n }, [key]);\n}\n","/**\n * Shell API -- imperative domain capabilities for modules.\n *\n * Import path: @/shell/api\n *\n * Grouped by domain: notification, dialog, navigation, panel, events.\n */\nimport type { ReactNode } from \"react\";\nimport { toast } from \"sonner\";\nimport { warnLog } from \"@petrarca/sonnet-core\";\nimport type { ShellEventMap } from \"./types\";\nimport * as eventBus from \"./eventBus\";\n\n// notification\n\ninterface PromiseOptions<T> {\n loading: string;\n success: string | ((data: T) => string);\n error: string | ((err: unknown) => string);\n}\n\nexport const notification = {\n success(message: string) {\n toast.success(message);\n },\n error(message: string) {\n toast.error(message);\n },\n warning(message: string) {\n toast.warning(message);\n },\n info(message: string) {\n toast.info(message);\n },\n promise<T>(promise: Promise<T>, opts: PromiseOptions<T>) {\n return toast.promise(promise, opts);\n },\n dismiss(id?: string | number) {\n toast.dismiss(id);\n },\n};\n\n// dialog\n\n// The shell mounts a single ConfirmDialog instance in AppShell and injects\n// its open function here via initDialog(). Modules call dialog.confirm()\n// without knowing where the implementation lives -- federation-safe, same\n// pattern as navigation and panel.\n\nexport interface ConfirmOptions {\n title: string;\n description?: string;\n confirmLabel?: string;\n cancelLabel?: string;\n variant?: \"default\" | \"destructive\";\n}\n\ntype DialogConfirmFn = (opts: ConfirmOptions) => Promise<boolean>;\n\nlet _dialogConfirm: DialogConfirmFn | null = null;\n\n/**\n * Called by the shell during initialization to inject the confirm dialog.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initDialog(confirmFn: DialogConfirmFn) {\n _dialogConfirm = confirmFn;\n}\n\nexport const dialog = {\n /**\n * Show a confirmation dialog. Returns true if confirmed, false if cancelled.\n */\n confirm(opts: ConfirmOptions): Promise<boolean> {\n if (_dialogConfirm) {\n return _dialogConfirm(opts);\n }\n warnLog(\"dialog.confirm: shell not initialized yet.\");\n return Promise.resolve(false);\n },\n};\n\n// navigation\n\n// The shell injects the navigate function and command menu opener at startup\n// via initNavigation(). Modules call navigation.goTo() etc. without knowing\n// where the implementation comes from -- works with both monorepo and\n// Module Federation (host provides the injector, remotes consume the API).\n\ntype NavigateFn = (path: string) => void;\ntype CommandMenuFn = () => void;\ntype FeatureLookupFn = (featureId: string) => string | null;\n\nlet _navigate: NavigateFn | null = null;\nlet _openCommandMenu: CommandMenuFn | null = null;\nlet _lookupFeature: FeatureLookupFn | null = null;\n\n/**\n * Called by the shell during initialization to inject router + command menu.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initNavigation(\n navigateFn: NavigateFn,\n openCommandMenuFn: CommandMenuFn,\n) {\n _navigate = navigateFn;\n _openCommandMenu = openCommandMenuFn;\n}\n\n/**\n * Called by the shell during initialization to inject the feature lookup.\n * Resolves a stable feature id (e.g. \"auditing.audit-events\") to a path.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initFeatureNav(lookupFn: FeatureLookupFn) {\n _lookupFeature = lookupFn;\n}\n\nexport const navigation = {\n /** Navigate to a path within the shell. */\n goTo(path: string) {\n if (_navigate) {\n _navigate(path);\n } else {\n warnLog(\"navigation.goTo: shell not initialized yet.\");\n }\n },\n /**\n * Navigate to a feature by its stable identifier (e.g. \"auditing.audit-events\").\n * The path is resolved from the module registry at call time, so it stays\n * correct even if routes are reorganised.\n */\n goToFeature(featureId: string) {\n if (!_navigate || !_lookupFeature) {\n warnLog(\"navigation.goToFeature: shell not initialized yet.\");\n return;\n }\n const path = _lookupFeature(featureId);\n if (path) {\n _navigate(path);\n } else {\n warnLog(\"navigation.goToFeature: unknown feature id '{}'.\", featureId);\n }\n },\n /** Open the Cmd+K command menu. */\n openCommandMenu() {\n if (_openCommandMenu) {\n _openCommandMenu();\n } else {\n warnLog(\"navigation.openCommandMenu: shell not initialized yet.\");\n }\n },\n /** Go back in browser history. */\n back() {\n window.history.back();\n },\n};\n\n// panel\n\n// The shell owns the slide-over panel (Sheet). Modules request it via\n// panel.open() with content to render. The shell renders that content inside\n// the panel. Same injection pattern as navigation -- federation-safe.\n\nexport interface PanelOptions {\n /**\n * Title shown in the panel header. When omitted the header is visually\n * hidden (the content owns its own header) but a screen-reader-only\n * title is still rendered for accessibility.\n */\n title?: string;\n /** React content to render inside the panel */\n content: ReactNode;\n /** Optional description below the title */\n description?: string;\n /**\n * Panel width token.\n *\n * - \"narrow\" -- 480px -- quick actions, simple single-field forms\n * - \"default\" -- 640px -- standard entity forms (default when omitted)\n * - \"wide\" -- 800px -- forms with embedded tables or complex layouts\n * - \"half\" -- 50vw -- split-screen inspection, side-by-side context\n * - \"full\" -- 100vw -- immersive editing, full-width canvases\n *\n * Legacy values \"lg\", \"1/3\", \"1/2\" are kept for backward compatibility\n * and map to \"default\" (640px), 33vw, and \"half\" (50vw) respectively.\n */\n width?:\n | \"narrow\"\n | \"default\"\n | \"wide\"\n | \"half\"\n | \"full\"\n | \"lg\"\n | \"1/3\"\n | \"1/2\";\n /**\n * Vertical coverage of the panel.\n * - \"below-header\" (default): panel starts below the TopBar, keeping it visible.\n * - \"full\": panel covers the full viewport height including the TopBar.\n */\n coverage?: \"below-header\" | \"full\";\n /** Called after the panel finishes closing. Use to restore focus to the triggering element. */\n onClose?: () => void;\n}\n\ntype PanelOpenFn = (opts: PanelOptions) => void;\ntype PanelCloseFn = () => void;\n\nlet _panelOpen: PanelOpenFn | null = null;\nlet _panelClose: PanelCloseFn | null = null;\n\n/**\n * Called by the shell during initialization to inject panel controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initPanel(openFn: PanelOpenFn, closeFn: PanelCloseFn) {\n _panelOpen = openFn;\n _panelClose = closeFn;\n}\n\nexport const panel = {\n /** Open the shell panel with content rendered inside. */\n open(opts: PanelOptions) {\n if (_panelOpen) {\n _panelOpen(opts);\n } else {\n warnLog(\"panel.open: shell not initialized yet.\");\n }\n },\n /** Close the panel. */\n close() {\n if (_panelClose) {\n _panelClose();\n } else {\n warnLog(\"panel.close: shell not initialized yet.\");\n }\n },\n};\n\n// sidePane\n\n// The shell owns the side pane (SidePane component in AppShell). Modules\n// request it via sidePane.open() with content and sizing options. The pane\n// sits inline beside the content area -- the main view shrinks rather than\n// being overlaid. Same injection pattern as panel -- federation-safe.\n\nimport type { SidePaneOptions } from \"./sidePaneState\";\nexport type { SidePaneOptions };\n\ntype SidePaneOpenFn = (opts: SidePaneOptions) => void;\ntype SidePaneCloseFn = () => void;\ntype SidePaneIsOpenFn = () => boolean;\ntype SidePaneActiveModuleIdFn = () => string | null;\ntype SidePaneSetFullWidthFn = (fullWidth: boolean) => void;\n\nlet _sidePaneOpen: SidePaneOpenFn | null = null;\nlet _sidePaneClose: SidePaneCloseFn | null = null;\nlet _sidePaneIsOpen: SidePaneIsOpenFn | null = null;\nlet _sidePaneActiveModuleId: SidePaneActiveModuleIdFn | null = null;\nlet _sidePaneSetFullWidth: SidePaneSetFullWidthFn | null = null;\n\n/**\n * Called by the shell during initialization to inject side pane controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initSidePane(\n openFn: SidePaneOpenFn,\n closeFn: SidePaneCloseFn,\n isOpenFn: SidePaneIsOpenFn,\n activeModuleIdFn: SidePaneActiveModuleIdFn,\n setFullWidthFn?: SidePaneSetFullWidthFn,\n) {\n _sidePaneOpen = openFn;\n _sidePaneClose = closeFn;\n _sidePaneIsOpen = isOpenFn;\n _sidePaneActiveModuleId = activeModuleIdFn;\n _sidePaneSetFullWidth = setFullWidthFn ?? null;\n}\n\nexport const sidePane = {\n /** Open the side pane with the given content and sizing options. */\n open(opts: SidePaneOptions) {\n if (_sidePaneOpen) {\n _sidePaneOpen(opts);\n } else {\n warnLog(\"sidePane.open: shell not initialized yet.\");\n }\n },\n /** Close the side pane. */\n close() {\n if (_sidePaneClose) {\n _sidePaneClose();\n } else {\n warnLog(\"sidePane.close: shell not initialized yet.\");\n }\n },\n /**\n * Toggle the side pane.\n *\n * If the pane is closed, opens it with opts.\n * If the pane is open with the same moduleId, closes it.\n * If the pane is open with a different moduleId, replaces it with opts.\n */\n toggle(opts: SidePaneOptions) {\n if (\n !_sidePaneIsOpen ||\n !_sidePaneActiveModuleId ||\n !_sidePaneOpen ||\n !_sidePaneClose\n ) {\n warnLog(\"sidePane.toggle: shell not initialized yet.\");\n return;\n }\n if (_sidePaneIsOpen() && _sidePaneActiveModuleId() === opts.moduleId) {\n _sidePaneClose();\n } else {\n _sidePaneOpen(opts);\n }\n },\n /** Returns true when the pane is currently open. */\n isOpen(): boolean {\n return _sidePaneIsOpen ? _sidePaneIsOpen() : false;\n },\n /** Returns the moduleId of the currently open pane, or null. */\n activeModuleId(): string | null {\n return _sidePaneActiveModuleId ? _sidePaneActiveModuleId() : null;\n },\n /**\n * Expand or collapse the pane to fill the full content area.\n *\n * When true the pane takes flex-1 and hides the main route content.\n * Use for full-screen tool views (schema viewer, form editors).\n * Call setFullWidth(false) to restore normal inline sizing.\n */\n setFullWidth(fullWidth: boolean) {\n if (_sidePaneSetFullWidth) {\n _sidePaneSetFullWidth(fullWidth);\n } else {\n warnLog(\"sidePane.setFullWidth: shell not initialized yet.\");\n }\n },\n};\n\n// fullscreen\n\n// The shell owns the fullscreen overlay. Modules request it via\n// fullscreen.enter(content) and exit via fullscreen.exit(). The shell renders\n// the content in a fixed inset-0 z-50 overlay, covering all shell chrome.\n// Same injection pattern as panel -- federation-safe.\n\nexport interface FullscreenOptions {\n /** React content to render inside the fullscreen overlay. */\n content: ReactNode;\n}\n\ntype FullscreenEnterFn = (opts: FullscreenOptions) => void;\ntype FullscreenExitFn = () => void;\n\nlet _fullscreenEnter: FullscreenEnterFn | null = null;\nlet _fullscreenExit: FullscreenExitFn | null = null;\n\n/**\n * Called by the shell during initialization to inject fullscreen controls.\n * Not part of the public module API -- only the shell calls this.\n */\nexport function initFullscreen(\n enterFn: FullscreenEnterFn,\n exitFn: FullscreenExitFn,\n) {\n _fullscreenEnter = enterFn;\n _fullscreenExit = exitFn;\n}\n\nexport const fullscreen = {\n /** Enter fullscreen mode, rendering content over all shell chrome. */\n enter(opts: FullscreenOptions) {\n if (_fullscreenEnter) {\n _fullscreenEnter(opts);\n } else {\n warnLog(\"fullscreen.enter: shell not initialized yet.\");\n }\n },\n /** Exit fullscreen mode, restoring the normal shell layout. */\n exit() {\n if (_fullscreenExit) {\n _fullscreenExit();\n } else {\n warnLog(\"fullscreen.exit: shell not initialized yet.\");\n }\n },\n};\n\n// events\n\ntype Unsubscribe = () => void;\n\nexport const events = {\n /** Subscribe to an event. Returns an unsubscribe function. */\n on<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n ): Unsubscribe {\n return eventBus.on(key as string, handler as (payload: unknown) => void);\n },\n\n /** Subscribe, auto-unsubscribing after the first emission. */\n once<K extends keyof ShellEventMap>(\n key: K,\n handler: (payload: ShellEventMap[K]) => void,\n ): Unsubscribe {\n return eventBus.once(key as string, handler as (payload: unknown) => void);\n },\n\n /** Emit an event. Handlers run asynchronously (microtask). */\n emit<K extends keyof ShellEventMap>(key: K, payload: ShellEventMap[K]): void {\n eventBus.emit(key as string, payload);\n },\n};\n","/**\n * Shell event bus -- in-memory pub/sub for cross-module communication.\n *\n * The bus is untyped internally (operates on strings and unknown payloads).\n * Type safety is provided by the public API layer in shell/api.ts, which\n * constrains keys and payloads via the ShellEventMap interface.\n *\n * Mirrors the server-side EventBus pattern (fire-and-forget, error isolation)\n * adapted for the browser's single-threaded model.\n */\nimport { devLog, warnLog } from \"@petrarca/sonnet-core\";\n\ntype Handler = (payload: unknown) => void;\ntype Unsubscribe = () => void;\n\nconst listeners = new Map<string, Set<Handler>>();\n\n/** Subscribe to an event key. Returns an unsubscribe function. */\nexport function on(key: string, handler: Handler): Unsubscribe {\n let set = listeners.get(key);\n if (!set) {\n set = new Set();\n listeners.set(key, set);\n }\n set.add(handler);\n devLog(`[shell:event] on \"${key}\" (${set.size} handler(s))`);\n return () => {\n set!.delete(handler);\n if (set!.size === 0) listeners.delete(key);\n devLog(`[shell:event] off \"${key}\" (${set!.size} handler(s))`);\n };\n}\n\n/** Subscribe to an event key, auto-unsubscribing after the first emission. */\nexport function once(key: string, handler: Handler): Unsubscribe {\n const unsubscribe = on(key, (payload) => {\n unsubscribe();\n handler(payload);\n });\n return unsubscribe;\n}\n\n/** Emit an event. Handlers run in microtasks (non-blocking, error-isolated). */\nexport function emit(key: string, payload: unknown): void {\n const set = listeners.get(key);\n const count = set?.size ?? 0;\n devLog(`[shell:event] emit \"${key}\"`, payload, `(${count} handler(s))`);\n if (!set || count === 0) return;\n for (const handler of set) {\n Promise.resolve().then(() => {\n try {\n handler(payload);\n } catch (err) {\n warnLog(`[shell:event] handler error \"${key}\":`, err);\n }\n });\n }\n}\n","/**\n * SidePane -- persistent resizable inline panel.\n *\n * Sits between SubNavPanel and the main content area in the AppShell flex row.\n * When open it takes a fixed pixel width; the content area (flex-1) shrinks to\n * fill the remainder. The main view remains fully visible and interactive.\n *\n * Opened/closed imperatively via sidePane.open() / sidePane.close() / sidePane.toggle()\n * from api.ts. State is provided by SidePaneContext (owned by AppShell).\n */\nimport type React from \"react\";\nimport { X } from \"lucide-react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { useResizablePanel } from \"@petrarca/sonnet-core/hooks\";\nimport { useSidePaneState } from \"./sidePaneState\";\n\nfunction DragHandle({\n separatorProps,\n onPointerDown,\n onDoubleClick,\n}: {\n separatorProps: React.HTMLAttributes<HTMLDivElement>;\n onPointerDown: React.PointerEventHandler<HTMLDivElement>;\n onDoubleClick: React.MouseEventHandler<HTMLDivElement>;\n}) {\n return (\n <div\n {...separatorProps}\n onPointerDown={onPointerDown}\n onDoubleClick={onDoubleClick}\n className={cn(\n \"absolute right-0 top-0 h-full w-1 cursor-col-resize\",\n \"hover:bg-primary/40 active:bg-primary/60 transition-colors\",\n )}\n />\n );\n}\n\nconst DEFAULT_WIDTH = 320;\nconst DEFAULT_MIN = 240;\nconst DEFAULT_MAX = 800;\n\n/**\n * SidePane renders nothing when no pane is open.\n * When open it renders a bordered panel with a drag handle on its right edge.\n * Double-clicking the handle resets to defaultWidth.\n */\nexport default function SidePane() {\n const { pane, close } = useSidePaneState();\n if (!pane) return null;\n return <SidePaneOpen pane={pane} onClose={close} />;\n}\n\nfunction SidePaneOpen({\n pane,\n onClose,\n}: {\n pane: NonNullable<ReturnType<typeof useSidePaneState>[\"pane\"]>;\n onClose: () => void;\n}) {\n const { panelWidth, handlePointerDown, handleDoubleClick, separatorProps } =\n useResizablePanel({\n defaultWidth: pane.defaultWidth ?? DEFAULT_WIDTH,\n minWidth: pane.minWidth ?? DEFAULT_MIN,\n maxWidth: pane.maxWidth ?? DEFAULT_MAX,\n direction: \"right-edge\",\n });\n\n const isFullWidth = pane.fullWidth === true;\n\n return (\n <div\n className={cn(\n \"relative flex flex-col border-r bg-background\",\n isFullWidth ? \"flex-1\" : \"shrink-0\",\n )}\n style={isFullWidth ? undefined : { width: panelWidth }}\n >\n {/* Header */}\n <div className=\"flex h-10 shrink-0 items-center justify-end border-b px-2\">\n <button\n onClick={onClose}\n className={cn(\n \"flex h-6 w-6 items-center justify-center rounded text-muted-foreground\",\n \"hover:bg-accent hover:text-foreground transition-colors\",\n \"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\",\n )}\n aria-label=\"Close side pane\"\n >\n <X className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-auto\">{pane.content}</div>\n\n {!isFullWidth && (\n <DragHandle\n separatorProps={separatorProps}\n onPointerDown={handlePointerDown}\n onDoubleClick={handleDoubleClick}\n />\n )}\n </div>\n );\n}\n","/**\n * Side pane state types and context.\n *\n * The side pane is a persistent, resizable inline panel that sits between\n * the SubNavPanel and the main content area. It shrinks the content area\n * rather than overlaying it -- the main view stays visible and interactive.\n *\n * Modules open/close/toggle it via the imperative sidePane API in api.ts.\n * AppShell owns the state and injects it here via SidePaneContext so that\n * SidePane.tsx can read it without coupling to AppShell directly.\n */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\nexport interface SidePaneOptions {\n /** Content to render inside the side pane. */\n content: ReactNode;\n /**\n * Stable key identifying which module/tool owns this pane instance.\n * Used by IconRail to highlight the active pane module and by toggle()\n * to detect whether the same content is already open.\n */\n moduleId: string;\n /** Initial width in pixels. Defaults to 320. */\n defaultWidth?: number;\n /** Minimum draggable width in pixels. Defaults to 240. */\n minWidth?: number;\n /**\n * Maximum draggable width in pixels.\n * Omit or pass Infinity for unlimited (fills remaining space up to viewport).\n */\n maxWidth?: number;\n /**\n * When true, the pane expands to fill the entire content area (flex-1),\n * collapsing the main route content to zero width. Use for full-screen\n * tool views (schema viewer, form editors). The drag handle is hidden.\n * Toggle via sidePane.setFullWidth(boolean) from inside the pane content.\n */\n fullWidth?: boolean;\n}\n\nexport interface SidePaneState {\n /** Currently open pane options, or null when closed. */\n pane: SidePaneOptions | null;\n open: (opts: SidePaneOptions) => void;\n close: () => void;\n toggle: (opts: SidePaneOptions) => void;\n /** Expand or collapse the pane to fill the full content area. */\n setFullWidth: (fullWidth: boolean) => void;\n}\n\nexport const SidePaneContext = createContext<SidePaneState | null>(null);\n\n/**\n * Read the side pane state from context.\n * Must be called within an AppShell (which provides the context).\n */\nexport function useSidePaneState(): SidePaneState {\n const ctx = useContext(SidePaneContext);\n if (!ctx) {\n throw new Error(\"useSidePaneState must be used within an AppShell\");\n }\n return ctx;\n}\n","import { useEffect, useCallback } from \"react\";\nimport { useNavigate } from \"react-router-dom\";\nimport {\n CommandDialog,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n} from \"@petrarca/sonnet-ui\";\nimport { useShellModules } from \"./shellModules\";\nimport type { ServiceModule } from \"./types\";\n\ninterface CommandMenuProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\n/**\n * CommandMenu -- Cmd+K command menu.\n *\n * Indexes all navigation items from the service domains so users can\n * quickly jump to any page by typing.\n */\nexport default function CommandMenu({ open, onOpenChange }: CommandMenuProps) {\n const navigate = useNavigate();\n const { mainModules, bottomModules } = useShellModules();\n\n // Global Cmd+K / Ctrl+K shortcut\n useEffect(() => {\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"k\" && (e.metaKey || e.ctrlKey)) {\n e.preventDefault();\n onOpenChange(!open);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [open, onOpenChange]);\n\n const handleSelect = useCallback(\n (path: string) => {\n navigate(path);\n onOpenChange(false);\n },\n [navigate, onOpenChange],\n );\n\n const renderNavGroup = (service: ServiceModule) => {\n if (service.navigation.length === 0) return null;\n const Icon = service.icon;\n return (\n <CommandGroup key={`nav-${service.id}`} heading={service.label}>\n {service.navigation.flatMap((group) =>\n group.links.map((link) => (\n <CommandItem\n key={link.path}\n value={`${service.label} ${group.heading ?? \"\"} ${link.label}`}\n onSelect={() => handleSelect(link.path)}\n >\n <Icon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n <span>{link.label}</span>\n {group.heading && (\n <span className=\"ml-auto text-xs text-muted-foreground\">\n {group.heading}\n </span>\n )}\n </CommandItem>\n )),\n )}\n </CommandGroup>\n );\n };\n\n const renderCommandGroup = (service: ServiceModule) => {\n if (!service.commands?.length) return null;\n const Icon = service.icon;\n return (\n <CommandGroup\n key={`cmd-${service.id}`}\n heading={service.commands[0].group ?? service.label}\n >\n {service.commands.map((cmd) => {\n const CmdIcon = cmd.icon ?? Icon;\n return (\n <CommandItem\n key={cmd.id}\n value={`${service.label} ${cmd.label}`}\n onSelect={() => {\n cmd.action();\n onOpenChange(false);\n }}\n >\n <CmdIcon className=\"mr-2 h-4 w-4 text-muted-foreground\" />\n <span>{cmd.label}</span>\n </CommandItem>\n );\n })}\n </CommandGroup>\n );\n };\n\n const allModules = [...mainModules, ...bottomModules];\n\n return (\n <CommandDialog open={open} onOpenChange={onOpenChange}>\n <CommandInput placeholder=\"Search pages, services, actions...\" />\n <CommandList>\n <CommandEmpty>No results found.</CommandEmpty>\n\n {mainModules.map(renderNavGroup)}\n\n <CommandSeparator />\n\n {bottomModules.map(renderNavGroup)}\n\n {allModules.some((m) => m.commands?.length) && (\n <>\n <CommandSeparator />\n {allModules.map(renderCommandGroup)}\n </>\n )}\n </CommandList>\n </CommandDialog>\n );\n}\n","/**\n * Shell configuration types and context.\n *\n * ShellConfig is intentionally minimal. The shell provides layout chrome\n * (TopBar header, IconRail, SubNavPanel, CommandMenu) but does not own\n * any app-specific content. The consumer composes the TopBar content --\n * logo, environment selector, search trigger, user menu -- via the\n * `topBar` ReactNode slot using building blocks exported from the shell.\n */\nimport { createContext, useContext, type ReactNode } from \"react\";\n\n// Top-level shell configuration.\n\nexport interface ShellConfig {\n /**\n * Content rendered inside the TopBar header. The shell provides the\n * `<header>` element with its sizing and border; the consumer owns\n * everything inside it.\n *\n * Compose this using building blocks like `SearchTrigger` and\n * `UserMenu` (exported from the shell) alongside any app-specific\n * components.\n */\n topBar?: ReactNode;\n}\n\n// Context\n\nexport const ShellConfigContext = createContext<ShellConfig | null>(null);\n\n/**\n * Read the shell configuration from context.\n * Must be called within a ShellConfigProvider.\n */\nexport function useShellConfig(): ShellConfig {\n const config = useContext(ShellConfigContext);\n if (!config) {\n throw new Error(\"useShellConfig must be used within a ShellConfigProvider\");\n }\n return config;\n}\n","import { Toaster } from \"sonner\";\nimport AppShell from \"./AppShell\";\nimport { ShellConfigProvider } from \"./ShellConfigProvider\";\nimport type { ShellConfig } from \"./shellConfig\";\nimport type { ModuleRegistry } from \"./createModuleRegistry\";\n\n/**\n * RootLayout -- top-level layout that wraps the entire authenticated app.\n * Renders the Toaster for notifications and the AppShell (icon rail + sub-nav + content).\n */\n\ninterface RootLayoutProps {\n config: ShellConfig;\n registry: ModuleRegistry;\n}\n\nexport default function RootLayout({ config, registry }: RootLayoutProps) {\n return (\n <ShellConfigProvider config={config}>\n <Toaster position=\"top-right\" richColors closeButton />\n <AppShell registry={registry} />\n </ShellConfigProvider>\n );\n}\n","/**\n * ShellConfigProvider -- wraps the shell with configuration context.\n */\nimport type { ReactNode } from \"react\";\nimport { ShellConfigContext, type ShellConfig } from \"./shellConfig\";\n\ninterface ShellConfigProviderProps {\n config: ShellConfig;\n children: ReactNode;\n}\n\nexport function ShellConfigProvider({\n config,\n children,\n}: ShellConfigProviderProps) {\n return (\n <ShellConfigContext.Provider value={config}>\n {children}\n </ShellConfigContext.Provider>\n );\n}\n","import { useLocation } from \"react-router-dom\";\n\n/**\n * PlaceholderPage — generic wireframe page for routes not yet implemented.\n * Shows the current path and a placeholder content area.\n */\nexport default function PlaceholderPage() {\n const { pathname } = useLocation();\n\n return (\n <div>\n <h1 className=\"mb-1\">{pathToTitle(pathname)}</h1>\n <p className=\"text-sm text-muted-foreground mb-6\">{pathname}</p>\n\n {/* Wireframe content placeholder */}\n <div className=\"rounded-lg border-2 border-dashed border-border p-12 flex items-center justify-center\">\n <span className=\"text-sm text-muted-foreground\">\n Content area — not yet implemented\n </span>\n </div>\n </div>\n );\n}\n\n/** Convert a path like \"/healthcare/patients\" to \"Patients\" */\nfunction pathToTitle(path: string): string {\n const last = path.split(\"/\").filter(Boolean).pop();\n if (!last) return \"Dashboard\";\n return last\n .split(\"-\")\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(\" \");\n}\n","import { Search } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { navigation } from \"./api\";\n\n/**\n * SearchTrigger -- Cmd+K search button for the TopBar.\n *\n * Opens the shell CommandMenu via the imperative navigation API.\n * Generic building block, no app-specific dependencies.\n */\nexport function SearchTrigger() {\n return (\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 gap-2 text-sm text-muted-foreground w-56 justify-start\"\n onClick={() => navigation.openCommandMenu()}\n >\n <Search className=\"h-4 w-4\" />\n <span>Search...</span>\n <kbd className=\"ml-auto pointer-events-none inline-flex h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground\">\n <span className=\"text-xs\">&#x2318;</span>K\n </kbd>\n </Button>\n );\n}\n","import { LogOut, User } from \"lucide-react\";\nimport { Button } from \"@petrarca/sonnet-ui\";\nimport { Avatar, AvatarFallback } from \"@petrarca/sonnet-ui\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@petrarca/sonnet-ui\";\n\n/**\n * UserMenu -- avatar dropdown for the TopBar.\n *\n * Shows the user's initials in an avatar. The dropdown displays name,\n * email, a profile link, and a sign-out action. Generic building block,\n * no app-specific dependencies.\n */\n\nexport interface UserMenuUser {\n name: string;\n email: string;\n initials: string;\n}\n\ninterface UserMenuProps {\n user: UserMenuUser;\n onSignOut?: () => void;\n}\n\nexport function UserMenu({ user, onSignOut }: UserMenuProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"compact\" className=\"rounded-full\">\n <Avatar className=\"h-7 w-7\">\n <AvatarFallback className=\"text-xs\">{user.initials}</AvatarFallback>\n </Avatar>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-48\">\n <DropdownMenuLabel className=\"font-normal\">\n <div className=\"flex flex-col gap-1\">\n <p className=\"text-sm font-medium\">{user.name}</p>\n <p className=\"text-xs text-muted-foreground\">{user.email}</p>\n </div>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <DropdownMenuItem>\n <User className=\"mr-2 h-4 w-4\" />\n Profile\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={onSignOut}>\n <LogOut className=\"mr-2 h-4 w-4\" />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","/**\n * Module registry factory.\n *\n * Accepts a list of ServiceModules and returns derived data structures\n * consumed by the shell (icon rail groups, flattened routes, extension\n * point lookup). This is the reusable core; the app-specific module\n * list lives in the consumer (e.g. modules/registry.ts).\n */\nimport type { RouteObject } from \"react-router-dom\";\nimport type { ServiceModule, Contribution } from \"./types\";\n\nexport interface ModuleRegistry {\n /** All modules in registration order. */\n modules: ServiceModule[];\n\n /** Visible modules shown in the main area of the icon rail. */\n mainModules: ServiceModule[];\n\n /** Modules pinned to the bottom of the icon rail. */\n bottomModules: ServiceModule[];\n\n /** All routes from all modules, flattened. */\n allRoutes: RouteObject[];\n\n /** Get contributions for a named extension point, sorted by order. */\n getExtensionPoint: (name: string) => Contribution[];\n}\n\nexport function createModuleRegistry(modules: ServiceModule[]): ModuleRegistry {\n const visible = modules.filter((m) => !m.hidden);\n const mainModules = visible.filter((m) => !m.pinBottom);\n const bottomModules = visible.filter((m) => m.pinBottom);\n const allRoutes = modules.flatMap((m) => m.routes);\n\n // Pre-collect all contributions for O(n) lookup per call.\n const contributionsByPoint = new Map<string, Contribution[]>();\n for (const m of modules) {\n for (const c of m.contributions ?? []) {\n const list = contributionsByPoint.get(c.extensionPoint) ?? [];\n list.push(c);\n contributionsByPoint.set(c.extensionPoint, list);\n }\n }\n // Sort each group once.\n for (const list of contributionsByPoint.values()) {\n list.sort((a, b) => a.order - b.order);\n }\n\n function getExtensionPoint(name: string): Contribution[] {\n return contributionsByPoint.get(name) ?? [];\n }\n\n return { modules, mainModules, bottomModules, allRoutes, getExtensionPoint };\n}\n","/**\n * OverviewCard and FeatureLink -- building blocks for module overview pages.\n *\n * OverviewCard presents a feature with icon, title, description, and optional\n * navigation via a stable feature id.\n *\n * FeatureLink is an inline text link for use in descriptions or prose,\n * also navigating via stable feature id.\n *\n * Part of the shell -- extracts to @petrarca/sonnet-shell.\n */\n\nimport React from \"react\";\nimport { cn } from \"@petrarca/sonnet-core\";\nimport { navigation } from \"./api\";\n\ninterface OverviewCardProps {\n /** Lucide icon component. */\n icon: React.ComponentType<{ className?: string }>;\n /** Card title. */\n title: string;\n /** Description -- plain string or React nodes (e.g. with FeatureLink). */\n description: React.ReactNode;\n /**\n * Stable feature identifier (e.g. \"auditing.audit-events\").\n * Resolved to a path via the shell registry at click time.\n * Omit for non-navigable cards.\n */\n feature?: string;\n /** Additional CSS class names. */\n className?: string;\n}\n\n/**\n * FeatureLink -- inline text link navigating to a feature by stable id.\n *\n * Use inside OverviewCard descriptions or overview page prose to link\n * to a feature without hardcoding paths.\n *\n * @example\n * <FeatureLink feature=\"auditing.audit-log\">Audit Log</FeatureLink>\n */\nexport function FeatureLink({\n feature,\n children,\n}: {\n feature: string;\n children: React.ReactNode;\n}): React.ReactElement {\n return (\n <span\n role=\"link\"\n tabIndex={0}\n onClick={(e) => {\n e.stopPropagation();\n navigation.goToFeature(feature);\n }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n e.stopPropagation();\n navigation.goToFeature(feature);\n }\n }}\n className=\"underline underline-offset-2 hover:text-foreground transition-colors cursor-pointer\"\n >\n {children}\n </span>\n );\n}\n\n/** Feature card for module overview pages. Navigates via the shell API. */\nexport function OverviewCard({\n icon: Icon,\n title,\n description,\n feature,\n className,\n}: OverviewCardProps): React.ReactElement {\n return (\n <button\n onClick={feature ? () => navigation.goToFeature(feature) : undefined}\n disabled={!feature}\n className={cn(\n \"rounded-lg border bg-card p-5 text-left flex flex-col gap-3\",\n \"disabled:cursor-default\",\n feature && \"hover:bg-accent/50 transition-colors\",\n className,\n )}\n >\n <div className=\"h-9 w-9 rounded-md bg-blue-50 flex items-center justify-center shrink-0\">\n <Icon className=\"h-5 w-5 text-blue-600\" />\n </div>\n <div>\n <p className=\"text-sm font-medium mb-1\">{title}</p>\n <p className=\"text-xs text-muted-foreground leading-relaxed\">\n {description}\n </p>\n </div>\n </button>\n );\n}\n"],"mappings":"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","names":["useEffect","useCallback","useRef","useMemo","useLocation","useNavigate","ScrollArea","jsx","Button","jsx","jsxs","Button","useMemo","cn","Tooltip","TooltipContent","TooltipTrigger","warnLog","warnLog","jsx","jsxs","useMemo","Tooltip","TooltipTrigger","TooltipContent","cn","cn","createContext","useContext","jsx","jsxs","cn","useEffect","jsx","jsxs","useEffect","createContext","useContext","Fragment","jsx","jsxs","useRef","useEffect","useLocation","useMemo","useCallback","useSidePaneState","useNavigate","sidePane","panel","dialog","ScrollArea","jsx","jsx","jsxs","useLocation","jsx","jsxs","Button","jsx","jsxs","Button","Button","jsx","jsxs","cn","jsx","jsxs","cn"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@petrarca/sonnet-shell",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Application shell, layout, navigation, auth flow, and imperative API for the Petrarca Sonnet component library",
5
5
  "license": "Apache-2.0",
6
6
  "publishConfig": {
@@ -25,12 +25,12 @@
25
25
  "dependencies": {
26
26
  "lucide-react": "^0.553.0",
27
27
  "sonner": "^2.0.7",
28
- "@petrarca/sonnet-core": "0.1.4",
29
- "@petrarca/sonnet-ui": "0.1.4"
28
+ "@petrarca/sonnet-core": "0.1.6",
29
+ "@petrarca/sonnet-ui": "0.1.6"
30
30
  },
31
31
  "peerDependencies": {
32
- "react": ">=18",
33
- "react-dom": ">=18",
32
+ "react": ">=19",
33
+ "react-dom": ">=19",
34
34
  "react-router-dom": ">=7",
35
35
  "tailwindcss": ">=3"
36
36
  },