efficient-shell 0.0.0 → 4.126.2

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.
Files changed (121) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +752 -4
  3. package/dist/Browser-DBRSIQ4J.js +478 -0
  4. package/dist/Browser-DBRSIQ4J.js.map +1 -0
  5. package/dist/Calculator-HCKFTSJR.js +188 -0
  6. package/dist/Calculator-HCKFTSJR.js.map +1 -0
  7. package/dist/ConfirmDialog-LJXMB45A.js +3 -0
  8. package/dist/ConfirmDialog-LJXMB45A.js.map +1 -0
  9. package/dist/CurrencyConverter-BKKORN6K.js +232 -0
  10. package/dist/CurrencyConverter-BKKORN6K.js.map +1 -0
  11. package/dist/Documents-5MD5SKPB.js +437 -0
  12. package/dist/Documents-5MD5SKPB.js.map +1 -0
  13. package/dist/Files-KY35DPWB.js +801 -0
  14. package/dist/Files-KY35DPWB.js.map +1 -0
  15. package/dist/Notepad-2BO5VEUG.js +406 -0
  16. package/dist/Notepad-2BO5VEUG.js.map +1 -0
  17. package/dist/PdfViewer-LSP3YNFC.js +4 -0
  18. package/dist/PdfViewer-LSP3YNFC.js.map +1 -0
  19. package/dist/PomodoroTimer-SVJ62RXM.js +629 -0
  20. package/dist/PomodoroTimer-SVJ62RXM.js.map +1 -0
  21. package/dist/Preview-N4UYYXG6.js +2955 -0
  22. package/dist/Preview-N4UYYXG6.js.map +1 -0
  23. package/dist/Sidebar-J76PV4QF.js +290 -0
  24. package/dist/Sidebar-J76PV4QF.js.map +1 -0
  25. package/dist/Spreadsheet-5EWZGMXN.js +515 -0
  26. package/dist/Spreadsheet-5EWZGMXN.js.map +1 -0
  27. package/dist/Stock-CDUS4VCZ.js +154 -0
  28. package/dist/Stock-CDUS4VCZ.js.map +1 -0
  29. package/dist/Weather-EEDBWBPQ.js +427 -0
  30. package/dist/Weather-EEDBWBPQ.js.map +1 -0
  31. package/dist/WorldClock-FFFULVTJ.js +198 -0
  32. package/dist/WorldClock-FFFULVTJ.js.map +1 -0
  33. package/dist/apps/index.d.ts +267 -0
  34. package/dist/apps/index.js +147 -0
  35. package/dist/apps/index.js.map +1 -0
  36. package/dist/brand.css +2 -0
  37. package/dist/chunk-2VM2FRZJ.js +215 -0
  38. package/dist/chunk-2VM2FRZJ.js.map +1 -0
  39. package/dist/chunk-32W6VY2J.js +549 -0
  40. package/dist/chunk-32W6VY2J.js.map +1 -0
  41. package/dist/chunk-3CUSBZWG.js +59 -0
  42. package/dist/chunk-3CUSBZWG.js.map +1 -0
  43. package/dist/chunk-3L463T5H.js +26 -0
  44. package/dist/chunk-3L463T5H.js.map +1 -0
  45. package/dist/chunk-4TVV2BIU.js +13334 -0
  46. package/dist/chunk-4TVV2BIU.js.map +1 -0
  47. package/dist/chunk-6XQHH7D2.js +105 -0
  48. package/dist/chunk-6XQHH7D2.js.map +1 -0
  49. package/dist/chunk-7CCHEEYC.js +94 -0
  50. package/dist/chunk-7CCHEEYC.js.map +1 -0
  51. package/dist/chunk-7KIOEKHS.js +617 -0
  52. package/dist/chunk-7KIOEKHS.js.map +1 -0
  53. package/dist/chunk-AXWC3332.js +1103 -0
  54. package/dist/chunk-AXWC3332.js.map +1 -0
  55. package/dist/chunk-BFBWLCO6.js +16 -0
  56. package/dist/chunk-BFBWLCO6.js.map +1 -0
  57. package/dist/chunk-C3NELUTX.js +79 -0
  58. package/dist/chunk-C3NELUTX.js.map +1 -0
  59. package/dist/chunk-CT3HVZRZ.js +26 -0
  60. package/dist/chunk-CT3HVZRZ.js.map +1 -0
  61. package/dist/chunk-D7PYW2QS.js +265 -0
  62. package/dist/chunk-D7PYW2QS.js.map +1 -0
  63. package/dist/chunk-DMCFOWOJ.js +153 -0
  64. package/dist/chunk-DMCFOWOJ.js.map +1 -0
  65. package/dist/chunk-FQB24B42.js +416 -0
  66. package/dist/chunk-FQB24B42.js.map +1 -0
  67. package/dist/chunk-GA35NEND.js +56 -0
  68. package/dist/chunk-GA35NEND.js.map +1 -0
  69. package/dist/chunk-HNH7FOYB.js +69 -0
  70. package/dist/chunk-HNH7FOYB.js.map +1 -0
  71. package/dist/chunk-HYZ5QMVO.js +462 -0
  72. package/dist/chunk-HYZ5QMVO.js.map +1 -0
  73. package/dist/chunk-I464GYJJ.js +17 -0
  74. package/dist/chunk-I464GYJJ.js.map +1 -0
  75. package/dist/chunk-JFQGPY7W.js +819 -0
  76. package/dist/chunk-JFQGPY7W.js.map +1 -0
  77. package/dist/chunk-MGGY74RY.js +14 -0
  78. package/dist/chunk-MGGY74RY.js.map +1 -0
  79. package/dist/chunk-N3CG5HG5.js +144 -0
  80. package/dist/chunk-N3CG5HG5.js.map +1 -0
  81. package/dist/chunk-W6XTG23K.js +3708 -0
  82. package/dist/chunk-W6XTG23K.js.map +1 -0
  83. package/dist/chunk-YG45DAUT.js +96 -0
  84. package/dist/chunk-YG45DAUT.js.map +1 -0
  85. package/dist/chunk-YMGXZABY.js +7 -0
  86. package/dist/chunk-YMGXZABY.js.map +1 -0
  87. package/dist/chunk-YNGPTOL6.js +46 -0
  88. package/dist/chunk-YNGPTOL6.js.map +1 -0
  89. package/dist/chunk-YNHLQ24V.js +400 -0
  90. package/dist/chunk-YNHLQ24V.js.map +1 -0
  91. package/dist/chunk-YNKK7NE3.js +171 -0
  92. package/dist/chunk-YNKK7NE3.js.map +1 -0
  93. package/dist/chunk-YPO6RDMX.js +20 -0
  94. package/dist/chunk-YPO6RDMX.js.map +1 -0
  95. package/dist/file-intake/index.d.ts +87 -0
  96. package/dist/file-intake/index.js +3 -0
  97. package/dist/file-intake/index.js.map +1 -0
  98. package/dist/index-dQia6NpB.d.ts +5904 -0
  99. package/dist/index.d.ts +2495 -0
  100. package/dist/index.js +7460 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/markdown/index.d.ts +118 -0
  103. package/dist/markdown/index.js +184 -0
  104. package/dist/markdown/index.js.map +1 -0
  105. package/dist/markup/index.d.ts +235 -0
  106. package/dist/markup/index.js +135 -0
  107. package/dist/markup/index.js.map +1 -0
  108. package/dist/page-tabs/index.d.ts +154 -0
  109. package/dist/page-tabs/index.js +706 -0
  110. package/dist/page-tabs/index.js.map +1 -0
  111. package/dist/shell.css +105 -0
  112. package/dist/sounds-NT4DEZGD.js +3 -0
  113. package/dist/sounds-NT4DEZGD.js.map +1 -0
  114. package/dist/styles.css +22 -0
  115. package/dist/themes.css +249 -0
  116. package/dist/types-BOinJXUf.d.ts +118 -0
  117. package/dist/ui/index.d.ts +4 -0
  118. package/dist/ui/index.js +15 -0
  119. package/dist/ui/index.js.map +1 -0
  120. package/dist/ui.css +1518 -0
  121. package/package.json +172 -3
@@ -0,0 +1,2495 @@
1
+ import { S as SearchConfig, C as ColumnDef, a as SortState, P as PaginatedResponse } from './index-dQia6NpB.js';
2
+ export { A as ALT, b as ALT_SHIFT_D, c as ALT_SHIFT_E, d as ALT_SHIFT_N, e as APP_UPDATE_POLL_MS, f as Accordion, g as AccordionItem, h as AccordionProps, i as Align, j as AngleScale, k as AppUpdateBanner, l as AppUpdateBannerProps, m as AttachButton, n as AttachButtonProps, o as AttachmentDropZone, p as AttachmentDropZoneProps, q as AttachmentList, r as AttachmentListProps, s as AuthScreen, t as AuthScreenProps, u as Avatar, v as AvatarGroup, w as AvatarGroupProps, x as AvatarProps, y as AvatarSize, z as AvatarStatus, B as BackgroundVariant, D as BandScale, E as Banner, F as BannerEmphasis, G as BannerProps, H as BannerTone, I as BarChart, J as BarChartProps, K as BoxPlotBox, L as BoxPlotChart, M as BoxPlotChartProps, N as BrandAssetEditor, O as BrandAssetEditorProps, Q as BrandAssetPreview, R as BrandMark, T as BrandMarkProps, U as BrandMarkSlot, V as BrandMarkSurface, W as BrandMarkTreatment, X as BreadcrumbItem, Y as Breadcrumbs, Z as BreadcrumbsProps, _ as BudgetBar, $ as BudgetBarProps, a0 as BudgetState, a1 as Button, a2 as ButtonProps, a3 as ButtonSize, a4 as ButtonVariant, a5 as CHART_INK, a6 as CMD_A, a7 as CMD_DOT, a8 as CMD_ENTER, a9 as CMD_K, aa as CMD_S, ab as Calendar, ac as CalendarMode, ad as CalendarProps, ae as Candle, af as CandlestickChart, ag as CandlestickChartProps, ah as Card, ai as CardPadding, aj as CardProps, ak as CartesianPlot, al as CartesianPlotProps, am as CellStyle, an as ChartBackgroundVariant, ao as ChartBrush, ap as ChartBrushProps, aq as ChartCurve, ar as ChartDefs, as as ChartDefsProps, at as ChartDot, au as ChartDotProps, av as ChartDotVariant, aw as ChartFillVariant, ax as ChartFrame, ay as ChartFrameProps, az as ChartHighlightProvider, aA as ChartLegendEntry, aB as ChartLegendSwatch, aC as ChartRange, aD as ChartReferenceLine, aE as ChartRevealDirection, aF as ChartSkeleton, aG as ChartSkeletonProps, aH as ChartStatusTone, aI as ChartStrokeVariant, aJ as ChartTooltip, aK as ChartTooltipProps, aL as ChartTooltipRow, aM as ChatTemplate, aN as Checkbox, aO as CheckboxProps, aP as CheckoutTemplate, aQ as ChordChart, aR as ChordChartProps, aS as ColoredBadge, aT as ColoredBadgeProps, aU as ColumnChart, aV as ColumnChartProps, aW as ColumnSeries, aX as ComposerAttachments, aY as ComposerAttachmentsProps, aZ as ConfirmProvider, a_ as ContainerFillChart, a$ as ContainerFillChartProps, b0 as ContainerFillItem, b1 as CountBadge, b2 as CountBadgeProps, b3 as CountBadgeTone, b4 as Curve, b5 as DAY_MS, b6 as DEFAULT_SHELL_STRINGS, b7 as DashboardTemplate, b8 as DataTable, b9 as DataTableColumn, ba as DataTableColumnGroup, bb as DataTableHeader, bc as DataTablePage, bd as DataTableProps, be as DatePicker, bf as DatePickerProps, bg as DateRangePicker, bh as DateRangePickerProps, bi as DateTimePicker, bj as DateTimePickerProps, bk as DescriptionColumns, bl as DescriptionItem, bm as DescriptionList, bn as DescriptionListProps, bo as Dialog, bp as DialogProps, bq as DialogSize, br as Divider, bs as DividerProps, bt as DividerSpacing, bu as DonutChart, bv as DonutChartProps, bw as DonutSegment, bx as Drawer, by as DrawerProps, bz as DrawerSide, bA as DrawerSize, bB as DropdownMenu, bC as DropdownMenuAlign, bD as DropdownMenuItem, bE as DropdownMenuProps, bF as DropdownMenuSide, bG as DropdownPositionOptions, bH as ENTER, bI as EditableGrid, bJ as EditableGridProps, bK as EmailTemplate, bL as EmptyState, bM as EmptyStateProps, bN as ErrorBoundary, bO as ErrorBoundaryProps, bP as ErrorPage, bQ as ErrorPageProps, bR as FileIntake, bS as FilePicker, bT as FilePickerProps, bU as FillVariant, bV as FormError, bW as FormErrorSummary, bX as FormErrorSummaryProps, bY as FormField, bZ as FormFieldProps, b_ as FormLayoutPage, b$ as FunnelChart, c0 as FunnelChartProps, c1 as FunnelStage, c2 as GLASS_DIVIDER, c3 as GLASS_INPUT_BG, c4 as GROUP_COLORS, c5 as GROUP_COLORS_SOLID, c6 as GalleryTemplate, c7 as Gap, c8 as GlobalSearch, c9 as GlobalSearchProps, ca as Grid, cb as GridCols, cc as GridColumn, cd as GridProps, ce as HeatmapChart, cf as HeatmapChartProps, cg as HelpCenter, ch as HelpCenterDoc, ci as HelpCenterProps, cj as HistogramBin, ck as HistogramChart, cl as HistogramChartProps, cm as INPUT_BASE, cn as INPUT_SIZES, co as IconButton, cp as IconButtonProps, cq as Inline, cr as InlineProps, cs as Input, ct as InputNumber, cu as InputNumberProps, cv as InputProps, cw as InputSize, cx as Justify, cy as Kanban, cz as KanbanColumn, cA as KanbanProps, cB as Kbd, cC as KbdProps, cD as KbdSize, cE as Label, cF as LabelProps, cG as LadderScale, cH as LineChart, cI as LineChartProps, cJ as LineChartSeries, cK as LinearScale, cL as ListFooter, cM as ListLoadError, cN as ListLoadErrorProps, cO as LoadingSpinner, cP as LoadingSpinnerProps, cQ as MAX_FRACTION_DIGITS, cR as MENU_MAX_HEIGHT, cS as MOD, cT as MOTION, cU as Markdown, cU as MarkdownLite, cV as MarkdownLiteProps, cV as MarkdownProps, cW as MediaUploadField, cX as MediaUploadFieldProps, cY as MediaUploadGrid, cZ as MediaUploadGridItem, c_ as MediaUploadGridProps, c$ as MenuPos, d0 as Meter, d1 as MeterProps, d2 as MetricBar, d3 as MetricBarProps, d4 as Milestone, d5 as MilestoneKind, d6 as MilestoneTimeline, d7 as MilestoneTimelineProps, d8 as NEXT_CHUNK_PATH_PREFIX, d9 as NativeSelect, da as NumericKeypad, db as NumericKeypadProps, dc as OVERLAY_OPEN_EVENT, dd as POPUP_MAX_WIDTH, de as POST_PRODUCTION_STATUSES, df as PageHeader, dg as PageHeaderProps, dh as Pagination, di as PaginationProps, dj as PaperclipGlyph, dk as Paragraph, dl as ParagraphProps, dm as PieChart, dn as PieChartProps, dp as PieSegment, dq as PlotGeometry, dr as Point, ds as PopupMenu, dt as PopupMenuDivider, du as PopupMenuItem, dv as PopupMenuItemProps, dw as PopupMenuLabel, dx as PopupMenuProps, dy as PopupSubmenu, dz as PopupSubmenuProps, dA as PortalBrandingContextValue, dB as PortalBrandingProvider, dC as PortalBrandingProviderProps, dD as ProductionTimeline, dE as ProductionTimelineProps, dF as ProductionTimelineSnapshot, dG as ProgressItem, dH as PublicPortal, dI as PublicPortalBranding, dJ as RADAR_SERIES_CAP, dK as RadarAxis, dL as RadarChart, dM as RadarChartProps, dN as RadarSeries, dO as RadialBarChart, dP as RadialBarChartProps, dQ as RadialBarRow, dR as Radio, dS as RadioGroup, dT as RadioGroupOption, dU as RadioGroupOrientation, dV as RadioGroupProps, dW as RadioProps, dX as Range, dY as RangeChart, dZ as RangeChartProps, d_ as RangeRow, d$ as RankedBars, e0 as RankedBarsProps, e1 as RankedBarsRow, e2 as ResolvedPortalBrandAsset, e3 as Result, e4 as ResultProps, e5 as ResultStatus, e6 as RevealDirection, e7 as SCATTER_SERIES_CAP, e8 as SERIES_SLOT_COUNT, e9 as SERIES_VARS, ea as SHIFT, eb as STAGES, ec as STALE_CHUNK_RELOAD_COOLDOWN_MS, ed as STALE_CHUNK_RELOAD_KEY, ee as STATUS_VARS, ef as SankeyChart, eg as SankeyChartProps, eh as SankeyLink, ei as SankeyNode, ej as ScatterChart, ek as ScatterChartProps, el as ScatterPoint, em as ScatterSeries, en as SearchProvider, eo as SearchResult, ep as SearchableOption, eq as SearchableSelect, er as SearchableSelectProps, es as Segmented, et as SegmentedOption, eu as SegmentedProps, ev as SegmentedSize, ew as Select, ex as SelectOption, ey as SelectProps, ez as SemanticGroup, eA as SettingRow, eB as SettingRowProps, eC as SeverityTone, eD as ShellPrefsAdapter, eE as ShellPrefsProvider, eF as ShellStrings, eG as ShellStringsOverride, eH as ShellStringsProvider, eI as SidebarActionButton, eJ as SidebarActionButtonProps, eK as SidebarGroupLabel, eL as SidebarLayout, eM as SidebarLayoutProps, eN as SidebarNavItem, eO as SidebarNavItemProps, eP as Skeleton, eQ as SkeletonProps, eR as SkeletonVariant, eS as Snippet, eT as SnippetProps, eU as SnippetSize, eV as SnippetVariant, eW as Sparkline, eX as SparklineProps, eY as Stack, eZ as StackProps, e_ as StaleChunkReloadOptions, e$ as StatCard, f0 as StatCardProps, f1 as StatTile, f2 as StatTileProps, f3 as Statistic, f4 as StatisticProps, f5 as StatisticSize, f6 as StatisticTone, f7 as StatusBadge, f8 as StatusBadgeProvider, f9 as StatusEmphasis, fa as StepItem, fb as Stepper, fc as StepperProps, fd as StrokeVariant, fe as SunburstChart, ff as SunburstChartProps, fg as SunburstNode, fh as Switch, fi as SwitchProps, fj as SwitchSize, fk as TabItem, fl as Tabs, fm as TabsProps, fn as TagInput, fo as TagInputOption, fp as TagInputProps, fq as Text, fr as TextProps, fs as TextSize, ft as TextTone, fu as TextWeight, fv as Textarea, fw as TextareaProps, fx as Theme, fy as TileButton, fz as TileButtonProps, fA as TileSize, fB as TimePicker, fC as TimePickerProps, fD as TimeSeriesChart, fE as TimeSeriesChartProps, fF as TimeSeriesSeries, fG as TimelineMarker, fH as TimelineMarkerKind, fI as TimelineReport, fJ as TimelineScrubProgress, fK as TimelineTrack, fL as TimelineTrackItem, fM as TimelineTrackKind, fN as TimelineTrackPending, fO as TimelineTrackPhase, fP as TimelineTrackPlayback, fQ as TimelineTrackProps, fR as TimelineTrackThumb, fS as Title, fT as TitleLevel, fU as TitleProps, fV as ToastOptions, fW as ToastPlacement, fX as Tooltip, fY as TooltipProps, fZ as TopNav, f_ as TopNavItem, f$ as TopNavProps, g0 as TreemapChart, g1 as TreemapChartProps, g2 as TreemapItem, g3 as TreemapTile, g4 as UseAppUpdateOptions, g5 as UseProductionTimelineOpts, g6 as VERSION, g7 as VITE_PRELOAD_ERROR_EVENT, g8 as WaterfallChart, g9 as WaterfallChartProps, ga as WaterfallStep, gb as ZLayer, gc as Z_LAYERS, gd as angleScale, ge as appendKey, gf as applyThemePrefs, gg as arcPath, gh as areaBetween, gi as areaFrom, gj as autoHighlightIndex, gk as backspace, gl as bandScale, gm as binValues, gn as budgetState, go as bumpPath, gp as calcOverall, gq as calcReportOverall, gr as confirm, gs as confirmDestructive, gt as curvePath, gu as dashFor, gv as dismissPopups, gw as fillFor, gx as fillId, gy as fmtSliderDate, gz as formatDate, gA as fromDateKey, gB as glassStyle, gC as glowId, gD as groundId, gE as groupColors, gF as highlightOpacity, gG as inputClasses, gH as installStaleChunkReload, gI as isMac, gJ as isSeverityTone, gK as isStaleChunkError, gL as ladderScale, gM as linearPath, gN as linearScale, gO as logScale, gP as maskId, gQ as mediaFileName, gR as monotonePath, gS as niceMax, gT as polygonPoints, gU as popupBounds, gV as prompt, gW as radiusScale, gX as resolveBrandMarkTreatment, gY as resolvePortalBrandAsset, gZ as resolveSeriesColor, g_ as resolveTheme, g$ as seriesColor, h0 as severityOf, h1 as splinePath, h2 as squarify, h3 as stagger, h4 as stepPath, h5 as tabButtonId, h6 as tabPanelId, h7 as toDateKey, h8 as toDayMs, h9 as toISODate, ha as toast, hb as useAppUpdate, hc as useClickOutside, hd as useConfirm, he as useDropdownPosition, hf as useFocusTrap, hg as useHighlight, hh as useIsMobile, hi as useLocalStoragePrefs, hj as usePlotWidth, hk as usePortalBranding, hl as useProductionTimeline, hm as useScrollLock, hn as useShellPrefs, ho as useShellStrings, hp as useTheme } from './index-dQia6NpB.js';
3
+ export { FileIntakeAlert, FileIntakeLimits, FileIntakeOptions, FileRejection, FileRejectionReason, acceptsFile, useFileIntake } from './file-intake/index.js';
4
+ import { W as WindowRegistry } from './types-BOinJXUf.js';
5
+ export { M as ModalRegistryEntry, P as PageRegistryEntry, a as WindowRegistryEntry, i as isEntityEntry, b as isPageEntry, s as setShellWindowRegistry } from './types-BOinJXUf.js';
6
+ import * as react from 'react';
7
+ import react__default, { ReactNode, Component, ErrorInfo, Dispatch, SetStateAction, RefObject } from 'react';
8
+ import * as react_jsx_runtime from 'react/jsx-runtime';
9
+ import * as _tanstack_query_core from '@tanstack/query-core';
10
+ import { AxiosInstance } from 'axios';
11
+
12
+ /**
13
+ * createWindowRegistry — composes a window registry from one or more partial
14
+ * registry maps. Used at the consumer's App layer to merge package-bundled
15
+ * apps with the consumer's own entity windows.
16
+ *
17
+ * const windows = createWindowRegistry(
18
+ * bundledApps, // utility + document + web apps from the package
19
+ * entityWindows, // consumer-defined entity windows (e.g. Sales Order, Invoice)
20
+ * );
21
+ *
22
+ * Later partials override earlier ones on the same key — handy when a
23
+ * consumer wants to swap a built-in app for their own variant.
24
+ */
25
+
26
+ declare function createWindowRegistry(...partials: WindowRegistry[]): WindowRegistry;
27
+
28
+ /**
29
+ * Nav data types used by <Layout> + <StartMenu>. The actual sections, icons,
30
+ * and categories are consumer-supplied via Layout props — the package never
31
+ * ships nav DATA, only the shape it expects.
32
+ */
33
+
34
+ interface NavItem {
35
+ to: string;
36
+ label: string;
37
+ /** Optional compact wording used only inside navigation menus. Keep `label`
38
+ * as the full semantic name for windows, search and assistive technology. */
39
+ menuLabel?: string;
40
+ perms?: string[];
41
+ /** Permissions the user must hold EVERY one of, unlike `perms` which needs
42
+ * only one. For an entry whose page is gated on a narrower permission than
43
+ * the one that puts it in the menu: listing both in `perms` would widen the
44
+ * audience (it's an OR), so the extra one goes here instead. */
45
+ allPerms?: string[];
46
+ dividerAfter?: boolean;
47
+ /** An action instead of a destination. When set, choosing the row calls this
48
+ * and opens no window — `to` is then only the row's key (and its `navIcons`
49
+ * lookup), so give it one no page uses. The menu closes as it would after
50
+ * opening a page. For rows whose target is not a window: signing in to
51
+ * another tenant, say, which Sign Out is the built-in precedent for. */
52
+ onSelect?: () => void;
53
+ /** Optional sub-items, to any depth — a child may have children of its own.
54
+ * Hovering the parent in <StartMenu> opens a nested flyout; in <Sidebar>
55
+ * the parent becomes an inline sub-accordion. */
56
+ children?: NavItem[];
57
+ }
58
+ interface NavSection {
59
+ label: string;
60
+ /** Optional compact wording used only inside navigation menus. */
61
+ menuLabel?: string;
62
+ items: NavItem[];
63
+ perms?: string[];
64
+ /** Same all-of rule as `NavItem.allPerms`, for a whole section. */
65
+ allPerms?: string[];
66
+ /** Optional landing route for clicks on the section title itself
67
+ * (e.g. R&D's `/rd` dashboard). */
68
+ to?: string;
69
+ /** Draw a divider under this section's row, so a consumer can split one
70
+ * flat group into visual bands (e.g. an operator console pinned above the
71
+ * ERP sections). Same knob as `NavItem.dividerAfter`, and drawn INSIDE the
72
+ * section's wrapper so it stays below the row in the `flex-col-reverse`
73
+ * Start menu a side/top taskbar renders. */
74
+ dividerAfter?: boolean;
75
+ }
76
+ interface VirtualSection {
77
+ label: string;
78
+ /** Optional compact wording used only inside navigation menus. */
79
+ menuLabel?: string;
80
+ items: NavItem[];
81
+ icon?: ReactNode;
82
+ }
83
+ interface StartMenuCategories {
84
+ /** Section labels rendered in the "ERP" group (bold, with section icon). */
85
+ erp: string[];
86
+ /** Section labels rendered in the "system" group. */
87
+ system: string[];
88
+ /** Optional virtual flyouts (e.g. a "Utilities" tray). */
89
+ virtual?: VirtualSection[];
90
+ /** Optional section labels pinned to the bottom of the menu, next to the user
91
+ * profile, separated from the ERP group by a divider. */
92
+ footer?: string[];
93
+ /** Optional flat top-level items pinned to the bottom of the menu, next to
94
+ * the user profile, separated from the ERP group by a divider. Unlike
95
+ * `footer` (section labels rendered as flyouts), these render as direct
96
+ * clickable rows — use for standalone destinations like System Preferences
97
+ * or a bug-report link. */
98
+ footerItems?: NavItem[];
99
+ }
100
+
101
+ /**
102
+ * INTERNAL stub — package-side compatibility for files that legacy-imported
103
+ * default nav data from `shell-config/nav`. The package never ships nav
104
+ * DATA; consumers always supply `navSections` / `navIcons` / `sectionIcons` /
105
+ * `categories` as Layout props.
106
+ *
107
+ * Empty defaults exported here so the copied files compile during the
108
+ * extraction transition. Each consumer-facing field is also re-exported as
109
+ * a TYPE alias for code that only needed the shape.
110
+ */
111
+
112
+ declare function setShellNavIcons(icons: Record<string, ReactNode>): void;
113
+
114
+ /** Register an Escape interceptor; returns an unregister function. */
115
+ declare function registerModalEscapeInterceptor(fn: (e: KeyboardEvent) => boolean): () => void;
116
+
117
+ /**
118
+ * What Cmd/Ctrl+Enter clicks when a window has no `<form>` to `requestSubmit()`.
119
+ *
120
+ * Two kinds of entry, in descending order of how much we trust them:
121
+ *
122
+ * - **Explicit** — `type="submit"`, the bare `data-submit` attribute, and the
123
+ * `.btn-submit` utility class. These are how a consumer *declares* "this is
124
+ * the submit", so they are the ones to reach for in new markup.
125
+ * - **Legacy colour heuristic** — `.bg-green-600` / `.bg-blue-600`. From before
126
+ * the explicit opt-ins existed, when the submit button was identified by
127
+ * being the blue (or green, for approve/post) one. Kept because consumers
128
+ * still ship plenty of it; do not add more colours here.
129
+ *
130
+ * `.btn-submit` was missing from this list until 4.76.0, which made converting a
131
+ * button from a hardcoded `bg-blue-600` fill to the themable `.btn-submit`
132
+ * utility silently drop it out of the selector — Cmd+Enter stopped working while
133
+ * the button's own ⌘⏎ badge went on advertising it. The admin portal hit this
134
+ * across ~26 buttons and worked around it by also adding `data-submit`.
135
+ *
136
+ * NOTE: a comma-separated `querySelector` returns the first match in **document
137
+ * order**, not the first *selector* that matches — so the order of the entries
138
+ * below is documentation, not precedence. When a panel holds several candidates,
139
+ * the earliest one in the DOM wins.
140
+ */
141
+ declare const MODAL_SUBMIT_SELECTOR = "button[type=\"submit\"], button[data-submit], button.btn-submit, button.bg-green-600, button.bg-blue-600";
142
+ /** Hook for widget components to register a settings handler for the right-click menu. */
143
+ declare function useWidgetSettings(handler: () => void): void;
144
+ /** Hook to add custom items to the window title menu from inside a modal */
145
+ declare function useWindowMenuItem(label: string, onClick: () => void, icon?: React.ReactNode): void;
146
+ /** Component that updates the surrounding window's title bar. Render anywhere
147
+ * inside a Modal-rendered page. Cross-bundle safe (uses DOM traversal rather
148
+ * than React Context, which has separate instances in each bundle). */
149
+ declare function WindowTitle({ title }: {
150
+ title: string;
151
+ }): react_jsx_runtime.JSX.Element;
152
+ /** @deprecated Use <WindowTitle> instead — the hook variant doesn't work
153
+ * across bundle boundaries (apps barrel has its own ModalIdContext). */
154
+ declare function useWindowTitle(_title: string): void;
155
+ /**
156
+ * Portal component — renders children into the nearest Modal's footer actions area.
157
+ * position="left" for destructive actions (Delete), default is right for primary actions.
158
+ */
159
+ declare function CopyButton({ text }: {
160
+ text: string;
161
+ }): react_jsx_runtime.JSX.Element;
162
+ /**
163
+ * The id of the `<Modal>` this component is rendered inside, or `''` outside one.
164
+ *
165
+ * This is the PRIVATE per-mount id (`modal-xxxxxx`) that `activationOrder` and
166
+ * the window-scoped CustomEvents carry — not the caller-supplied `windowKey`.
167
+ * Same context `useWindowMenuItem` and `useWidgetSettings` already read, and it
168
+ * reaches portal window content the same way theirs does.
169
+ *
170
+ * Use it to answer "was this event meant for MY window?", which
171
+ * {@link useModalActive} cannot: that asks "is the frontmost modal mine", and
172
+ * every nested dialog a form opens pushes itself onto `activationOrder`, so a
173
+ * form with a child dialog open reads as inactive.
174
+ */
175
+ declare function useEnclosingModalId(): string;
176
+ /** Hook to check if the current modal is active (frontmost) */
177
+ declare function useModalActive(): boolean;
178
+ declare function ModalActions({ children, position, quiet }: {
179
+ children: React.ReactNode;
180
+ position?: 'left' | 'right';
181
+ /** @internal Content that joins a footer but is no reason to show one — the
182
+ * shell's own Undo/Redo pair. It does not tell the Modal the footer has
183
+ * content. */
184
+ quiet?: boolean;
185
+ }): react.ReactPortal | null;
186
+ /**
187
+ * Cancel button that checks for unsaved changes before calling onClick.
188
+ * Automatically detects dirty state from the nearest Modal context.
189
+ */
190
+ declare function CancelButton({ onClick, children, className }: {
191
+ onClick: () => void;
192
+ children?: React.ReactNode;
193
+ className?: string;
194
+ }): react_jsx_runtime.JSX.Element;
195
+ /**
196
+ * Seed an initial position for a window — applied only when no saved
197
+ * position already exists for the given key. Use this from the consumer
198
+ * to lay out the first-run desktop without overwriting whatever the user
199
+ * has subsequently dragged the windows to.
200
+ *
201
+ * The key matches the window's `windowKey` — for `openPage('/weather')`
202
+ * that's `'page:/weather'`.
203
+ */
204
+ declare function setWindowDefaultPosition(key: string, pos: {
205
+ x: number;
206
+ y: number;
207
+ w: number;
208
+ h: number;
209
+ }): void;
210
+ /** Read the saved box for a window key (viewport-relative px), or null. */
211
+ declare function getWindowPosition(key: string): {
212
+ x: number;
213
+ y: number;
214
+ w: number;
215
+ h: number;
216
+ } | null;
217
+ /**
218
+ * Force a window's saved position — unlike `setWindowDefaultPosition` this
219
+ * overwrites any existing entry. Use when the consumer wants to deliberately
220
+ * (re)place a window, e.g. the Widget Manager dropping a freshly-added widget
221
+ * into a tidy top-left slot regardless of where it last sat. For `autoHeight`
222
+ * windows the `h` here is only a first-paint placeholder — the window
223
+ * re-measures its content height on open.
224
+ */
225
+ declare function setWindowPosition(key: string, pos: {
226
+ x: number;
227
+ y: number;
228
+ w: number;
229
+ h: number;
230
+ }): void;
231
+ interface ModalProps {
232
+ open: boolean;
233
+ onClose: () => void;
234
+ title: React.ReactNode;
235
+ /** Window icon — shown before title, clicking it opens window menu */
236
+ icon?: React.ReactNode;
237
+ /** Text to copy when the copy icon in the header is clicked (e.g. entity number) */
238
+ copyText?: string;
239
+ size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl';
240
+ dirty?: boolean | 'auto';
241
+ onNext?: () => void;
242
+ onPrev?: () => void;
243
+ footer?: React.ReactNode;
244
+ /** When true, body uses overflow-hidden — child content manages its own scrolling */
245
+ bodyScroll?: false;
246
+ /** Custom minimize handler — called instead of the default global minimize */
247
+ onMinimize?: (savedBox: {
248
+ x: number;
249
+ y: number;
250
+ w: number;
251
+ h: number;
252
+ maximized: boolean;
253
+ }) => void;
254
+ /** Restore to a previously saved position/size */
255
+ initialBox?: {
256
+ x: number;
257
+ y: number;
258
+ w: number;
259
+ h: number;
260
+ maximized: boolean;
261
+ };
262
+ /** Footer actions (right side) — alternative to ModalActions portal */
263
+ actions?: React.ReactNode;
264
+ /** Footer actions (left side) — alternative to ModalActions portal */
265
+ actionsLeft?: React.ReactNode;
266
+ /** The window is in an editing state — a draft, a duplicate, or Edit mode
267
+ * on a detail — which is when the footer shows Undo/Redo for the form's
268
+ * stack of its own accord. `WindowManager` sets it; a window that edits in
269
+ * place says `useUndoCanEdit(true)` instead. */
270
+ editing?: boolean;
271
+ /** Allow the window to be pinned on top of all others */
272
+ allowPinOnTop?: boolean;
273
+ /** Initial position hint */
274
+ initialPosition?: 'top-right' | 'top-left';
275
+ /** Widget mode: no title/footer, drag anywhere, right-click context menu */
276
+ widget?: boolean;
277
+ /** Compact title bar: smaller header with title + close only, no minimize/maximize */
278
+ compact?: boolean;
279
+ /** App-style window: full title bar but body uses no padding so the app's own
280
+ * chrome (toolbars, menus) sits flush against the window frame. For
281
+ * self-chromed apps like Preview, Files, Browser. Implies `bodyScroll: false`
282
+ * — apps manage their own scrolling internally. */
283
+ appStyle?: boolean;
284
+ /** Flush body: keeps the STANDARD full title bar and footer, but drops the
285
+ * body padding so the content sits flush to the window edges (e.g. a
286
+ * `<SidebarLayout>`). Implies `bodyScroll: false`. */
287
+ flushBody?: boolean;
288
+ /** Auto-size height based on content. Window's height adapts to whatever the
289
+ * body renders; floored by `autoMinHeight` and capped to the available
290
+ * viewport so nothing overflows the screen. Naturally-flowing content (a
291
+ * form, a table) shrinks the window to hug it; a fill-height root (`h-full` /
292
+ * `flex-1` between a fixed header and footer) keeps the normal size-ladder
293
+ * height and scrolls internally rather than collapsing. */
294
+ autoHeight?: boolean;
295
+ /** Minimum height (px) when `autoHeight` is on. Defaults to 240. */
296
+ autoMinHeight?: number;
297
+ /** Fit the width to the content when the window opens: a body that would
298
+ * scroll sideways grows the window until it doesn't, up to the work area.
299
+ * Grow-only, and only for the first few seconds — never after the user
300
+ * moves, resizes or maximizes it. On by default; `false` keeps the
301
+ * size-ladder / `dimensions` width exactly. Widgets, `appStyle` windows and
302
+ * mobile never fit. */
303
+ autoWidth?: boolean;
304
+ /** Custom menu items for widget right-click context menu */
305
+ widgetMenu?: React.ReactNode;
306
+ /** Custom window dimensions [width, height] in pixels */
307
+ dimensions?: [number, number];
308
+ /** Stable key for persisting window position to sessionStorage */
309
+ windowKey?: string;
310
+ /** windowKey of whichever window was active when this one opened. On
311
+ * mobile, swipe-to-back broadcasts this value so the parent window can
312
+ * un-hide itself underneath the sliding panel. */
313
+ openedFromKey?: string;
314
+ /** Per-section window accent (SG#00372): an `R G B` triple (e.g.
315
+ * '91 141 190'). When set, the panel publishes it as the
316
+ * `--window-accent-rgb` CSS custom property, and — if the user has turned
317
+ * the `window_accent_stripe` preference on — renders a thin accent stripe
318
+ * between the title bar and the body, so overlapping windows from
319
+ * different app sections are distinguishable at a glance. The header
320
+ * itself stays theme-neutral — themes and the user's custom header
321
+ * colour are untouched. Absent = exactly the previous rendering.
322
+ *
323
+ * The CSS custom property is published whenever `accentRgb` is passed,
324
+ * independent of the preference, so consumer CSS can key off the section
325
+ * accent even with the stripe switched off. */
326
+ accentRgb?: string;
327
+ children: React.ReactNode;
328
+ }
329
+ /**
330
+ * Bring the window with this stable key to the front — now if it is mounted,
331
+ * or the moment it mounts if it is not.
332
+ *
333
+ * This is the shell's answer to "the user asked for this window", and it is
334
+ * deliberately the ONLY way a window rises without the user touching the
335
+ * window itself. Nothing about rendering reaches this function: a background
336
+ * window that refetches, re-renders, or receives a push cannot call it, so it
337
+ * cannot jump in front of whoever is typing. Somebody has to ASK.
338
+ *
339
+ * The deferred half is not an edge case, it is the common one. A window that
340
+ * is closed has no modal to raise, so the very first "open this" arrives
341
+ * before React has rendered anything — the request waits here and `mountModal`
342
+ * claims it below. That makes first-open and re-open one code path instead of
343
+ * two, which is the whole point: the second one is what used to be missing.
344
+ *
345
+ * An outstanding request is SPENT by the mount that honours it — see
346
+ * {@link _frontRequest} for why that is safe for a StrictMode remount. It also
347
+ * expires unhonoured when the user puts a different window in front (see
348
+ * `activateModal`), or when the ask is withdrawn.
349
+ */
350
+ declare function requestWindowFront(windowKey: string): void;
351
+ /**
352
+ * Is the window identified by `windowKey` the frontmost one?
353
+ *
354
+ * For anything that binds a global shortcut from *outside* the `<Modal>` it
355
+ * belongs to — `UndoProvider` is mounted a level above `PageWindow`, so it has
356
+ * no `ModalActionsContext` to read and cannot use {@link useModalActive}, whose
357
+ * fallback path is made of module globals and therefore answers identically for
358
+ * every caller. That fallback is fine for "is any window active"; it is useless
359
+ * for "is *mine* active", and a per-window listener that asks the wrong
360
+ * question fires in every window at once.
361
+ *
362
+ * `windowKey` is the stable key a window passes to `<Modal windowKey=...>` —
363
+ * `WindowManager`'s `item.id` — not the private per-mount `modalId` that
364
+ * `activationOrder` actually holds. The two are mapped by `mountModal`, and
365
+ * resolving through that map is the whole point: `useIsActiveModal(item.id)`
366
+ * would compare a window key against a `modal-xxxxxx` id and be false for every
367
+ * window rather than true for every window.
368
+ *
369
+ * Omit `windowKey` — or pass one no mounted modal has claimed, which is the
370
+ * case for a `rendersOwnModal` window whose inner `<Modal>` sets no
371
+ * `windowKey` — and this degrades to {@link useModalActive}: the enclosing
372
+ * `<ModalActions>` context if there is one, the global test if there is not.
373
+ */
374
+ declare function useIsActiveWindow(windowKey?: string | null): boolean;
375
+ declare function toggleExposeMode(): void;
376
+ declare function exitExposeMode(): void;
377
+ declare function subscribeExposeHighlight(fn: () => void): () => boolean;
378
+ declare function getExposeHighlight(): string | null;
379
+ declare function setExposeHighlight(id: string | null): void;
380
+ /**
381
+ * Commit the current keyboard highlight: focus the highlighted window
382
+ * and exit exposé with the standard "picked tile glides back" animation.
383
+ * No-op when no highlight is set.
384
+ */
385
+ declare function commitExposeHighlight(): void;
386
+
387
+ declare function Modal({ open, onClose, title, icon, copyText, size, dirty, onNext, onPrev, footer, bodyScroll, onMinimize, initialBox, actions, actionsLeft, editing, allowPinOnTop, initialPosition, widget, compact, appStyle, flushBody, autoHeight, autoMinHeight, autoWidth, widgetMenu, dimensions, windowKey, openedFromKey, accentRgb, children }: ModalProps): react.ReactPortal | null;
388
+
389
+ /**
390
+ * A target as the window receives it: what was asked for, plus the stamp that
391
+ * makes THIS staging distinct from the last one.
392
+ *
393
+ * The stamp is not decoration. Without it, staging is only observable when the
394
+ * target's contents change — so asking for the window that is already showing
395
+ * exactly what you asked for would be a no-op, and "New form" on top of an open
396
+ * blank form would neither reset it nor (before this module) raise it. Callers
397
+ * never set `nav`; staging does.
398
+ */
399
+ type StagedTarget<T> = T & {
400
+ readonly nav: number;
401
+ };
402
+ interface WindowTargetOptions {
403
+ /**
404
+ * Whether staging this target should also bring the window forward.
405
+ * Defaults to `true`, because staging a target is how the product says
406
+ * "the user asked to see this".
407
+ *
408
+ * Pass `false` for the rare staging that is not a request to be looked at —
409
+ * restoring a session's window state on load, say, where several windows are
410
+ * re-staged at once and none of them was just asked for.
411
+ */
412
+ raise?: boolean;
413
+ }
414
+ interface WindowTarget<T> {
415
+ /** The stable key the window's `Modal` must be given, so the shell can find
416
+ * it to raise. Read it straight off the channel:
417
+ * `<Modal windowKey={myTarget.windowKey}>`. */
418
+ readonly windowKey: string;
419
+ /** Point the window at something — or `null` to close it. A non-null target
420
+ * also brings the window forward, whether it was closed, open and buried, or
421
+ * already showing this very thing. */
422
+ set(target: T | null, options?: WindowTargetOptions): void;
423
+ /** What is staged right now (`null` if nothing) — for readers outside React. */
424
+ get(): StagedTarget<T> | null;
425
+ /** Watch for stagings. Returns its own unsubscribe. */
426
+ subscribe(listener: () => void): () => void;
427
+ }
428
+ /**
429
+ * Create the staging channel for one app-mounted window.
430
+ *
431
+ * `windowKey` identifies the window to the shell and must match the `windowKey`
432
+ * given to that window's `Modal`. Use a stable, namespaced string — it also
433
+ * becomes the key the window's position and stacking order are remembered
434
+ * under, so it outlives a page refresh.
435
+ */
436
+ declare function createWindowTarget<T extends object>(windowKey: string): WindowTarget<T>;
437
+ /**
438
+ * Read the staged target inside the window's host component, re-rendering
439
+ * whenever a new one is staged — including a fresh staging of an identical
440
+ * target, which `nav` makes a distinct value.
441
+ *
442
+ * Replaces the `useState` + `useEffect(subscribe)` pair every hand-written
443
+ * version of this channel used to carry, and closes that pair's own gap: it
444
+ * reads the currently-staged target during render rather than after mount, so a
445
+ * host mounted while something is already staged shows it on its first frame.
446
+ */
447
+ declare function useWindowTarget<T extends object>(target: WindowTarget<T>): StagedTarget<T> | null;
448
+
449
+ /** Inline crash state rendered in place of a window's content. Centers itself
450
+ * in whatever space the body gives it; `onReload` resets the owning boundary
451
+ * so the content remounts from scratch.
452
+ *
453
+ * One crash a remount can never fix: a lazy chunk the server no longer has,
454
+ * because a deploy replaced the build under this tab (see `staleChunk.ts`).
455
+ * Remounting re-imports the same missing file. For that error the button is
456
+ * "Reload page" and reloads the document, which is the only recovery. */
457
+ declare function WindowCrashedFallback({ error, onReload }: {
458
+ error: Error;
459
+ onReload: () => void;
460
+ }): react_jsx_runtime.JSX.Element;
461
+ interface WindowErrorBoundaryProps {
462
+ children: ReactNode;
463
+ /** Replaces the default inline {@link WindowCrashedFallback}. `reset`
464
+ * clears the caught error so the children remount. */
465
+ fallback?: (error: Error, reset: () => void) => ReactNode;
466
+ }
467
+ /**
468
+ * Error boundary around a window's content. Without it, a page/entity
469
+ * component that throws during render propagates to the root and unmounts the
470
+ * entire desktop. With it, only the crashed window's body is replaced — the
471
+ * window chrome (close/minimize), the taskbar and every other window keep
472
+ * running. Modal wraps its body in one; WindowManager wraps each whole window
473
+ * in another as a last resort for crashes outside the body (e.g. a registry
474
+ * `title()` throwing on malformed data).
475
+ */
476
+ declare class WindowErrorBoundary extends Component<WindowErrorBoundaryProps, {
477
+ error: Error | null;
478
+ }> {
479
+ state: {
480
+ error: Error | null;
481
+ };
482
+ static getDerivedStateFromError(error: unknown): {
483
+ error: Error;
484
+ };
485
+ componentDidCatch(error: Error, info: ErrorInfo): void;
486
+ reset: () => void;
487
+ render(): string | number | boolean | Iterable<ReactNode> | react_jsx_runtime.JSX.Element | null | undefined;
488
+ }
489
+
490
+ declare function ShortcutHelp(): react_jsx_runtime.JSX.Element;
491
+
492
+ interface ShellContextMenuProps {
493
+ /** Turn this instance off — every right-click falls through to the browser.
494
+ * Under `Layout`, pass `contextMenu={false}` to `Layout` instead: it mounts
495
+ * its own instance. */
496
+ disabled?: boolean;
497
+ }
498
+ declare function ShellContextMenu({ disabled }?: ShellContextMenuProps): react_jsx_runtime.JSX.Element | null;
499
+
500
+ /**
501
+ * What the shell's global context menu should offer for a given right-click.
502
+ *
503
+ * Kept free of React (and of the DOM beyond the element it is handed) so the
504
+ * two rules that matter can be tested directly: which targets keep the
505
+ * browser's own menu, and which items a click produces.
506
+ */
507
+ /** Opt-out attribute: anything inside `[data-native-context-menu]` keeps the
508
+ * browser's menu. For surfaces the shell has no business re-skinning — an
509
+ * embedded viewer, a third-party widget. */
510
+ declare const NATIVE_MENU_ATTR = "data-native-context-menu";
511
+ /**
512
+ * Does this target keep the browser's native menu?
513
+ *
514
+ * Text inputs, textareas and contenteditable do, deliberately: the browser's
515
+ * spellcheck suggestions and "Add to dictionary" cannot be rebuilt by a web
516
+ * page, and a Paste item of our own would need a clipboard permission prompt.
517
+ * Images, canvases and media do for the same reason — Save / Copy image and
518
+ * the playback controls are the browser's. The shell menu is for everywhere
519
+ * else.
520
+ */
521
+ declare function keepsNativeMenu(target: EventTarget | null): boolean;
522
+ type ShellContextKind = 'selection' | 'link' | 'default';
523
+ interface ShellContextTarget {
524
+ /** The most specific thing under the pointer — what the menu leads with. */
525
+ kind: ShellContextKind;
526
+ /** Trimmed selection text, when something is selected. */
527
+ selectionText?: string;
528
+ /** Absolute href of the nearest enclosing link. */
529
+ linkUrl?: string;
530
+ }
531
+ /**
532
+ * Describe a right-click: what was under the pointer, and what is selected.
533
+ *
534
+ * A selection inside a link reports both, so the menu can offer both sections
535
+ * the way a browser's does; `kind` names whichever the menu should lead with.
536
+ */
537
+ declare function describeContextTarget(target: EventTarget | null, selectionText?: string | null, baseUrl?: string): ShellContextTarget;
538
+
539
+ /**
540
+ * Copy, then say so: `"<label> copied"`, or an error toast when the browser
541
+ * refused. `label` names what was copied ("Text", "Link address", "3 rows",
542
+ * a column heading) and is shown as written — the failure message leaves it
543
+ * out, so a heading like "PO #" is never lower-cased into a sentence.
544
+ */
545
+ declare function copyToClipboard(label: string, text: string, html?: string): Promise<boolean>;
546
+
547
+ /** Generic notification shape consumed by the shell. Consumer-specific
548
+ * fields live on `extra` (or just on additional properties — TS structural
549
+ * typing is permissive). */
550
+ interface ShellNotification {
551
+ id: string;
552
+ title: string;
553
+ message?: string;
554
+ entity_type?: string;
555
+ entity_id?: string;
556
+ entity_label?: string;
557
+ is_read: boolean;
558
+ actor_name?: string;
559
+ created_at: string;
560
+ }
561
+ /** Config bundle for the notification system. Supplied by the consumer;
562
+ * the shell never calls a hardcoded URL. */
563
+ interface NotificationsConfig {
564
+ /** Hook called inside <NotificationBell> on every render. Return the
565
+ * current unread count for the signed-in user. */
566
+ useUnreadCount: () => number;
567
+ /** Fetcher for the dropdown list (most recent first). */
568
+ list: (params?: {
569
+ page_size?: number;
570
+ }) => Promise<{
571
+ results: ShellNotification[];
572
+ }>;
573
+ /** Mark a single notification as read. */
574
+ markRead: (id: string) => Promise<unknown>;
575
+ /** Mark every unread notification as read. */
576
+ markAllRead: () => Promise<unknown>;
577
+ /** Click handler for a notification item. */
578
+ onItemClick: (notif: ShellNotification) => void;
579
+ /** Click handler for "View all notifications". When omitted, the link is hidden. */
580
+ onViewAll?: () => void;
581
+ }
582
+ interface NotificationBellProps extends NotificationsConfig {
583
+ popDirection?: 'left' | 'right';
584
+ }
585
+ declare function NotificationBell({ useUnreadCount, list, markRead, markAllRead, onItemClick, onViewAll, popDirection, }: NotificationBellProps): react_jsx_runtime.JSX.Element;
586
+
587
+ /**
588
+ * System-tray badge shown only when the app is served from a developer's own
589
+ * machine (localhost / 127.0.0.1). Drop it into the host's `taskbarTrayLeft`
590
+ * slot — it renders nothing off-localhost, so it's safe to leave wired on every
591
+ * build. Visibility is gated on the runtime hostname; see utils/env.ts.
592
+ */
593
+ declare function DevIndicator(): react_jsx_runtime.JSX.Element | null;
594
+
595
+ /**
596
+ * Dev-environment detection + browser chrome.
597
+ *
598
+ * A deployed bundle is byte-identical across hosts (CI builds once and promotes
599
+ * the same image between environments), so build-time signals like
600
+ * `import.meta.env.MODE` can't tell a developer's machine from a deployed site —
601
+ * only the runtime hostname can. `isDevEnv()` is therefore gated on
602
+ * `localhost`/`127.0.0.1`, and everything dev-only (the <DevIndicator/> tray
603
+ * badge, the `[DEV]` tab-title prefix) keys off it so it never leaks onto a
604
+ * deployed host.
605
+ */
606
+ /** Tooltip/banner copy for the dev indicator. */
607
+ declare const DEV_BANNER_TEXT = "LOCAL DEV ENVIRONMENT \u2014 running against your local backend; not connected to the live system";
608
+ /**
609
+ * True only when served from a developer's own machine.
610
+ *
611
+ * Gated on `localhost`/`127.0.0.1` only (not `*.local` mDNS names), so a
612
+ * teammate reaching a dev server over the LAN doesn't get the dev chrome.
613
+ */
614
+ declare function isDevEnv(): boolean;
615
+ /**
616
+ * Apply local-dev browser chrome: prefix the tab title with `[DEV]` and,
617
+ * optionally, swap the favicon. No-op outside localhost. Idempotent — safe to
618
+ * call at startup and again whenever a route overwrites `document.title`.
619
+ *
620
+ * Call before React mounts (e.g. in `main.tsx`) so the title is correct on the
621
+ * first paint. Pass `faviconHref` only if the host actually ships that asset.
622
+ */
623
+ declare function applyDevTitle(opts?: {
624
+ faviconHref?: string;
625
+ }): void;
626
+
627
+ interface SavedBox {
628
+ x: number;
629
+ y: number;
630
+ w: number;
631
+ h: number;
632
+ maximized: boolean;
633
+ }
634
+ interface MinimizedItem {
635
+ id: string;
636
+ type: 'part_number' | 'modal' | 'page';
637
+ label: string;
638
+ /** Route path for icon lookup (e.g. '/orders', '/invoices') */
639
+ route?: string;
640
+ savedBox?: SavedBox;
641
+ /** Entity registry key (e.g. 'order', 'manufacturer') for restore */
642
+ entityType?: string;
643
+ /** Entity UUID for API fetch on cross-page restore */
644
+ entityId?: string;
645
+ /** Cached entity data for instant rendering while refetch happens */
646
+ entitySnapshot?: any;
647
+ /** windowKey of the window that was active when this opened — used by the
648
+ * mobile swipe-to-back gesture to reveal the parent (e.g. the list a
649
+ * detail entity was opened from) instead of the home wallpaper. Undefined
650
+ * when this window opened directly from home / the start menu. */
651
+ openedFrom?: string;
652
+ }
653
+ interface MinimizedContextType {
654
+ openWindows: MinimizedItem[];
655
+ openEntity: (entityType: string, entityId: string, snapshot?: any, label?: string, route?: string) => void;
656
+ openPage: (path: string) => void;
657
+ closeEntity: (id: string) => void;
658
+ /** @deprecated kept for backward compat — aliases openEntity/closeEntity */
659
+ minimize: (item: MinimizedItem) => void;
660
+ items: MinimizedItem[];
661
+ restore: (item: MinimizedItem) => void;
662
+ remove: (id: string) => void;
663
+ restoreIfMinimized: (label: string) => boolean;
664
+ openItems: MinimizedItem[];
665
+ }
666
+ /**
667
+ * Registers controlled dirty state with the enclosing shell window, so closing
668
+ * it asks before discarding. Works in both kinds of window the shell owns the
669
+ * Modal for: page windows and entity/detail windows.
670
+ *
671
+ * While dirty it also holds the page itself: a reload, Back out of the app or
672
+ * closing the tab gets the browser's "Leave site?" prompt first.
673
+ *
674
+ * Ignored — deliberately, and silently — in the one place the shell has no
675
+ * Modal to guard: a registry entry with `rendersOwnModal`, where the consumer
676
+ * renders its own Modal and should pass that Modal `dirty` directly. Also a
677
+ * no-op outside a WindowManager entirely.
678
+ */
679
+ declare function useWindowDirty(dirty: boolean): void;
680
+ declare function useWindowManager(): MinimizedContextType;
681
+ /** Star button to favorite a document — saves to preferences.favorite_documents */
682
+ declare function DocFavStar({ entityType, entityId, label }: {
683
+ entityType: string;
684
+ entityId: string;
685
+ label: string;
686
+ }): react_jsx_runtime.JSX.Element;
687
+ /** Public, framework-free helper: route ('/orders', '/qc-reports', …)
688
+ * of the window the user is currently focused on. Returns undefined
689
+ * when no app window is active (start menu / dashboard with nothing
690
+ * open). Useful for code paths that fire outside React render — the
691
+ * shell consumer can stamp metadata on a payload (e.g. "which module
692
+ * is this bug report against?") without needing to lift state. */
693
+ declare function getActiveWindowRoute(): string | undefined;
694
+ declare function WindowManagerProvider({ children, windowAccentForRoute }: {
695
+ children: ReactNode;
696
+ /** SG#00372: optional per-section window accent. Called with each open
697
+ * window's route ('/sales-orders', '/payments', …); return an `R G B`
698
+ * triple (e.g. '91 141 190') to give that window a thin accent stripe
699
+ * between its title bar and body (see Modal's `accentRgb`), or undefined
700
+ * for no accent. The stripe is drawn only for users who have switched it
701
+ * on in Customization → Windows; the mapping is still worth wiring
702
+ * regardless, since it also publishes `--window-accent-rgb` per window.
703
+ * The consumer owns the route→section→colour mapping; omitting the prop
704
+ * keeps every window exactly as before. */
705
+ windowAccentForRoute?: (route: string) => string | undefined;
706
+ }): react_jsx_runtime.JSX.Element;
707
+
708
+ interface EntityWindowStateProps<T> extends EntityWindowFallbackProps {
709
+ entity: T | null | undefined;
710
+ children: (entity: T) => ReactNode;
711
+ }
712
+ interface EntityWindowFallbackProps {
713
+ isPending: boolean;
714
+ isFetching: boolean;
715
+ /**
716
+ * Whether the query is switched on. FALSE for a window whose detail fetch is
717
+ * deliberately skipped — a `new-` draft, a duplicate, or a host that never
718
+ * called `setShellApiClient`.
719
+ *
720
+ * This has to be passed, because a DISABLED TanStack query is
721
+ * `status: 'pending'` / `fetchStatus: 'idle'` forever, so `isPending` alone
722
+ * reads as "still loading" for a request that will never be made — and the
723
+ * window sits on a spinner it can never leave. `isLoading` (`isPending &&
724
+ * isFetching`) is the pair that already accounts for this, which is why the
725
+ * old inline check did not have the bug.
726
+ */
727
+ enabled?: boolean;
728
+ error: unknown;
729
+ onRetry: () => void;
730
+ }
731
+ declare function EntityWindowLoading(): react_jsx_runtime.JSX.Element;
732
+ /**
733
+ * The no-data half of an entity window: what to show when there is no entity
734
+ * to render yet, or never will be. Split out from `EntityWindowState` because
735
+ * a `rendersOwnModal` entry supplies its own Modal only once it HAS an entity,
736
+ * so these states need a modal of their own to live inside.
737
+ */
738
+ declare function EntityWindowFallback({ isPending, isFetching, enabled, error, onRetry, }: EntityWindowFallbackProps): react_jsx_runtime.JSX.Element;
739
+ /**
740
+ * Owns the no-data states for an entity window. Existing data always wins so
741
+ * background refreshes never replace a usable detail with transient chrome.
742
+ */
743
+ declare function EntityWindowState<T>({ entity, children, ...fallback }: EntityWindowStateProps<T>): react_jsx_runtime.JSX.Element;
744
+
745
+ /**
746
+ * Frame-timing maths + bottleneck attribution for the desktop perf HUD.
747
+ *
748
+ * Split out from `PerfStats.tsx` because the interesting part is the
749
+ * *attribution*, and attribution is pure: given a frame rate and how much of
750
+ * the wall clock the main thread spent blocked, decide whether a janky UI is
751
+ * the GPU's fault or JavaScript's. That decision is what the HUD exists for —
752
+ * "the UI feels laggy" is not actionable, "you are GPU-bound" is — so it lives
753
+ * where a test can pin it.
754
+ *
755
+ * The discriminator: frames are landing late, but the main thread is idle.
756
+ * If JS were the problem the main thread would be busy, so late frames plus an
757
+ * idle thread means the cost is downstream of script — compositing, paint,
758
+ * backdrop-filter. The shell leans hard on frosted glass (`utils/glass.ts`
759
+ * blurs at a 40px radius), which is exactly the kind of work that shows up
760
+ * here and nowhere in a JS profile.
761
+ */
762
+ /** Frame rate at or above which we call the UI smooth. Below 60 because
763
+ * rAF sampling is noisy and a display may be capped at 60Hz anyway — a
764
+ * steady 55 is not a complaint anyone would file. */
765
+ declare const SMOOTH_FPS = 50;
766
+ /** Share of wall-clock time in long tasks that makes JS the prime suspect.
767
+ * `longtask` entries are >50ms by definition, so even 20% means the thread
768
+ * is stalling several times a second — visible as jank on its own. */
769
+ declare const BLOCKED_PCT_CPU = 20;
770
+ type BottleneckKind = 'smooth' | 'gpu' | 'cpu' | 'unknown';
771
+ interface PerfReading {
772
+ /** Frames per second across the sample window. */
773
+ fps: number;
774
+ /** Mean frame interval, ms. */
775
+ frameMs: number;
776
+ /** Slowest single frame in the window, ms — where the jank actually lives. */
777
+ worstMs: number;
778
+ /** Percentage of wall-clock time the main thread spent inside long tasks
779
+ * (0–100), or null when the browser exposes no `longtask` observer.
780
+ * Null is load-bearing: without it we cannot separate GPU from CPU, and
781
+ * guessing would defeat the point of the HUD. */
782
+ blockedPct: number | null;
783
+ }
784
+ interface Verdict {
785
+ kind: BottleneckKind;
786
+ /** Headline, for the HUD's verdict row. */
787
+ label: string;
788
+ /** One line of what to do about it. */
789
+ detail: string;
790
+ }
791
+ /** Turn a window of rAF timestamps into a frame-rate reading.
792
+ * Needs two timestamps to describe one interval; anything less is not yet a
793
+ * measurement and reports zeroes rather than a fabricated 0 fps. */
794
+ declare function summariseFrames(timestamps: number[]): Pick<PerfReading, 'fps' | 'frameMs' | 'worstMs'>;
795
+ /**
796
+ * Attribute a reading to a bottleneck.
797
+ *
798
+ * Order matters: smooth wins outright (nobody cares what a healthy frame rate
799
+ * is *not* bound by), and an unknown block share can only ever downgrade to
800
+ * `unknown` — never to `gpu`. Blaming the GPU because the browser withheld
801
+ * long-task timing would be a confident wrong answer, which is worse than no
802
+ * answer when someone is about to go change settings on the strength of it.
803
+ */
804
+ declare function classify(reading: PerfReading): Verdict;
805
+
806
+ /**
807
+ * Session log for the desktop perf HUD — and the analysis that turns it into
808
+ * a conclusion.
809
+ *
810
+ * A live frame rate tells you the UI is slow *now*. It does not tell you what
811
+ * made it slow, and the person watching it is usually the last person able to
812
+ * say. So while the HUD is on, every reading is stamped with what was
813
+ * happening around it: how many windows were open, which one was on top,
814
+ * whether the user was clicking, typing, scrolling or dragging. The log is
815
+ * exportable, so a laggy machine somewhere else can produce evidence rather
816
+ * than an adjective.
817
+ *
818
+ * The maths here is deliberately non-clever. Median rather than mean, because
819
+ * one 400ms stall would drag a mean somewhere no frame ever was. Buckets
820
+ * rather than a fitted curve, because "6 windows open halves the frame rate"
821
+ * is a sentence someone can act on and a correlation coefficient is not.
822
+ */
823
+
824
+ /** One flush interval's worth of measurement plus its context. Keys are short
825
+ * because thousands of these get JSON-serialised into localStorage and into
826
+ * whatever the user sends afterwards. */
827
+ interface PerfLogRecord {
828
+ /** Milliseconds since logging began. */
829
+ t: number;
830
+ fps: number;
831
+ frameMs: number;
832
+ worstMs: number;
833
+ blockedPct: number | null;
834
+ heapMB: number | null;
835
+ verdict: BottleneckKind;
836
+ /** Open shell windows at the moment of the reading. */
837
+ windows: number;
838
+ /** Identity of the topmost window, when there is one — so a summary can name
839
+ * the screen that was slow rather than just the count. */
840
+ active: string | null;
841
+ clicks: number;
842
+ keys: number;
843
+ scrolls: number;
844
+ /** Milliseconds spent with the pointer down and moving. Dragging is the
845
+ * single most compositing-heavy thing a user does in a window shell, so it
846
+ * gets its own axis rather than being lumped in with clicks. Covers any
847
+ * drag — a window gesture, a desktop icon, a text selection; `moveMs` and
848
+ * `resizeMs` below are the window subset of it. */
849
+ dragMs: number;
850
+ /** Start-menu opens (`perfEvents`). */
851
+ menus: number;
852
+ /** Flyout opens — 2nd- and 3rd-level menus. Hover-opened flyouts fire no
853
+ * click and no keypress, so before this axis existed the frame a submenu
854
+ * painted on was filed as *idle*, which is where the jank people actually
855
+ * report was going missing. */
856
+ submenus: number;
857
+ /** Last menu or flyout opened in the interval, so one can be named. */
858
+ menuKey: string | null;
859
+ /** Milliseconds spent dragging a window by its title bar. */
860
+ moveMs: number;
861
+ /** Milliseconds spent dragging a window's resize handle. */
862
+ resizeMs: number;
863
+ }
864
+ interface FpsGroup {
865
+ samples: number;
866
+ /** Median across the group's *measurable* samples — see `summariseLog`. Zero
867
+ * when every sample in the group stalled. */
868
+ medianFps: number;
869
+ /** Slowest single frame anywhere in the group. For a group of brief
870
+ * interactions this is the number that matters: a flyout that costs one
871
+ * 300ms frame barely moves a median but is exactly what the user saw. */
872
+ worstMs: number;
873
+ /** Samples too blocked to report a frame rate at all. Kept beside the median
874
+ * rather than folded into it, so a group that is entirely stalls reads as
875
+ * the emergency it is instead of as a 0 fps reading. */
876
+ stalls: number;
877
+ }
878
+ /**
879
+ * What the user was doing during a sample, reduced to one label.
880
+ *
881
+ * A single interval routinely carries several axes at once — opening a flyout
882
+ * involves a click, a pointer move and a menu mark — so the ranking below
883
+ * decides which one the sample is filed under. It runs most-specific first:
884
+ * the deliberate, expensive gesture beats the incidental click that came with
885
+ * it, because filing a janky submenu open under "click" is how you end up
886
+ * optimising the wrong thing.
887
+ */
888
+ type ActivityKind = 'submenu' | 'menu' | 'resize' | 'move' | 'scroll' | 'type' | 'click' | 'idle';
889
+ /** File a sample under the one thing most likely to have cost it. */
890
+ declare function classifyActivity(r: PerfLogRecord): ActivityKind;
891
+ interface LogSummary {
892
+ samples: number;
893
+ durationMs: number;
894
+ medianFps: number;
895
+ worstFrameMs: number;
896
+ /** Fraction of samples in each verdict, 0–1. */
897
+ verdictShare: Record<BottleneckKind, number>;
898
+ /** Split by whether the user was doing anything. The gap between these two
899
+ * is the headline: a desktop that is smooth at rest and janky in use has a
900
+ * rendering cost that only shows up under interaction. */
901
+ interacting: FpsGroup | null;
902
+ idle: FpsGroup | null;
903
+ /** Frame rate per kind of interaction, worst-first — the answer to "which
904
+ * gesture is slow", which is the question a report is usually filed to
905
+ * ask. */
906
+ byActivity: (FpsGroup & {
907
+ kind: ActivityKind;
908
+ })[];
909
+ /** Frame rate against how many windows were open. */
910
+ byWindowCount: (FpsGroup & {
911
+ windows: number;
912
+ })[];
913
+ /** Windows ranked worst-first, so the slowest screen names itself. */
914
+ worstWindows: (FpsGroup & {
915
+ key: string;
916
+ })[];
917
+ /** Menus and flyouts ranked worst-first, same idea one layer up. */
918
+ worstMenus: (FpsGroup & {
919
+ key: string;
920
+ })[];
921
+ }
922
+ /** Records kept in memory before the oldest are dropped. At one record per
923
+ * 500ms this is about 20 minutes — long enough to catch an intermittent
924
+ * stall, small enough to serialise without thinking about it. */
925
+ declare const LOG_CAP = 2400;
926
+ /** Minimum samples before a group is reported. One unlucky reading is not a
927
+ * finding, and a summary that names a window off a single sample invites
928
+ * someone to go optimise the wrong screen. */
929
+ declare const MIN_GROUP_SAMPLES = 4;
930
+ /** The floor for menu and gesture groups. Lower because these events are rare
931
+ * by construction — a flyout opens inside a single 500ms interval, not across
932
+ * twenty of them, so holding them to the window floor would suppress exactly
933
+ * the findings the axes were added for. Two still rules out a one-off, and
934
+ * every group carries its own `samples` for whoever reads it. */
935
+ declare const MIN_EVENT_SAMPLES = 2;
936
+ /** Append with a cap, oldest dropped first. Returns a new array — callers hold
937
+ * this in React state, where mutation would not re-render. */
938
+ declare function appendRecord(log: PerfLogRecord[], record: PerfLogRecord, cap?: number): PerfLogRecord[];
939
+ /** True when the user was doing something during the interval. */
940
+ declare function isInteracting(r: PerfLogRecord): boolean;
941
+ /** Reduce a log to the handful of statements worth acting on. */
942
+ declare function summariseLog(log: PerfLogRecord[]): LogSummary;
943
+ /** Flat CSV, for opening in a spreadsheet without writing any code. */
944
+ declare function toCsv(log: PerfLogRecord[]): string;
945
+
946
+ /**
947
+ * What machine the readings came off.
948
+ *
949
+ * A frame rate without a machine attached is half a report. "GPU-bound
950
+ * (compositing)" means something very different on an Intel UHD 620 driving a
951
+ * 4K panel than on an M3 Max, and the first question anyone triaging a
952
+ * slowdown asks is which one they are looking at — a question the reporter is
953
+ * usually the worst-placed person to answer, and often simply cannot.
954
+ *
955
+ * The user agent alone does not settle it. Chromium freezes its UA string, so
956
+ * Windows 11 reports itself as Windows 10 and an ARM machine looks like an x86
957
+ * one; and no UA string has ever named the GPU, which is the part that matters
958
+ * most here. So this reads the specifics directly, from four sources:
959
+ *
960
+ * - **Synchronous navigator and screen fields** — cores, memory, panel,
961
+ * network, display settings. Cheap, and occasionally withheld.
962
+ * - **A WebGL context**, purely to ask the driver its own name. Created and
963
+ * destroyed inside one call, at report time and never during measurement,
964
+ * because a live context is exactly the sort of thing that would show up in
965
+ * the numbers it is meant to explain.
966
+ * - **Client hints and the battery**, both async, both unchanging, so both
967
+ * are resolved once when the HUD mounts rather than while a report is being
968
+ * assembled.
969
+ * - **Media queries**, for the settings that change how much compositing the
970
+ * browser is being asked to do in the first place.
971
+ *
972
+ * Everything is best-effort and every field degrades to null. Firefox and
973
+ * Safari withhold `deviceMemory`; Safari has no client hints; a blocklisted
974
+ * driver refuses WebGL. A report missing one line is a far smaller loss than a
975
+ * report that failed to send, so nothing here is allowed to throw.
976
+ */
977
+ /** Async platform detail from `navigator.userAgentData`. */
978
+ interface PlatformHints {
979
+ /** 'macOS', 'Windows', 'Linux', … */
980
+ platform: string | null;
981
+ /** The real OS version, which the frozen UA string no longer carries. */
982
+ platformVersion: string | null;
983
+ /** 'arm' | 'x86' — an Intel Mac and an Apple Silicon Mac are not the same
984
+ * machine and do not have the same compositing story. */
985
+ architecture: string | null;
986
+ /** Device model, on mobile. */
987
+ model: string | null;
988
+ /** Browser name and full version, e.g. 'Chrome 141.0.7390.55'. */
989
+ browser: string | null;
990
+ }
991
+ /** Power state. A laptop in Low Power Mode is capped at 30 fps by the OS, which
992
+ * is the single most common cause of "it halved this afternoon". */
993
+ interface BatteryInfo {
994
+ charging: boolean;
995
+ /** 0–1. */
996
+ level: number;
997
+ }
998
+ /** The parts that cannot be read synchronously, resolved once on mount. */
999
+ interface AsyncEnvironment {
1000
+ hints: PlatformHints | null;
1001
+ battery: BatteryInfo | null;
1002
+ }
1003
+ interface PerfEnvironment {
1004
+ userAgent: string;
1005
+ /** Browser name and version from client hints; null outside Chromium, where
1006
+ * `userAgent` above is the fallback. */
1007
+ browser: string | null;
1008
+ platform: string | null;
1009
+ platformVersion: string | null;
1010
+ architecture: string | null;
1011
+ model: string | null;
1012
+ /** Logical cores. The shell is single-threaded, so this mostly says how much
1013
+ * else the machine can be doing at once. */
1014
+ cpuCores: number | null;
1015
+ /** Approximate RAM in GB. Deliberately coarse — the spec has browsers round
1016
+ * to a power of two and permits clamping to 8 to blunt fingerprinting, so
1017
+ * treat it as a floor ("this much or more"), not a measurement. Chromium
1018
+ * only; Firefox and Safari publish nothing. */
1019
+ deviceMemoryGB: number | null;
1020
+ /** The driver's own name for itself, e.g. 'Apple M3 Max' or
1021
+ * 'ANGLE (Intel, Intel(R) UHD Graphics 620 …)'. */
1022
+ gpu: string | null;
1023
+ gpuVendor: string | null;
1024
+ /** False when the browser fell back to software rendering — which explains a
1025
+ * GPU-bound verdict outright. */
1026
+ webglAvailable: boolean;
1027
+ screen: {
1028
+ width: number;
1029
+ height: number;
1030
+ dpr: number;
1031
+ colorDepth: number | null;
1032
+ };
1033
+ /** The window, which is what actually gets composited — a maximised window on
1034
+ * a 4K panel and a small one are different amounts of work. */
1035
+ viewport: {
1036
+ width: number;
1037
+ height: number;
1038
+ };
1039
+ /** Heap ceiling this tab was given, MB. Chromium only. */
1040
+ heapLimitMB: number | null;
1041
+ /** Effective connection class, downlink Mb/s and round-trip ms. Separates a
1042
+ * UI that is slow from a UI that is waiting. */
1043
+ network: {
1044
+ effectiveType: string | null;
1045
+ downlinkMbps: number | null;
1046
+ rttMs: number | null;
1047
+ saveData: boolean;
1048
+ } | null;
1049
+ battery: BatteryInfo | null;
1050
+ /** Settings that change how much work the browser is being asked to do. */
1051
+ prefersReducedMotion: boolean;
1052
+ forcedColors: boolean;
1053
+ /** The shell's own "reduce transparency" — the first thing to suggest, and
1054
+ * the first thing to check has not already been tried. */
1055
+ reducedTransparency: boolean;
1056
+ /** Screens can be scaled; a report from a 4K panel at 200% is not the same as
1057
+ * one at 100%. */
1058
+ touchPoints: number | null;
1059
+ }
1060
+ /**
1061
+ * Resolve everything that needs a promise. Call once, keep the result — the
1062
+ * values cannot change for the life of the page, and the prompt some browsers
1063
+ * attach to high-entropy hints must not fire while someone is filing a report.
1064
+ *
1065
+ * Never rejects. A browser with no client hints and no battery API is the
1066
+ * normal case outside Chromium, not an error worth handling at the call site.
1067
+ */
1068
+ declare function requestAsyncEnvironment(): Promise<AsyncEnvironment>;
1069
+ /** Snapshot the machine. Synchronous — pass what `requestAsyncEnvironment`
1070
+ * resolved earlier, or null if it never arrived. */
1071
+ declare function readEnvironment(async_: AsyncEnvironment | null): PerfEnvironment;
1072
+ /**
1073
+ * One line naming the machine, for a report title or a bug-tracker
1074
+ * description — the place where a reader decides whether this report is about
1075
+ * a slow machine or a slow application. The full record is in the attachment;
1076
+ * this is the digest.
1077
+ *
1078
+ * Omits what it does not know rather than printing "unknown": a line of
1079
+ * absences reads as a broken probe, when in truth Firefox simply does not
1080
+ * publish RAM.
1081
+ */
1082
+ declare function describeMachine(env: PerfEnvironment): string;
1083
+
1084
+ /** A finished report, handed to the host to file. The JSON is pre-serialised
1085
+ * because the host's job is to attach it, not to re-derive it; `summary` and
1086
+ * `verdict` come along so a host can title or route the report without
1087
+ * parsing the attachment back apart. */
1088
+ interface PerfReport {
1089
+ /** What the user typed about what they were doing. May be empty. */
1090
+ message: string;
1091
+ /** Suggested attachment filename, stamped with the local time. */
1092
+ filename: string;
1093
+ /** The whole report — environment, summary and every raw record. */
1094
+ json: string;
1095
+ summary: LogSummary;
1096
+ /** The HUD's current headline, e.g. "GPU-bound (compositing)". */
1097
+ verdict: string;
1098
+ /** The machine the readings came off — GPU, cores, memory, OS, screen. A
1099
+ * verdict of "GPU-bound" is not actionable until you know which GPU. */
1100
+ environment: PerfEnvironment;
1101
+ }
1102
+ interface PerfStatsProps {
1103
+ /** Dismiss handler — the shell wires this to turn the pref back off, so the
1104
+ * HUD can be closed from itself rather than only from Preferences. */
1105
+ onClose?: () => void;
1106
+ /** File the report through the host's own feedback channel — the whole point
1107
+ * of the HUD, since a log that reaches nobody fixes nothing. Rejecting
1108
+ * surfaces the error and keeps the composer open with the text intact.
1109
+ * Without it the button downloads the same JSON instead, so the shell stays
1110
+ * usable standalone. */
1111
+ onSubmit?: (report: PerfReport) => void | Promise<void>;
1112
+ className?: string;
1113
+ }
1114
+ declare function PerfStats({ onClose, onSubmit, className }: PerfStatsProps): react_jsx_runtime.JSX.Element;
1115
+
1116
+ /**
1117
+ * INTERNAL stub — Desktop's What's New modal references the consumer-side
1118
+ * changelog. The package ships no built-in changelog; the consumer wires
1119
+ * their own through `DesktopHostConfig.productChangelog` (the What's New
1120
+ * dialog reads it first, and this empty default keeps the import compiling
1121
+ * when they don't). Layout's `branding` prop carries the rest of the visual
1122
+ * identity — name, logo, tagline.
1123
+ */
1124
+ interface ChangelogEntry {
1125
+ version: string;
1126
+ date: string;
1127
+ changes: string[];
1128
+ }
1129
+
1130
+ type PreviewFileKind = 'pdf' | 'dxf' | '3d' | 'image' | 'csv';
1131
+
1132
+ interface DesktopItem {
1133
+ entityType: string;
1134
+ entityId: string;
1135
+ label: string;
1136
+ x?: number;
1137
+ y?: number;
1138
+ folderId?: string;
1139
+ folderX?: number;
1140
+ folderY?: number;
1141
+ filePath?: string;
1142
+ fileKind?: PreviewFileKind;
1143
+ }
1144
+
1145
+ interface DesktopFolder {
1146
+ id: string;
1147
+ name: string;
1148
+ x?: number;
1149
+ y?: number;
1150
+ }
1151
+ interface StickyEntityRef {
1152
+ /** Window-registry key used by `openEntity`. */
1153
+ entityType: string;
1154
+ entityId: string;
1155
+ /** Optional human-readable label (defaults to `${prefix}#${number}`). */
1156
+ label?: string;
1157
+ /** Optional pre-fetched entity payload passed to openEntity as snapshot. */
1158
+ snapshot?: unknown;
1159
+ }
1160
+ type StickyResolver = (prefix: string, number: string) => Promise<StickyEntityRef | null>;
1161
+ /** Optional items in the desktop right-click menu that a consumer can hide via
1162
+ * {@link DesktopHostConfig.hiddenContextMenuItems} — e.g. when they're surfaced
1163
+ * under Preferences instead. `'customization'` is the Preferences/Customization
1164
+ * shortcut, `'favorites'` the Favorites shortcut, `'about'` the About item,
1165
+ * `'perf-stats'` the performance-overlay toggle. */
1166
+ type DesktopContextMenuItem = 'customization' | 'favorites' | 'about' | 'perf-stats';
1167
+ interface DesktopHostConfig {
1168
+ /** Product name shown in the About dialog and desktop context menu. */
1169
+ productName?: string;
1170
+ /** Tagline shown under the product name in the About dialog. */
1171
+ productTagline?: string;
1172
+ /** Icon URL shown in the About dialog. Defaults to `/favicon.svg`. */
1173
+ productIcon?: string;
1174
+ /** Version string shown on the desktop watermark and About dialog. Falls
1175
+ * back to the react-os-shell package version if omitted. */
1176
+ productVersion?: string;
1177
+ /** Changelog rendered in the "What's New" dialog. Hidden when omitted. */
1178
+ productChangelog?: ChangelogEntry[];
1179
+ /** Copyright line in the About dialog footer. Hidden when omitted. */
1180
+ productCopyright?: string;
1181
+ /** Website URL in the About dialog footer. Hidden when omitted. */
1182
+ productWebsite?: string;
1183
+ /** Wallpaper picker options for the Customization settings page. */
1184
+ wallpapers?: {
1185
+ src: string;
1186
+ label: string;
1187
+ }[];
1188
+ /** Resolves sticky-note refs (e.g. "SO#27150") to window-registry coords. */
1189
+ stickyResolver?: StickyResolver;
1190
+ /** Persists the user's desktop shortcut list. */
1191
+ saveShortcuts?: (items: DesktopItem[]) => void | Promise<void>;
1192
+ /** Persists the user's folder list. */
1193
+ saveFolders?: (folders: DesktopFolder[]) => void | Promise<void>;
1194
+ /** Persists the snap-to-grid preference. */
1195
+ saveSnap?: (snap: boolean) => void | Promise<void>;
1196
+ /** Persists the user's notepad / sticky-note content. */
1197
+ saveNotes?: (notes: unknown[]) => void | Promise<void>;
1198
+ /** Desktop right-click menu items to hide (all shown by default). Pass keys
1199
+ * here when the consumer surfaces them elsewhere — e.g. under Preferences. */
1200
+ hiddenContextMenuItems?: DesktopContextMenuItem[];
1201
+ /** Open the consumer's own "report a bug / suggestion" UI. When set, the
1202
+ * desktop and taskbar right-click menus show a "Suggestion or Bug" item that
1203
+ * calls this. Lets a consumer that files feedback natively (the shell itself
1204
+ * dropped bug reporting in v3.0.0) surface the familiar right-click entry. */
1205
+ onReportBug?: () => void;
1206
+ /** File a performance report from the desktop perf HUD through the same
1207
+ * feedback channel, with the session log attached. Unset, the HUD's button
1208
+ * downloads the report instead — which is a strictly worse outcome, because
1209
+ * a file on someone's Downloads folder is not a bug report. Rejecting shows
1210
+ * the error in the HUD and keeps what the user typed. */
1211
+ onSubmitPerfReport?: (report: PerfReport) => void | Promise<void>;
1212
+ }
1213
+ declare function DesktopHostProvider({ value, children }: {
1214
+ value: DesktopHostConfig;
1215
+ children: ReactNode;
1216
+ }): react_jsx_runtime.JSX.Element;
1217
+ declare function useDesktopHost(): DesktopHostConfig;
1218
+ declare function Desktop({ profile }: {
1219
+ profile: any;
1220
+ }): react_jsx_runtime.JSX.Element;
1221
+
1222
+ interface MobileAppConfig {
1223
+ /** Link the primary button opens — a native deep link, an App Store /
1224
+ * Play Store page, or a mobile-optimised web app. When omitted the screen
1225
+ * still renders (branding + message) but without a call-to-action. */
1226
+ url?: string;
1227
+ /** Primary button label. Defaults to `Open the app`. */
1228
+ ctaLabel?: string;
1229
+ /** Headline. Defaults to a message built from the product name. */
1230
+ heading?: string;
1231
+ /** Supporting copy under the headline. */
1232
+ description?: string;
1233
+ }
1234
+
1235
+ /**
1236
+ * The product's visual identity, in one object: the start-menu button, the
1237
+ * startup splash, the logout cover and the mobile landing all read from it.
1238
+ * The About dialog and What's New changelog belong to `DesktopHostConfig`
1239
+ * (`productName`, `productIcon`, `productChangelog`, …), which configures the
1240
+ * desktop; this configures the chrome around it.
1241
+ */
1242
+ interface ShellBranding {
1243
+ /** Product name — start-menu button, startup splash, mobile landing. */
1244
+ productName?: string;
1245
+ /** Logo URL — start-menu button, splash and logout covers. Defaults to `/favicon.svg`. */
1246
+ logo?: string;
1247
+ /** Apply the shared contrast treatment to arbitrary tenant artwork. */
1248
+ adaptiveLogo?: boolean;
1249
+ /** Server-detected transparency hint for `logo`. */
1250
+ logoHasAlpha?: boolean | null;
1251
+ /** Server-detected tone hint for `logo`. */
1252
+ logoIsLight?: boolean | null;
1253
+ /** Line under the product name on the splash and logout covers —
1254
+ * typically the company name. */
1255
+ tagline?: string;
1256
+ }
1257
+ interface LayoutProps {
1258
+ /** One-object form of the brand fields below; its fields win over them, so
1259
+ * a consumer can move over field by field. */
1260
+ branding?: ShellBranding;
1261
+ /** Brand label rendered on the start-menu button. */
1262
+ productName?: string;
1263
+ /** Icon URL rendered next to the brand label. Defaults to `/favicon.svg`. */
1264
+ productIcon?: string;
1265
+ /** Wallpaper image URLs for the desktop background. When omitted, falls back
1266
+ * to the wallpapers registered on the `DesktopHostProvider` (the same pool the
1267
+ * Customization picker shows); with neither, the desktop renders no image. */
1268
+ wallpapers?: string[];
1269
+ /** Override the default nav sections shown in the start menu. */
1270
+ navSections?: (NavSection | NavItem)[];
1271
+ /** Override the per-route icon map used by start menu and favorites. */
1272
+ navIcons?: Record<string, ReactNode>;
1273
+ /** Override the per-section header icon map used by start menu. */
1274
+ sectionIcons?: Record<string, ReactNode>;
1275
+ /** Override the start menu's section grouping + virtual flyouts. */
1276
+ categories?: StartMenuCategories;
1277
+ /** Notification system config — when omitted, the bell isn't rendered. */
1278
+ notifications?: NotificationsConfig;
1279
+ /** Universal search config — when omitted, Cmd-K opens the picker but no
1280
+ * results ever come back. */
1281
+ search?: SearchConfig;
1282
+ /** Custom node rendered in the system tray to the LEFT of the
1283
+ * notification bell (or above it on a vertical taskbar). Lets a host
1284
+ * app drop in icons like a server-status indicator without forking
1285
+ * the shell. The shell renders the node as-is — keep it small (a
1286
+ * single icon-sized button) so it fits the existing tray rhythm. */
1287
+ taskbarTrayLeft?: ReactNode;
1288
+ /** Taskbar clock popover config — lets the host surface per-day content
1289
+ * (e.g. tasks due that day) inside the mini month calendar. When omitted
1290
+ * the popover stays a plain date grid. */
1291
+ clockCalendar?: ClockCalendarConfig;
1292
+ /** Mobile-app link shown on phone/tablet-portrait viewports in place of the
1293
+ * desktop chrome. The shell has no built-in touch UI — wire a link to your
1294
+ * dedicated mobile app here (see MobileAppConfig). When omitted, small
1295
+ * screens get a plain "best viewed on desktop" notice. */
1296
+ mobileApp?: MobileAppConfig;
1297
+ /** The shell-wide right-click menu (`ShellContextMenu`). On by default;
1298
+ * `false` hands every right-click back to the browser — for a consumer
1299
+ * that is mid-migration, or that draws a menu of its own everywhere. */
1300
+ contextMenu?: boolean;
1301
+ }
1302
+ interface ClockCalendarConfig {
1303
+ /** Local dates (YYYY-MM-DD) to mark with a dot in the mini month grid —
1304
+ * typically days that have items on them. */
1305
+ markedDates?: string[];
1306
+ /** Render content for the selected day below the grid. The popover
1307
+ * defaults the selection to today when it opens, so clicking the clock
1308
+ * immediately shows today's items; clicking any day in the grid
1309
+ * re-renders the panel for that day (days outside the shown month also
1310
+ * flip the grid to their month). `close` dismisses the popover — call
1311
+ * it when a row click opens a window so the popup doesn't linger over
1312
+ * it. */
1313
+ renderDay?: (dateISO: string, api: {
1314
+ close: () => void;
1315
+ }) => ReactNode;
1316
+ }
1317
+ declare function Layout({ branding, productName, productIcon, wallpapers, navSections, navIcons, sectionIcons, categories, notifications, search, taskbarTrayLeft, clockCalendar, mobileApp, contextMenu, }?: LayoutProps): react_jsx_runtime.JSX.Element;
1318
+
1319
+ interface StartMenuProps {
1320
+ open: boolean;
1321
+ onClose: () => void;
1322
+ openPage: (path: string) => void;
1323
+ openWindows: {
1324
+ route?: string;
1325
+ label: string;
1326
+ }[];
1327
+ profile: any;
1328
+ user: any;
1329
+ onLogout: () => void;
1330
+ onNavigate: (path: string) => void;
1331
+ taskbarPosition: 'top' | 'bottom' | 'left' | 'right';
1332
+ taskbarH: number;
1333
+ taskbarW?: number;
1334
+ /** Gap (px) between the menu and the taskbar edge; set per taskbar size by Layout. */
1335
+ taskbarGap?: number;
1336
+ size?: 'small' | 'medium' | 'large';
1337
+ /** Override the default nav sections (sections + top-level items). */
1338
+ navSections?: (NavSection | NavItem)[];
1339
+ /** Per-route icon map for items in the menu. */
1340
+ navIcons?: Record<string, ReactNode>;
1341
+ /** Per-section header icon map. */
1342
+ sectionIcons?: Record<string, ReactNode>;
1343
+ /** Section grouping + virtual flyouts (Utilities-style trays). */
1344
+ categories?: StartMenuCategories;
1345
+ }
1346
+ declare function StartMenu({ open, onClose, openPage, profile, user, onLogout, taskbarPosition, taskbarH, taskbarW, taskbarGap, size, navSections, navIcons, sectionIcons, categories, }: StartMenuProps): react_jsx_runtime.JSX.Element | null;
1347
+
1348
+ /**
1349
+ * Widget gallery — the "manage widgets on the desktop" surface.
1350
+ *
1351
+ * Enumerates every widget-flagged page in the live window registry (so it
1352
+ * automatically lists whatever the consumer registered, not just the bundled
1353
+ * five) and cross-references the window manager's `openWindows` to know which
1354
+ * are currently sitting on the desktop. Adding a widget calls `openPage`
1355
+ * (which drops the chrome-less panel onto the desktop and persists it via the
1356
+ * normal open-windows session store); removing one calls `closeEntity` on the
1357
+ * live window. No new persistence layer — it drives the same plumbing the
1358
+ * Start Menu already uses.
1359
+ *
1360
+ * Surfaced from the desktop right-click menu ("Manage Widgets…") and exported
1361
+ * so a consumer can also register it as a window or wire it to a tray button.
1362
+ */
1363
+ declare function WidgetManager({ open, onClose }: {
1364
+ open: boolean;
1365
+ onClose: () => void;
1366
+ }): react_jsx_runtime.JSX.Element | null;
1367
+
1368
+ /** Sections that can be omitted from the inline Customization page when the
1369
+ * consumer renders them elsewhere (e.g. as separate entries in SystemPreferences). */
1370
+ type CustomizationOmitSection = 'behavior' | 'desktop';
1371
+ /** A logical group of the Customization page. Pass `section` to render only one
1372
+ * group, so the page can be split across separate SystemPreferences entries
1373
+ * (Appearance / Layout / Behavior). Omit `section` to render the whole page. */
1374
+ type CustomizationSection = 'appearance' | 'layout' | 'behavior';
1375
+ interface CustomizationProps {
1376
+ omit?: readonly CustomizationOmitSection[];
1377
+ /** Render only one logical group of settings. Omit to render everything. */
1378
+ section?: CustomizationSection;
1379
+ }
1380
+ declare function Customization({ omit, section }?: CustomizationProps): react_jsx_runtime.JSX.Element;
1381
+
1382
+ /**
1383
+ * Standalone Behavior settings panel — window position, double-click,
1384
+ * default window size, plus the two desktop-related toggles
1385
+ * (Show version on desktop, Enter full screen mode automatically).
1386
+ *
1387
+ * Reads/writes shell prefs via `useShellPrefs()`. Suitable for use as a
1388
+ * section in `SystemPreferences` or rendered standalone.
1389
+ */
1390
+ declare function BehaviorPanel(): react_jsx_runtime.JSX.Element;
1391
+
1392
+ /**
1393
+ * Standalone Sounds settings panel — sound effects toggle + per-event
1394
+ * sound-pack picker. Reads/writes the shell's `erp_sounds` and
1395
+ * `erp_sound_config` localStorage keys. Plays a preview when a pack is
1396
+ * picked. Suitable as a section in `SystemPreferences` or rendered
1397
+ * standalone.
1398
+ */
1399
+ declare function SoundsPanel(): react_jsx_runtime.JSX.Element;
1400
+
1401
+ /** One section in the SystemPreferences sidebar. */
1402
+ interface SystemPreferencesSection {
1403
+ /** Unique key, e.g. `'notifications'`, `'customization'`. */
1404
+ key: string;
1405
+ /** Sidebar label, e.g. "Notifications". */
1406
+ label: string;
1407
+ /** Short description shown under the label in the sidebar. */
1408
+ description?: string;
1409
+ /** Sidebar icon (any ReactNode — typically a 4×4 svg). */
1410
+ icon?: ReactNode;
1411
+ /** Body renderer. Receives no args; access shell prefs via your own hook. */
1412
+ render: () => ReactNode;
1413
+ }
1414
+ interface SystemPreferencesProps {
1415
+ /** Ordered sections — left sidebar lists these top-to-bottom, the matching
1416
+ * body renders on the right. The first section is selected by default
1417
+ * unless `defaultSelected` is supplied. */
1418
+ sections: readonly SystemPreferencesSection[];
1419
+ /** Key of the section to highlight on first render. Falls back to the first
1420
+ * section in `sections`. */
1421
+ defaultSelected?: string;
1422
+ /** Optional className applied to the outer flex container. */
1423
+ className?: string;
1424
+ }
1425
+ /**
1426
+ * Two-pane settings window: a fixed sidebar of sections on the left, the
1427
+ * active section's body on the right. Consumers compose by passing
1428
+ * `sections` — each entry has a `key`, `label`, optional `icon` and
1429
+ * `description`, and a `render()` callback that returns the body JSX.
1430
+ *
1431
+ * The shell exposes ready-made panels (`Customization`, `BehaviorPanel`,
1432
+ * `SoundsPanel`) that can be used as section bodies directly; portals
1433
+ * add their own sections (notification subscriptions, delivery defaults,
1434
+ * personal formatting prefs, etc.) alongside.
1435
+ *
1436
+ * Example:
1437
+ * ```tsx
1438
+ * <SystemPreferences sections={[
1439
+ * { key: 'notifications', label: 'Notifications', icon: <BellIcon />, render: () => <MyNotifications /> },
1440
+ * { key: 'customization', label: 'Customization', icon: <PaintBrushIcon />, render: () => <Customization omit={['behavior','desktop']} /> },
1441
+ * { key: 'behavior', label: 'Behavior', icon: <Cog6ToothIcon />, render: () => <BehaviorPanel /> },
1442
+ * { key: 'sounds', label: 'Sounds', icon: <SpeakerWaveIcon />, render: () => <SoundsPanel /> },
1443
+ * ]} />
1444
+ * ```
1445
+ */
1446
+ declare function SystemPreferences({ sections, defaultSelected, className, }: SystemPreferencesProps): react_jsx_runtime.JSX.Element;
1447
+
1448
+ interface ImageAnnotatorHandle {
1449
+ copy: () => Promise<void>;
1450
+ save: () => Promise<void>;
1451
+ }
1452
+ interface ImageAnnotatorProps {
1453
+ src: string;
1454
+ filename: string;
1455
+ /** Standalone-mode: when provided, an Apply pill is rendered in the
1456
+ * annotator's toolbar that composites the current canvas + SVG into a PNG
1457
+ * blob and hands it back. Used by callers like the bug-report dialog that
1458
+ * embed the annotator outside the Preview app and want the result. */
1459
+ onApply?: (blob: Blob) => void;
1460
+ /** Standalone-mode companion to `onApply` — Cancel pill that lets the
1461
+ * consumer dismiss the annotator without applying. */
1462
+ onCancel?: () => void;
1463
+ }
1464
+ declare const ImageAnnotator: react.ForwardRefExoticComponent<ImageAnnotatorProps & react.RefAttributes<ImageAnnotatorHandle>>;
1465
+
1466
+ interface EntityListColumn {
1467
+ key: string;
1468
+ label: string;
1469
+ defaultWidth?: number;
1470
+ minWidth?: number;
1471
+ defaultHidden?: boolean;
1472
+ sortField?: string;
1473
+ headerNode?: ReactNode;
1474
+ }
1475
+ /** A page-supplied entry in the right-click bulk menu (e.g. invoice Post /
1476
+ * Cancel). `onClick` receives the currently-selected rows. Set `divider` to
1477
+ * render a separator above the item, `danger` for a destructive tint. */
1478
+ interface EntityListContextAction<T = unknown> {
1479
+ label: string;
1480
+ onClick: (items: T[]) => void;
1481
+ danger?: boolean;
1482
+ disabled?: boolean;
1483
+ divider?: boolean;
1484
+ }
1485
+ interface EntityListProps<T> {
1486
+ items: T[];
1487
+ isLoading: boolean;
1488
+ emptyState: ReactNode;
1489
+ totalCount?: number;
1490
+ /** When true and no rows have loaded yet, render {@link ListLoadError}
1491
+ * (with a retry) instead of `emptyState` — so a failed fetch reads as an
1492
+ * error, not "nothing here". Wire `useInfiniteScroll`'s `isError`. A
1493
+ * mid-scroll next-page failure (rows already loaded) keeps the list.
1494
+ * Defaults to unset → behaviour unchanged for existing callers. */
1495
+ isError?: boolean;
1496
+ /** Retry handler for the error state — wire the data source's `refetch`.
1497
+ * It also backs the row menu's Refresh item. */
1498
+ onRetry?: () => void;
1499
+ /** Heading for the error state, e.g. "Couldn't load candidates". Omit for
1500
+ * {@link ListLoadError}'s generic "Couldn't load this list" — name the
1501
+ * entity where the list has one, so an outage reads as *this* list failing. */
1502
+ errorTitle?: string;
1503
+ /** Explanatory line under {@link EntityListProps.errorTitle}. Omit for
1504
+ * ListLoadError's generic connection/retry message. */
1505
+ errorMessage?: string;
1506
+ tableId: string;
1507
+ columns: EntityListColumn[];
1508
+ renderCell: (item: T, colKey: string) => ReactNode;
1509
+ getRowId?: (item: T) => string | number;
1510
+ sort?: {
1511
+ field: string;
1512
+ direction: 'asc' | 'desc';
1513
+ };
1514
+ onSort?: (field: string) => void;
1515
+ selected: Set<string | number>;
1516
+ setSelected: Dispatch<SetStateAction<Set<string | number>>>;
1517
+ onRowClick: (item: T) => void;
1518
+ onRowHover?: (item: T) => void;
1519
+ getRowClassName?: (item: T) => string;
1520
+ footerLabel: string;
1521
+ footerExtra?: ReactNode;
1522
+ sentinelRef?: RefObject<any>;
1523
+ isFetchingNextPage?: boolean;
1524
+ /** Perm codenames that gate "Save as default for all users" in the column
1525
+ * picker. Defaults to `['change_numberingconfig']` (admin-portal's proxy
1526
+ * for admin access). Pass `[]` to hide the link entirely. */
1527
+ saveDefaultPerms?: string[];
1528
+ /** Right-click row menu. Every list has one, with nothing to wire: Open (the
1529
+ * row right-clicked), Copy for text selected under the pointer, Copy
1530
+ * `<first column>` and Copy rows (the ticked rows as a table of the visible
1531
+ * columns in their on-screen order, which pastes into a spreadsheet as
1532
+ * cells), Select all, Clear selection, and Refresh when `onRetry` is wired.
1533
+ * Provide `exportEndpoint` (a list's `<base>/export_csv/` path, relative to
1534
+ * the api base) to add "Export selected to CSV", which downloads just the
1535
+ * ticked rows (`?ids=`, honouring the visible/ordered columns). Provide
1536
+ * `contextActions` for domain actions (e.g. invoice Post / Cancel); they sit
1537
+ * between the copy items and the selection items. */
1538
+ exportEndpoint?: string;
1539
+ exportFilename?: string;
1540
+ contextActions?: (items: T[]) => EntityListContextAction<T>[];
1541
+ }
1542
+ /**
1543
+ * The canonical pageless data grid for both portals. Wraps `<ResizableTable>`
1544
+ * with selection-checkbox logic, keyboard navigation, the standardized list
1545
+ * footer, an infinite-scroll sentinel hook-up, and a right-click row menu.
1546
+ *
1547
+ * Usage:
1548
+ *
1549
+ * const { sort, onSort } = useSort('mid', 'asc', 'suppliers');
1550
+ * const { items, totalCount, isLoading, isFetchingNextPage, sentinelRef } =
1551
+ * useInfiniteScroll<Supplier>({ queryKey: ['suppliers', filters],
1552
+ * fetchFn: (p) => getSuppliers(p) });
1553
+ *
1554
+ * <EntityList
1555
+ * items={items}
1556
+ * totalCount={totalCount}
1557
+ * isLoading={isLoading}
1558
+ * sentinelRef={sentinelRef}
1559
+ * isFetchingNextPage={isFetchingNextPage}
1560
+ * tableId="suppliers"
1561
+ * columns={SUPPLIER_COLUMNS}
1562
+ * renderCell={(s, k) => …}
1563
+ * sort={sort} onSort={onSort}
1564
+ * selected={selected} setSelected={setSelected}
1565
+ * onRowClick={(s) => openEntity('supplier', s.id, s, s.mid, '/suppliers')}
1566
+ * exportEndpoint="/suppliers/export_csv/" exportFilename="Suppliers.csv"
1567
+ * footerLabel="suppliers"
1568
+ * emptyState={<EmptyState message="No suppliers yet." />}
1569
+ * />
1570
+ */
1571
+ declare function EntityList<T>(props: EntityListProps<T>): react_jsx_runtime.JSX.Element;
1572
+
1573
+ interface ResizableTableProps {
1574
+ tableId: string;
1575
+ columns: ColumnDef[];
1576
+ sort?: SortState;
1577
+ onSort?: (field: string) => void;
1578
+ footer?: React.ReactNode;
1579
+ afterBody?: React.ReactNode;
1580
+ /** Permission codename(s) that gate the "Save as default for all users" link
1581
+ * in the column picker. The shell's `useShellAuth().hasAnyPerm` is queried
1582
+ * against this list. Defaults to a single placeholder permission; consumers
1583
+ * with no admin gate can pass `[]` to hide the link entirely. */
1584
+ saveDefaultPerms?: string[];
1585
+ children: (orderedColumns: (ColumnDef & {
1586
+ width: number;
1587
+ })[]) => React.ReactNode;
1588
+ }
1589
+ /**
1590
+ * Sticky-header, scrollable-body table primitive with:
1591
+ * - drag-to-reorder columns (with a blue drop-indicator line)
1592
+ * - resize columns by the right-edge handle
1593
+ * - hide/show columns via a glass popover picker
1594
+ * - admin-only "Save as default for all users" link in the picker
1595
+ * - per-user persistence (via the shell-registered apiClient)
1596
+ * - mobile-aware selection mode toggle
1597
+ */
1598
+ declare function ResizableTable({ tableId, columns, sort, onSort, footer, afterBody, saveDefaultPerms, children, }: ResizableTableProps): react_jsx_runtime.JSX.Element;
1599
+
1600
+ /**
1601
+ * Keyboard + shift-click navigation for table rows.
1602
+ *
1603
+ * - J/K or Arrow keys to move focus
1604
+ * - Enter to open the focused row (calls `onSelect`)
1605
+ * - Space to toggle the focused row's checkbox (calls `onToggle`)
1606
+ * - Shift+J/K to extend selection while moving
1607
+ * - Shift+click to range-select (calls `onSelectRange`, which must ADD the range
1608
+ * rather than toggle it — the clicked checkbox is ticked on that assumption)
1609
+ * - Cmd/Ctrl+A to select/deselect all (calls `onSelectAll`)
1610
+ *
1611
+ * Each list instance gates its document-level listeners on `useModalActive`
1612
+ * so two open lists don't both react to the same shift-click.
1613
+ */
1614
+ declare function useTableNav<T>(items: T[], onSelect: (item: T) => void, onToggle?: (item: T) => void, onSelectAll?: () => void, onSelectRange?: (from: number, to: number) => void): number;
1615
+
1616
+ /**
1617
+ * Resizable + reorderable + hideable column state for `<ResizableTable>`.
1618
+ *
1619
+ * Reads and persists per-user through the `<ShellPrefsProvider>` adapter
1620
+ * (`prefs.columns_{tableId}`), with a 1-second debounce on the save, and reads
1621
+ * per-viewport admin defaults through the shared
1622
+ * `GET /auth/default-columns/{tableId}?viewport=…` probe.
1623
+ *
1624
+ * The prefs half used to be a raw `GET /auth/me/` fired from a `[tableId]`
1625
+ * effect, outside react-query — so every list mount re-fetched the whole user
1626
+ * profile (~1.5 MB on prod), on a client with no cache adapter and no in-flight
1627
+ * dedupe, and a screen that also renders a column-aware CSV export paid for it
1628
+ * twice. The adapter already holds that profile in one cached query, which is
1629
+ * the same object the raw GET was reading `preferences` off (SG#00590).
1630
+ *
1631
+ * Three consequences worth knowing, because this is NOT behaviour-preserving:
1632
+ *
1633
+ * - **A `<ShellPrefsProvider>` is now required for server-side persistence.**
1634
+ * Without one, `useShellPrefs()` reads empty and drops saves, so column
1635
+ * config degrades to localStorage-only. All three consumers that render
1636
+ * `<EntityList>`/`<ResizableTable>` mount the provider.
1637
+ * - **Prefs and admin defaults resolve independently**, where the raw pair was
1638
+ * a single `Promise.all`. If the admin default lands while prefs are still
1639
+ * loading it applies first and the user's own config replaces it a beat
1640
+ * later, rather than never showing. `useSort` has always behaved this way;
1641
+ * the two hooks now agree. The precedence that used to be one `if/else if`
1642
+ * is now the `userSavedRef` guard below plus the order the two effects fire
1643
+ * in — `tests/columnConfigDefaultsPrecedence.test.tsx` pins both directions.
1644
+ * - **A failed save is silent here.** The old `useMutation` let a rejected
1645
+ * `PATCH` reach a consumer's global `MutationCache({ onError })` — the admin
1646
+ * portal toasted it. The adapter's `save` owns its own failures now, so a
1647
+ * consumer that wants that toast raises it inside the adapter.
1648
+ *
1649
+ * What it does NOT require is a `<QueryClientProvider>` — it used to, for the
1650
+ * `useMutation`. `useDefaultColumnConfig` falls back to a client the package
1651
+ * owns when no provider is mounted.
1652
+ */
1653
+ declare function useColumnConfig(tableId: string, defaultColumns: ColumnDef[]): {
1654
+ orderedColumns: {
1655
+ width: number;
1656
+ key: string;
1657
+ label: string;
1658
+ defaultWidth?: number;
1659
+ minWidth?: number;
1660
+ defaultHidden?: boolean;
1661
+ headerNode?: react__default.ReactNode;
1662
+ sortField?: string;
1663
+ }[];
1664
+ allColumns: {
1665
+ key: string;
1666
+ label: string;
1667
+ hidden: boolean;
1668
+ }[];
1669
+ onResizeStart: (idx: number, e: react__default.MouseEvent) => void;
1670
+ onDragStart: (idx: number) => void;
1671
+ onDragOver: (visIdx: number, e: react__default.DragEvent) => void;
1672
+ onDrop: (_visIdx: number, e: react__default.DragEvent) => void;
1673
+ onDragEnd: () => void;
1674
+ toggleColumn: (key: string) => void;
1675
+ resetColumns: () => void;
1676
+ draggedIdx: number | null;
1677
+ dropGap: number | null;
1678
+ };
1679
+
1680
+ interface UseInfiniteScrollOptions<T> {
1681
+ /** queryKey segments — arbitrary cache shapes (filters often include booleans,
1682
+ * dates, undefined). Callers pass their natural filter shape. */
1683
+ queryKey: (string | Record<string, unknown>)[];
1684
+ fetchFn: (params: Record<string, string>) => Promise<PaginatedResponse<T>>;
1685
+ /** Extra query-string params. Undefined values are dropped so axios doesn't
1686
+ * serialise them as "?key=undefined". */
1687
+ extraParams?: Record<string, string | undefined>;
1688
+ pageSize?: number;
1689
+ }
1690
+ /**
1691
+ * Wraps TanStack `useInfiniteQuery` with an IntersectionObserver-driven
1692
+ * sentinel so attaching `sentinelRef` to a div near the bottom of a scrollable
1693
+ * container auto-fires `fetchNextPage` as the user scrolls.
1694
+ *
1695
+ * Works inside modal windows too — the observer roots itself on the nearest
1696
+ * scrollable ancestor rather than the viewport.
1697
+ */
1698
+ declare function useInfiniteScroll<T>({ queryKey, fetchFn, extraParams, pageSize: _pageSize, }: UseInfiniteScrollOptions<T>): {
1699
+ items: T[];
1700
+ totalCount: number;
1701
+ isLoading: boolean;
1702
+ isError: boolean;
1703
+ error: Error | null;
1704
+ isFetchingNextPage: boolean;
1705
+ hasNextPage: boolean;
1706
+ sentinelRef: react.RefObject<HTMLDivElement>;
1707
+ refetch: (options?: _tanstack_query_core.RefetchOptions) => Promise<_tanstack_query_core.QueryObserverResult<_tanstack_query_core.InfiniteData<PaginatedResponse<T>, unknown>, Error>>;
1708
+ };
1709
+
1710
+ /** The part of a column `useSort` reads — `ColumnDef` and `EntityList`'s own
1711
+ * column both fit.
1712
+ *
1713
+ * Not `DataTable`: its columns nest inside groups and it offers a sort on
1714
+ * `sortable`, not on `sortField ?? key`. `DataTableColumn` satisfies this
1715
+ * interface structurally, so passing one typechecks and then drops sorts the
1716
+ * table does offer. `DataTable` owns its own sort state through
1717
+ * `onSortChange`. */
1718
+ interface SortableColumn {
1719
+ key: string;
1720
+ sortField?: string;
1721
+ }
1722
+ /**
1723
+ * Sort-state hook used by list pages. Returns:
1724
+ * - `sort` — current `{ field, direction }`
1725
+ * - `onSort(field)` — toggle direction if same field, else activate asc
1726
+ * - `ordering` — DRF-style string (`'-mid'` for desc, `'mid'` for asc) ready
1727
+ * to drop into a query-string param.
1728
+ *
1729
+ * Pass `tableId` (the same id the list gives `<ResizableTable>`/`<EntityList>`)
1730
+ * to persist the user's choice alongside their column config. Restore order:
1731
+ * per-user pref (`prefs.sort_{tableId}` via the ShellPrefs adapter) → the
1732
+ * admin-saved default on `/auth/default-columns/{tableId}/` → the page's
1733
+ * `defaultField`/`defaultDir`. localStorage mirrors the last-applied value so
1734
+ * the first render (before async prefs arrive) already uses the last-known
1735
+ * sort instead of flashing the default and refetching. Without `tableId` the
1736
+ * hook is pure in-memory state, exactly as before.
1737
+ *
1738
+ * The admin default is read through `useDefaultColumnConfig`, shared with
1739
+ * `useColumnConfig` so a list page fetches that row once rather than twice
1740
+ * (SG#00590). That probe is a react-query query, but this hook still needs no
1741
+ * `<QueryClientProvider>` in either form: with no provider above it the probe
1742
+ * falls back to a client the package owns (see `useDefaultColumnConfig`), and
1743
+ * the `tableId`-less form issues no request at all, as before.
1744
+ *
1745
+ * Pass `options.columns` (the same array the table gets) and a sort the
1746
+ * columns do not offer is replaced by the page default in `sort` and
1747
+ * `ordering` — a save on a column since given `sortField: ''`, or on a key
1748
+ * whose column now sorts on another `sortField`. Without it that stale save
1749
+ * goes out as `?ordering=` on every open; DRF drops a term it cannot order by,
1750
+ * so the list comes back unsorted for good. Without `columns` — or while the
1751
+ * array is still empty, which is a list page whose columns are not built yet
1752
+ * rather than a table that offers no sort — nothing is checked, as before.
1753
+ */
1754
+ declare function useSort(defaultField: string, defaultDir?: 'asc' | 'desc', tableId?: string, options?: {
1755
+ columns?: ReadonlyArray<SortableColumn>;
1756
+ }): {
1757
+ sort: SortState;
1758
+ onSort: (field: string) => void;
1759
+ ordering: string;
1760
+ };
1761
+
1762
+ interface FilterOption {
1763
+ label: string;
1764
+ field: string;
1765
+ options: {
1766
+ value: string;
1767
+ label: string;
1768
+ }[];
1769
+ searchable?: boolean;
1770
+ }
1771
+ interface FilterBarProps {
1772
+ filters: FilterOption[];
1773
+ values: Record<string, string>;
1774
+ onChange: (field: string, value: string) => void;
1775
+ onClear: () => void;
1776
+ children?: ReactNode;
1777
+ }
1778
+ /**
1779
+ * FilterBar — a horizontal row of filter controls. Each filter renders a custom
1780
+ * listbox dropdown or, for long lists, a glass-styled searchable dropdown; a
1781
+ * "Clear filters" button appears when any filter is active. Pair with
1782
+ * `useFilters`.
1783
+ *
1784
+ * The short-list path used to render a native `<select>`, but a native select's
1785
+ * OS popup swallows every key event while open — so list-page hotkeys died
1786
+ * until it closed (BG#00421). PlainFilter is a custom listbox that keeps key
1787
+ * events flowing to the page.
1788
+ */
1789
+ declare function FilterBar({ filters, values, onChange, onClear, children }: FilterBarProps): react_jsx_runtime.JSX.Element;
1790
+ /** Local filter-state helper: tracks active filter values + derived params. */
1791
+ declare function useFilters(): {
1792
+ filterValues: Record<string, string>;
1793
+ setFilter: (field: string, value: string) => void;
1794
+ clearFilters: () => void;
1795
+ filterParams: Record<string, string>;
1796
+ };
1797
+
1798
+ /**
1799
+ * Shell interaction marks, folded into every perf-log record.
1800
+ *
1801
+ * The HUD can see that frames were dropped and it can see that the pointer
1802
+ * moved, but "the pointer moved" covers wandering across the desktop and
1803
+ * opening a third-level flyout, and only one of those repaints a frosted
1804
+ * surface. The first version of the log conflated them, so the readings that
1805
+ * mattered most — the frame the start menu opened on, the frame a window was
1806
+ * dragged across — arrived indistinguishable from an idle mouse. A report full
1807
+ * of anonymous samples points at nothing.
1808
+ *
1809
+ * So the places that do the expensive things say so. Menus mark themselves when
1810
+ * they open; window drags and resizes mark themselves for as long as they run.
1811
+ * Everything here is a plain counter drained once per flush interval — no
1812
+ * subscriptions, no allocation per event, nothing that would make marking an
1813
+ * event cost more than the event.
1814
+ *
1815
+ * Marks accumulate only while the HUD is mounted (`setPerfCollecting`). That is
1816
+ * not really about cost — incrementing a number is free — but about honesty: a
1817
+ * counter that had been climbing since page load would attribute a morning's
1818
+ * worth of menu opens to whichever 500ms interval happened to drain it first.
1819
+ */
1820
+ /** Which menu layer opened. The distinction is the point: the root menu is one
1821
+ * surface appearing, a flyout is a second one appearing *over* it, and the
1822
+ * cost of the second is what people report. */
1823
+ type MenuLayer = 'menu' | 'submenu';
1824
+ /** Record that a menu surface opened. `key` names it (a section label, a route)
1825
+ * so the summary can rank flyouts against each other. */
1826
+ declare function markMenuOpen(layer: MenuLayer, key: string): void;
1827
+ /**
1828
+ * Record a window drag or resize for as long as it runs. Returns the end
1829
+ * function; calling it twice adds the span once.
1830
+ *
1831
+ * Duration rather than a count because these are the gestures whose *cost is
1832
+ * their length* — a drag that stutters for four seconds and a drag that lasted
1833
+ * one frame are the same event and very different reports. The span is credited
1834
+ * to the interval it ends in, which can straddle a flush boundary; that is
1835
+ * accepted rather than apportioned, because a drag long enough to straddle two
1836
+ * intervals is already the thing being investigated.
1837
+ */
1838
+ declare function beginWindowGesture(kind: 'move' | 'resize'): () => void;
1839
+
1840
+ /** The description body for a filed performance report. */
1841
+ declare function describePerfReport(report: PerfReport): string;
1842
+ /** The attachment. A `File` rather than a `Blob` so the multipart part carries
1843
+ * the stamped filename the receiving end stores. */
1844
+ declare function perfReportFile(report: PerfReport): File;
1845
+
1846
+ /**
1847
+ * Semantic role of a column, used to auto-map CSV columns to fields and to
1848
+ * format the per-column totals. The first column is always the merge "key"
1849
+ * regardless of its declared kind.
1850
+ * - `key` — the identifier rows are de-duplicated/merged on (text-like).
1851
+ * - `price` — a money column (matched by `$`/decimals; totalled with 2 dp).
1852
+ * - `qty` — a whole-number quantity column.
1853
+ * - `text` — anything else; never auto-mapped, never totalled.
1854
+ */
1855
+ type BulkColumnKind = 'key' | 'price' | 'qty' | 'text';
1856
+ interface BulkColumn {
1857
+ key: string;
1858
+ title: string;
1859
+ width?: number;
1860
+ required?: boolean;
1861
+ /**
1862
+ * Hint for CSV auto-mapping and totals. Defaults: the first column is `key`,
1863
+ * every other column is `text`. Set `price`/`qty` to opt a column into
1864
+ * auto-detection and the totals strip.
1865
+ */
1866
+ kind?: BulkColumnKind;
1867
+ }
1868
+ interface BulkImportGridProps {
1869
+ /** Column definitions. The FIRST column is always the merge key. */
1870
+ columns: BulkColumn[];
1871
+ /** Called with the resolved, de-duplicated rows (one object per row, keyed by column key). */
1872
+ onImport: (rows: Record<string, string>[]) => Promise<void>;
1873
+ /** Called when the user cancels an in-progress mapping/duplicate review. */
1874
+ onCancel: () => void;
1875
+ /** Optional override for the help text shown above the grid. */
1876
+ description?: string;
1877
+ /**
1878
+ * When set, duplicate key values are merged into a single row with their
1879
+ * numeric columns summed, instead of offering keep-first / keep-last / skip.
1880
+ * Use for quantity-based imports where two rows of the same key mean "both" —
1881
+ * never for price imports, where summing prices would be nonsensical.
1882
+ */
1883
+ mergeDuplicates?: boolean;
1884
+ }
1885
+ /**
1886
+ * Spreadsheet-style bulk entry surface: type, paste, or upload a CSV/TSV, with
1887
+ * automatic column auto-mapping (with a manual mapping fallback) and duplicate
1888
+ * de-duplication (keep-first/last/all/skip, or summed merge in `mergeDuplicates`
1889
+ * mode). Purely presentational — owns local grid/CSV state and reports the
1890
+ * resolved rows via `onImport`; it does no fetching, auth, or persistence.
1891
+ *
1892
+ * Rows are de-duplicated and merged on the FIRST column (the "key"). Declare a
1893
+ * column's `kind` (`price`/`qty`) to opt it into CSV auto-detection and the
1894
+ * totals strip.
1895
+ */
1896
+ declare function BulkImportGrid({ columns, onImport, description, mergeDuplicates }: BulkImportGridProps): react_jsx_runtime.JSX.Element;
1897
+
1898
+ /**
1899
+ * Pure helpers for reconciling a bulk-import grid (rows of string cells) against
1900
+ * an existing items array, keyed by a designated "key column" (e.g. a part
1901
+ * number, SKU, or code). No app/framework dependencies — usable by any consumer
1902
+ * of {@link BulkImportGrid}.
1903
+ *
1904
+ * Merge rule: a blank cell means "don't touch the original" — only an explicit
1905
+ * value (including "0") replaces what's already there. Existing rows the user
1906
+ * didn't mention in the bulk grid are left untouched. Unknown keys are appended
1907
+ * via the caller-supplied `newItem` factory.
1908
+ */
1909
+ type BulkRow = Record<string, string | undefined>;
1910
+ interface DuplicateGroup {
1911
+ /** Key value as it appeared in the first occurrence (trimmed, original casing). */
1912
+ key: string;
1913
+ /** Indices into the rows array where this key appears. Length is always >= 2. */
1914
+ rowIndices: number[];
1915
+ }
1916
+ /**
1917
+ * Find key values that appear more than once within a single bulk-import file.
1918
+ * Match is case-insensitive (consistent with {@link mergeBulkItems}). Blank key
1919
+ * cells are ignored. The caller is expected to surface these to the user before
1920
+ * merge so they can pick a per-group resolution.
1921
+ */
1922
+ declare function findDuplicateKeys(rows: BulkRow[], keyRowKey: string): DuplicateGroup[];
1923
+ interface BaseItem {
1924
+ [key: string]: unknown;
1925
+ }
1926
+ interface MergeBulkResult<T> {
1927
+ merged: T[];
1928
+ importedCount: number;
1929
+ }
1930
+ interface MergeBulkOptions<T extends BaseItem> {
1931
+ /** Raw rows from BulkImportGrid. */
1932
+ rows: BulkRow[];
1933
+ /** Existing items array to merge into. */
1934
+ existing: T[];
1935
+ /**
1936
+ * Mapping from row column key → item field key.
1937
+ * MUST include the key-column mapping (e.g. `{ pn: 'part_number' }`).
1938
+ * Example: `{ pn: 'part_number', qty: 'quantity', price: 'unit_price' }`.
1939
+ */
1940
+ fieldMap: Record<string, string>;
1941
+ /**
1942
+ * The item field that holds the merge key — the value `fieldMap` maps the
1943
+ * grid's key column onto. Existing items are matched against this field
1944
+ * (case-insensitive). Defaults to `'part_number'`.
1945
+ */
1946
+ keyField?: string;
1947
+ /**
1948
+ * Build a brand-new item for a row whose key isn't in `existing`.
1949
+ * `filled` contains only the fields the user actually entered (mapped to
1950
+ * item-field keys via `fieldMap`); supply defaults for anything required
1951
+ * by your item shape.
1952
+ */
1953
+ newItem: (filled: Partial<T>) => T;
1954
+ }
1955
+ declare function mergeBulkItems<T extends BaseItem>(options: MergeBulkOptions<T>): MergeBulkResult<T>;
1956
+
1957
+ interface UndoControlsProps {
1958
+ /** Extra classes for the wrapping row. */
1959
+ className?: string;
1960
+ /**
1961
+ * Set by the shell on the pair it mounts itself — the window footer's, or a
1962
+ * nested provider's. A form's own `<UndoControls />` leaves it unset and
1963
+ * claims the stack, and the shell then mounts nothing for it — so a form
1964
+ * written before the shell did this keeps its pair where it put it, and
1965
+ * shows one rather than two.
1966
+ */
1967
+ auto?: boolean;
1968
+ }
1969
+ /**
1970
+ * Undo/Redo for the enclosing form, reading the stack from {@link UndoProvider}.
1971
+ *
1972
+ * The shell mounts this pair itself, in the window footer's left slot, for any
1973
+ * window whose form has registered state and whose user may edit it — a form
1974
+ * has nothing to add for the buttons to appear. Mount it by hand only for a
1975
+ * place the footer is not: a section header, a toolbar of a page with no
1976
+ * window footer. The keys work without it; this is for discoverability and for
1977
+ * the mouse. Both buttons stay rendered and go disabled, so the row does not
1978
+ * reflow the moment there is something to undo.
1979
+ *
1980
+ * Renders nothing for a user who may not edit the record — dead buttons on a
1981
+ * read-only form read as something broken rather than something withheld —
1982
+ * and nothing while the shell's own pair is showing for the same stack.
1983
+ */
1984
+ declare function UndoControls({ className, auto }: UndoControlsProps): react_jsx_runtime.JSX.Element | null;
1985
+
1986
+ /** Result of a single health poll. `latencyMs` is optional; when omitted on
1987
+ * an ok result the indicator simply shows no latency for that poll. */
1988
+ interface HealthCheckResult {
1989
+ ok: boolean;
1990
+ latencyMs?: number;
1991
+ }
1992
+ /** Display fields for the signed-in user shown in the popover's Session
1993
+ * section. All optional — the indicator derives a sensible name and hides
1994
+ * the Role row when `role` is empty. Pass `null`/`undefined` for "not
1995
+ * signed in". */
1996
+ interface ServerStatusUser {
1997
+ name?: string;
1998
+ email?: string;
1999
+ /** Secondary label for the Role row (e.g. group names, portal, company).
2000
+ * Hidden when empty. */
2001
+ role?: string;
2002
+ }
2003
+ interface ServerStatusIndicatorProps {
2004
+ /**
2005
+ * Performs one health poll and resolves with the outcome. The shell calls
2006
+ * this on mount and on every interval tick. A custom implementation owns
2007
+ * its own timeout/abort. When omitted, the indicator polls `healthUrl`
2008
+ * (default `/api/health/`) with a `requestTimeoutMs` AbortController.
2009
+ */
2010
+ healthCheck?: () => Promise<HealthCheckResult>;
2011
+ /** URL used by the built-in fetcher when no `healthCheck` is supplied. */
2012
+ healthUrl?: string;
2013
+ /** Poll interval in ms. Default 15 000. */
2014
+ pollMs?: number;
2015
+ /** Per-request timeout for the built-in fetcher, in ms. Default 5 000. */
2016
+ requestTimeoutMs?: number;
2017
+ /** Signed-in user's display fields for the Session section. */
2018
+ user?: ServerStatusUser | null;
2019
+ /** Build/version label for the Build section. Defaults to the shell's own
2020
+ * package version. Pass the consuming app's version here. */
2021
+ version?: string;
2022
+ }
2023
+ /**
2024
+ * System-tray globe that polls a health check and exposes connection details
2025
+ * in a click-to-open popover. Drop into a taskbar tray alongside the
2026
+ * notification bell; supply `healthCheck` (or `healthUrl`), `user`, and
2027
+ * `version` via a thin per-app wrapper.
2028
+ */
2029
+ declare function ServerStatusIndicator({ healthCheck, healthUrl, pollMs, requestTimeoutMs, user, version, }: ServerStatusIndicatorProps): react_jsx_runtime.JSX.Element;
2030
+
2031
+ interface ChangePasswordFormProps {
2032
+ /**
2033
+ * Perform the password change. Resolve to indicate success; reject with an
2034
+ * `Error` whose `message` becomes the inline error shown to the user.
2035
+ */
2036
+ onSubmit: (oldPassword: string, newPassword: string) => Promise<void>;
2037
+ /** Called after a successful change — on Done click, or immediately when
2038
+ * `confirmOnSuccess` is false. */
2039
+ onSuccess?: () => void;
2040
+ /**
2041
+ * Show a success confirmation screen with a Done button after a successful
2042
+ * change. When false, `onSuccess` fires immediately on success. Default true.
2043
+ */
2044
+ confirmOnSuccess?: boolean;
2045
+ /** Minimum length required for the new password. Default 8. */
2046
+ minLength?: number;
2047
+ /** Label for the submit button. Default 'Change Password'. */
2048
+ submitLabel?: string;
2049
+ /** Label for the Done button on the success screen. Default 'Done'. */
2050
+ doneLabel?: string;
2051
+ }
2052
+ /**
2053
+ * Product-agnostic change-password form. The host supplies `onSubmit` to do the
2054
+ * actual change (e.g. call an API then re-authenticate).
2055
+ */
2056
+ declare function ChangePasswordForm({ onSubmit, onSuccess, confirmOnSuccess, minLength, submitLabel, doneLabel, }: ChangePasswordFormProps): react_jsx_runtime.JSX.Element;
2057
+
2058
+ interface PdfActionButtonProps {
2059
+ /**
2060
+ * Resolves the PDF bytes. The consumer owns the transport (axios/fetch/etc.)
2061
+ * and any error reporting; returning `null` aborts the action without the
2062
+ * shell surfacing a toast (the consumer is expected to have explained the
2063
+ * failure already).
2064
+ */
2065
+ fetchPdf: () => Promise<Blob | null>;
2066
+ /** Filename used for download and shown in the Preview window (e.g. "Invoice_CI#1234.pdf"). */
2067
+ filename: string;
2068
+ /** Button label. */
2069
+ label?: string;
2070
+ /** Override the default button styling. */
2071
+ className?: string;
2072
+ /** Disabled state. */
2073
+ disabled?: boolean;
2074
+ /**
2075
+ * Optional email handler. When supplied, a "Send by Email" item (with a
2076
+ * divider above it) is added to the menu and an Email button is wired into
2077
+ * the Preview window — both invoked with the already-fetched blob so the
2078
+ * consumer can hand it to its own composer. Omit it to hide email entirely.
2079
+ */
2080
+ onEmail?: (blob: Blob) => void;
2081
+ /**
2082
+ * Optional notification fired after a Preview window has been opened with the
2083
+ * resolved PDF (e.g. to drop a Recent Documents shortcut). Receives the
2084
+ * filename so the consumer can label the entry.
2085
+ */
2086
+ onPreviewOpened?: (filename: string) => void;
2087
+ /**
2088
+ * How to show the Preview. Defaults to the window manager's
2089
+ * `openPage('/preview')`; a routed app without windows (no
2090
+ * `WindowManagerProvider`, so that `openPage` does nothing) passes its own —
2091
+ * a tab, a route or an overlay that mounts the shell's `Preview`.
2092
+ *
2093
+ * It is called where `openPage` would be: after the "LOADING PDF"
2094
+ * placeholder is staged and before the fetch, so the viewer opens on the
2095
+ * placeholder and swaps the document in itself. `onPreviewOpened` is too
2096
+ * late for this: it fires once the document has arrived, so nothing would
2097
+ * show while it loads.
2098
+ */
2099
+ openPreview?: () => void;
2100
+ /** Optional leading icon override for the button (defaults to a document glyph). */
2101
+ icon?: ReactNode;
2102
+ /**
2103
+ * How long to wait for `fetchPdf()` before giving up, in milliseconds.
2104
+ * Defaults to 30 seconds. Pass `0` (or `Infinity`) to wait for ever.
2105
+ *
2106
+ * This exists because a promise that never settles used to leave the Preview
2107
+ * window on its "LOADING PDF" placeholder indefinitely, so a slow document
2108
+ * and a dead one were indistinguishable to the person waiting. Timing out
2109
+ * turns that silence into a stated failure. The request itself is not
2110
+ * cancelled — the consumer owns the transport (see `withTimeout`).
2111
+ */
2112
+ timeoutMs?: number;
2113
+ }
2114
+ /**
2115
+ * A dropdown PDF button that can Preview the document in the shell's Preview
2116
+ * window, Download it, or (when `onEmail` is supplied) hand the bytes to a
2117
+ * consumer-provided email composer.
2118
+ *
2119
+ * The shell is transport-agnostic: it never fetches the PDF itself. The
2120
+ * consumer injects a `fetchPdf()` resolver (typically wrapping its own HTTP
2121
+ * client) and the shell handles object-URL lifecycle, download, the loading
2122
+ * placeholder in Preview, and success toasts. App concerns (email composer,
2123
+ * recent-documents logging, where the Preview opens) are lifted to the
2124
+ * optional `onEmail` / `onPreviewOpened` / `openPreview` callbacks.
2125
+ */
2126
+ declare function PdfActionButton({ fetchPdf, filename, label, className, disabled, onEmail, onPreviewOpened, openPreview, icon, timeoutMs, }: PdfActionButtonProps): react_jsx_runtime.JSX.Element;
2127
+
2128
+ interface ShellAuth {
2129
+ /** Returns true if the current user has any of the listed permission codes. */
2130
+ hasAnyPerm: (perms: string[]) => boolean;
2131
+ }
2132
+ declare function ShellAuthProvider({ value, children }: {
2133
+ value: ShellAuth;
2134
+ children: ReactNode;
2135
+ }): react_jsx_runtime.JSX.Element;
2136
+ declare function useShellAuth(): ShellAuth;
2137
+
2138
+ type EntityFetcher = (endpoint: string, id: string) => Promise<any>;
2139
+ declare function ShellEntityFetcherProvider({ value, children, }: {
2140
+ value: EntityFetcher;
2141
+ children: ReactNode;
2142
+ }): react_jsx_runtime.JSX.Element;
2143
+ /** Returns the consumer-supplied entity fetcher, or a stub that throws
2144
+ * on call. Components that fetch entities call this hook eagerly so any
2145
+ * misconfiguration surfaces synchronously rather than at click-time. */
2146
+ declare function useShellEntityFetcher(): EntityFetcher;
2147
+
2148
+ /**
2149
+ * INTERNAL stub — bridges legacy `apiClient.get/patch('/auth/me/', ...)` calls
2150
+ * inside the shell to the new <ShellPrefsProvider> contract. This keeps the
2151
+ * package's copied source compiling without rewriting every call site at
2152
+ * once; consumer-supplied prefs flow through `<ShellPrefsProvider>`.
2153
+ *
2154
+ * Outside calls (anything not hitting `/auth/me/`) need the consumer to mount
2155
+ * <ShellEntityFetcherProvider> and wire `entityFetcher` on <WindowManager>.
2156
+ *
2157
+ * When `setShellApiClient` has not been called, HTTP methods resolve with an
2158
+ * empty payload instead of throwing — so consumers without a backend (the
2159
+ * Pages demo, for instance) can still mount the shell. A one-time console
2160
+ * warning surfaces the missing wiring without crashing the app.
2161
+ *
2162
+ * Long-term this file is removed when each call site migrates to
2163
+ * `useShellPrefs()` directly.
2164
+ */
2165
+
2166
+ /** Consumer registers their own axios instance — typically the same one
2167
+ * they use elsewhere. The package only delegates HTTP to this. */
2168
+ declare function setShellApiClient(instance: AxiosInstance): void;
2169
+
2170
+ /**
2171
+ * Shared task data model — used by the TodoList app, the Pomodoro widget,
2172
+ * and the Calendar app. Stored in `shellPrefs.todo_tasks` (a single array)
2173
+ * so all three views read/write the same source of truth.
2174
+ */
2175
+ interface TodoTask {
2176
+ /** Local UUID — stable across renames, used by Pomodoro for active-task pinning. */
2177
+ id: string;
2178
+ name: string;
2179
+ done: boolean;
2180
+ /** Optional deadline, formatted YYYY-MM-DD so it sorts as a string. Drives the Calendar badge. */
2181
+ dueDate?: string;
2182
+ /** Pomodoro estimate (count of focus blocks the user expects to spend). */
2183
+ estimated?: number;
2184
+ /** Pomodoro count completed against this task. */
2185
+ completed?: number;
2186
+ /** Free-text notes — surfaced in the Todo edit drawer. */
2187
+ notes?: string;
2188
+ /** Origin of the task — e.g. 'crm' for tasks backed by a CRM deal/contact. Set by a TodoProvider. */
2189
+ source?: string;
2190
+ /** Short context label shown as a badge (e.g. "Deal: Acme", "Contact: Jane"). Set by a TodoProvider. */
2191
+ contextLabel?: string;
2192
+ /** ISO timestamp — useful for "recently added" sorting. */
2193
+ createdAt: string;
2194
+ /** ISO timestamp — bumped on every local mutation. */
2195
+ updatedAt: string;
2196
+ }
2197
+
2198
+ /**
2199
+ * A consumer-supplied task backend. Registering one (opt-in) routes every
2200
+ * shell task surface through it instead of the prefs blob. Methods map to/from
2201
+ * the shell's `TodoTask` shape, including the optional `source`/`contextLabel`.
2202
+ */
2203
+ interface TodoProvider {
2204
+ list(): Promise<TodoTask[]>;
2205
+ create(input: Partial<Omit<TodoTask, 'id' | 'createdAt' | 'updatedAt'>> & {
2206
+ name: string;
2207
+ }): Promise<TodoTask>;
2208
+ update(id: string, patch: Partial<TodoTask>): Promise<TodoTask>;
2209
+ remove(id: string): Promise<void>;
2210
+ }
2211
+ /** Register (or clear with `null`) the backend task provider. Opt-in. */
2212
+ declare function setShellTodoProvider(next: TodoProvider | null): void;
2213
+
2214
+ interface ShellUser {
2215
+ email?: string;
2216
+ first_name?: string;
2217
+ last_name?: string;
2218
+ avatar_url?: string;
2219
+ }
2220
+ /** Consumer registers user-identity + logout handler once at app startup. */
2221
+ declare function setShellAuthBridge(bridge: {
2222
+ user?: ShellUser;
2223
+ logout?: () => void;
2224
+ }): void;
2225
+
2226
+ /**
2227
+ * Registers Alt+Shift+N (Windows) / Opt+Shift+N (Mac) hotkey to trigger a "New" action.
2228
+ * Uses e.code (physical key) to avoid locale/modifier character issues.
2229
+ * Only fires for the active (frontmost) window, mirroring useEditHotkey — without
2230
+ * the guard every mounted window's hook answered one keypress at once, so with
2231
+ * two or more list windows open Alt+Shift+N fired "New" in all of them.
2232
+ */
2233
+ declare function useNewHotkey(callback: () => void): void;
2234
+
2235
+ /**
2236
+ * Registers Alt+Shift+E (Windows) / Opt+Shift+E (Mac) hotkey to trigger an "Edit" action.
2237
+ * Uses e.code (physical key) to avoid locale/modifier character issues.
2238
+ * Only fires for the active (frontmost) modal.
2239
+ */
2240
+ declare function useEditHotkey(callback: (() => void) | null): void;
2241
+
2242
+ /**
2243
+ * Registers a handler for the Cmd+S "save and stay" event dispatched by Modal.
2244
+ * The callback should save the form data WITHOUT closing the modal.
2245
+ *
2246
+ * Fires ONLY for the window the keystroke was aimed at. Modal restricts the
2247
+ * dispatch to the topmost modal, but the event goes to `document`, so without
2248
+ * this match every mounted window's hook answered one keypress — and with two
2249
+ * forms open, a single Cmd+S saved both. Depending on each form's mode that
2250
+ * either PATCHed a background record or CREATEd a brand new one, each with its
2251
+ * own success toast, so the user saw the write and misread which window it came
2252
+ * from.
2253
+ *
2254
+ * The match is on the originating modal's id rather than on
2255
+ * {@link useModalActive}, which asks a different question: "is the frontmost
2256
+ * modal mine". Every nested dialog a form opens pushes itself onto the
2257
+ * activation order, so a form with a child dialog open would read as inactive
2258
+ * and Cmd+S would silently do nothing where it used to save.
2259
+ *
2260
+ * An event with no `detail.modalId` is treated as "for me", so a consumer on a
2261
+ * newer package than the shell that dispatched it keeps working exactly as
2262
+ * before rather than losing Cmd+S entirely.
2263
+ */
2264
+ declare function useModalSave(callback: (() => void) | null): void;
2265
+
2266
+ /**
2267
+ * Registers a handler for the Alt+Shift+D "save as new / duplicate" event
2268
+ * dispatched by Modal. The callback should save the form data as a NEW record
2269
+ * (ignoring the current ID).
2270
+ *
2271
+ * Window-scoped for the same reason as {@link useModalSave}, and the stakes are
2272
+ * higher here: an unscoped duplicate created a second record from every open
2273
+ * form at once, and one consumer closes its window on success, so a background
2274
+ * window vanished as well.
2275
+ */
2276
+ declare function useModalDuplicate(callback: (() => void) | null): void;
2277
+
2278
+ /**
2279
+ * Undo/redo for one open form — the pure half.
2280
+ *
2281
+ * A form window owns a single stack covering everything in it: its fields, its
2282
+ * line-items table, a bulk import. One ⌘Z steps back the last thing the user
2283
+ * did, whatever part of the form it happened in.
2284
+ *
2285
+ * The stack holds snapshots, not diffs. A step stores the value of every
2286
+ * registered slice **as it was before the change**, so undoing restores the
2287
+ * whole form to a coherent moment rather than replaying one slice out of step
2288
+ * with the others. Forms are small — a few dozen fields and some line items —
2289
+ * so whole-form snapshots stay cheap and the logic stays honest.
2290
+ *
2291
+ * Scope is the unsaved edit. History lives with the open window and dies with
2292
+ * it; nothing here reverses a saved record.
2293
+ */
2294
+ /** Registered slice id → that slice's value at the time of the snapshot. */
2295
+ type UndoSnapshot = Record<string, unknown>;
2296
+ interface UndoStep {
2297
+ /** The form as it stood before this change. Undo restores exactly this. */
2298
+ values: UndoSnapshot;
2299
+ /** What the step was, for a button title: `"qty"`, `"import of 5 lines"`. */
2300
+ label: string;
2301
+ /**
2302
+ * Consecutive changes sharing a non-null key fold into one step — typing in
2303
+ * a field is one Undo, not one per keystroke. Null never folds.
2304
+ */
2305
+ coalesceKey: string | null;
2306
+ }
2307
+ interface UndoState {
2308
+ past: UndoStep[];
2309
+ future: UndoStep[];
2310
+ }
2311
+
2312
+ interface UndoProviderProps {
2313
+ children: React.ReactNode;
2314
+ /**
2315
+ * Whether this user may edit the record. Undo is offered to everyone who
2316
+ * can — it is not gated on role or seniority — and withheld from a reader
2317
+ * only because they have nothing to take back. Defaults to true.
2318
+ */
2319
+ canEdit?: boolean;
2320
+ /**
2321
+ * Permission codes that count as "may edit", checked through
2322
+ * `ShellAuthProvider`. Combined with `canEdit`, so a form that already knows
2323
+ * it is read-only stays read-only whatever the codes say. Omit to rely on
2324
+ * `canEdit` alone.
2325
+ */
2326
+ perms?: string[];
2327
+ /**
2328
+ * The window this stack belongs to — the same stable key its `<Modal>` gets
2329
+ * as `windowKey`, which for a `WindowManager` window is `item.id`.
2330
+ *
2331
+ * This is what keeps ⌘Z inside the window the user is looking at. The
2332
+ * provider binds its hotkey on `window`, so every open window's provider sees
2333
+ * every keypress; the id is how one of them recognises the press as its own.
2334
+ * Without it there is no per-window identity to test and the best available
2335
+ * answer is "is any window active", which is true in all of them at once.
2336
+ *
2337
+ * Omit it for a provider nested inside a `<Modal>` — there the enclosing
2338
+ * modal's own context answers the question — or where there are no windows
2339
+ * at all.
2340
+ */
2341
+ windowId?: string;
2342
+ }
2343
+ /**
2344
+ * Undo/redo for everything in one open form.
2345
+ *
2346
+ * Wrap a form window in it, register each piece of its state with
2347
+ * {@link useUndoable}, and the whole form shares one stack: a field edit, a
2348
+ * line added, a bulk import are all steps in the same history, undone newest
2349
+ * first. Two open windows have two independent stacks, and passing
2350
+ * {@link UndoProviderProps.windowId} is what keeps them independent in
2351
+ * practice — see that prop.
2352
+ *
2353
+ * History is the unsaved edit only. It lives with the mounted provider and
2354
+ * dies with it. Both ends of the edit are the caller's to mark: `baseline()`
2355
+ * once the record has arrived, so the load is the starting point rather than
2356
+ * the first thing ⌘Z takes back, and `clear()` at a save, past which "earlier"
2357
+ * is on the server and not something a form can reach.
2358
+ *
2359
+ * Anyone who may edit the record gets it — it is not a privileged feature, and
2360
+ * the user most helped by an undo is the one least sure of what they just did.
2361
+ * A reader is the only one it is withheld from, and only because there is
2362
+ * nothing for them to take back. Note that `canEdit` is the caller's claim, and
2363
+ * the shell-level provider `WindowManager` mounts around every window has no
2364
+ * way to make it: it knows nothing about the record inside. That one defaults
2365
+ * to enabled, and a read-only form states the fact by nesting its own
2366
+ * `<UndoProvider canEdit={false}>`, which shadows it.
2367
+ */
2368
+ declare function UndoProvider({ children, canEdit, perms, windowId }: UndoProviderProps): react_jsx_runtime.JSX.Element;
2369
+ interface UndoControlsApi {
2370
+ undo: () => void;
2371
+ redo: () => void;
2372
+ canUndo: boolean;
2373
+ canRedo: boolean;
2374
+ /** What Undo/Redo would act on, for a button title. Null when unavailable. */
2375
+ undoLabel: string | null;
2376
+ redoLabel: string | null;
2377
+ /** End the history — call after a successful save. */
2378
+ clear: () => void;
2379
+ /**
2380
+ * Make the form as it stands the starting point, discarding the history.
2381
+ *
2382
+ * Call it once the record has arrived from the server. State filled from a
2383
+ * fetch is a change like any other as far as the slices are concerned, so
2384
+ * without this the load itself is the oldest step and the user's first ⌘Z
2385
+ * empties the form they were just given:
2386
+ *
2387
+ * const { data } = useQuery(...);
2388
+ * const { baseline } = useUndo();
2389
+ * useEffect(() => { if (data) baseline(); }, [data, baseline]);
2390
+ *
2391
+ * Worth calling on every arrival, not just the first: a window kept open
2392
+ * across a refetch, or one whose entity changes underneath it, wants the
2393
+ * same treatment, and the call is idempotent for a form nobody has touched.
2394
+ *
2395
+ * A history is only ever dropped when a record actually lands (a slice takes
2396
+ * a value while the baseline settles); a call on a refetch edge where the
2397
+ * form's guard re-seeds nothing leaves the user's edits undoable. Name the
2398
+ * record — `baseline(id)` — and a switch to another record drops the history
2399
+ * even when the new record's values happen to equal the old, so an undo can
2400
+ * never land on the wrong record.
2401
+ *
2402
+ * Name it from the first load on. The first named call has no earlier name
2403
+ * to compare with, so it never drops the history by itself: a switch away
2404
+ * from a record that was baselined bare keeps its history. And a record with
2405
+ * no id yet passes `null`, not `undefined` — `baseline(undefined)` is a bare
2406
+ * call, so `baseline(record?.id)` loses the switch guard on a create route;
2407
+ * write `baseline(record?.id ?? null)`.
2408
+ */
2409
+ baseline: (key?: string | number | null) => void;
2410
+ /** False when the user may not edit this record, so custom UI can hide
2411
+ * itself the way `UndoControls` does. */
2412
+ enabled: boolean;
2413
+ /** True once the form has registered any state — the window has something
2414
+ * an Undo could act on, and the shell shows the controls for it. */
2415
+ hasState: boolean;
2416
+ }
2417
+ /**
2418
+ * The enclosing form's undo stack, for custom UI or to `clear()` on save.
2419
+ * Everything is inert outside an {@link UndoProvider}.
2420
+ */
2421
+ declare function useUndo(): UndoControlsApi;
2422
+ /**
2423
+ * Tell the enclosing stack whether this record may be edited right now.
2424
+ *
2425
+ * `enabled` is otherwise the provider's own claim (`canEdit`, `perms`), and
2426
+ * the provider `WindowManager` mounts around every window makes none — it
2427
+ * knows nothing about the record inside. A form that does know says so here:
2428
+ *
2429
+ * useUndoCanEdit(!isLocked);
2430
+ *
2431
+ * and while the value is false the stack records nothing, ⌘Z is left to the
2432
+ * browser, and the footer shows no controls. A nested
2433
+ * `<UndoProvider canEdit={false}>` in the form's JSX says the same thing: it
2434
+ * shadows the stack for its children and forwards the read-only claim to the
2435
+ * stack above it — the one the form's own `useUndoableState` calls, made above
2436
+ * it in the tree, register with. Either form of the claim makes the whole
2437
+ * window read-only; the hook just needs no extra element. `useUndoCanEdit(true)`
2438
+ * is the positive claim: a window that edits in place outside Edit mode asks
2439
+ * the footer for the pair with it.
2440
+ */
2441
+ declare function useUndoCanEdit(canEdit: boolean): void;
2442
+ interface UndoableOptions {
2443
+ /** Names the step in a button title: `"qty"`, `"line items"`. */
2444
+ label: string;
2445
+ /**
2446
+ * Consecutive changes sharing a key fold into one step — pass the field name
2447
+ * so a run of typing is one Undo rather than one per keystroke. Omit for a
2448
+ * change that is already whole, like a bulk import or a deleted row.
2449
+ */
2450
+ coalesceKey?: string | null;
2451
+ }
2452
+ /**
2453
+ * Put one piece of the form's state under the window's undo stack.
2454
+ *
2455
+ * `value` is watched; when it changes, the form as it stood beforehand becomes
2456
+ * a step. `apply` puts a value back — the same setter the form already uses.
2457
+ *
2458
+ * const [items, setItems] = useState<Line[]>([]);
2459
+ * useUndoable(items, setItems, { label: 'line items' });
2460
+ *
2461
+ * Registering more than one slice is the point: a step snapshots all of them
2462
+ * together, so undoing restores a coherent form rather than one slice out of
2463
+ * step with the rest.
2464
+ *
2465
+ * `value` must be the state itself, not something derived from it on the way
2466
+ * in. A change is detected by identity, so `useUndoable(rows.filter(r => r.on),
2467
+ * ...)` hands over a new array on every render and reads as a change every
2468
+ * time — and since recording a step re-renders the provider, that closes a
2469
+ * loop at render speed. The provider trips a runaway guard and says so rather
2470
+ * than letting the tab hang, but the fix is at the call site: register the
2471
+ * state, and derive from it afterwards.
2472
+ */
2473
+ declare function useUndoable<T>(value: T, apply: (next: T) => void, opts: UndoableOptions): void;
2474
+ /**
2475
+ * `useState`, with the value in the window's undo stack.
2476
+ *
2477
+ * Written to be a rename rather than an extra line, because the forms this is
2478
+ * for hold their state in dozens of separate `useState` calls — one of them has
2479
+ * forty-three. Adopting a form is then a per-line edit:
2480
+ *
2481
+ * const [supplier, setSupplier] = useState('');
2482
+ * const [supplier, setSupplier] = useUndoableState('', { label: 'supplier' });
2483
+ *
2484
+ * Which also makes the choice legible: state left as plain `useState` is state
2485
+ * deliberately kept out of the history. Keep it that way for anything that is
2486
+ * not the user's input — a search box, fetched data, validation output, an
2487
+ * initialisation guard. Undoing those puts stale results back on screen, and a
2488
+ * reverted guard can re-fire the effect it exists to suppress.
2489
+ *
2490
+ * Pass `coalesceKey` for anything typed into, so a run of keystrokes is one
2491
+ * Undo; the field's own name is the obvious key.
2492
+ */
2493
+ declare function useUndoableState<T>(initial: T | (() => T), opts: UndoableOptions): [T, React.Dispatch<React.SetStateAction<T>>];
2494
+
2495
+ export { type ActivityKind, type AsyncEnvironment, BLOCKED_PCT_CPU, type BatteryInfo, BehaviorPanel, type BottleneckKind, type BulkColumn, type BulkColumnKind, BulkImportGrid, type BulkImportGridProps, type BulkRow, CancelButton, ChangePasswordForm, type ChangePasswordFormProps, type ChangelogEntry, type ClockCalendarConfig, ColumnDef, CopyButton, Customization, type CustomizationOmitSection, type CustomizationProps, type CustomizationSection, DEV_BANNER_TEXT, Desktop, type DesktopContextMenuItem, type DesktopHostConfig, DesktopHostProvider, DevIndicator, DocFavStar, type DuplicateGroup, type EntityFetcher, EntityList, type EntityListColumn, type EntityListContextAction, type EntityListProps, EntityWindowFallback, type EntityWindowFallbackProps, EntityWindowLoading, EntityWindowState, type EntityWindowStateProps, FilterBar, type FilterOption, type FpsGroup, type HealthCheckResult, ImageAnnotator, type ImageAnnotatorHandle, type ImageAnnotatorProps, LOG_CAP, Layout, type LayoutProps, type LogSummary, MIN_EVENT_SAMPLES, MIN_GROUP_SAMPLES, MODAL_SUBMIT_SELECTOR, type MenuLayer, type MergeBulkOptions, type MergeBulkResult, type MobileAppConfig, Modal, ModalActions, NATIVE_MENU_ATTR, NotificationBell, type NotificationsConfig, PaginatedResponse, PdfActionButton, type PdfActionButtonProps, type PerfEnvironment, type PerfLogRecord, type PerfReading, type PerfReport, PerfStats, type PerfStatsProps, type PlatformHints, ResizableTable, SMOOTH_FPS, SearchConfig, ServerStatusIndicator, type ServerStatusIndicatorProps, type ServerStatusUser, type ShellAuth, ShellAuthProvider, type ShellBranding, type ShellContextKind, ShellContextMenu, type ShellContextMenuProps, type ShellContextTarget, ShellEntityFetcherProvider, type ShellNotification, ShortcutHelp, SortState, type SortableColumn, SoundsPanel, type StagedTarget, StartMenu, type StickyEntityRef, type StickyResolver, SystemPreferences, type SystemPreferencesProps, type SystemPreferencesSection, type TodoProvider, type TodoTask, UndoControls, type UndoControlsApi, type UndoControlsProps, UndoProvider, type UndoProviderProps, type UndoSnapshot, type UndoState, type UndoStep, type UndoableOptions, type Verdict, WidgetManager, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowRegistry, type WindowTarget, type WindowTargetOptions, WindowTitle, appendRecord as appendPerfRecord, applyDevTitle, beginWindowGesture, classifyActivity, classify as classifyPerf, commitExposeHighlight, copyToClipboard, createWindowRegistry, createWindowTarget, describeContextTarget, describeMachine, describePerfReport, exitExposeMode, findDuplicateKeys, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isDevEnv, isInteracting, keepsNativeMenu, markMenuOpen, mergeBulkItems, toCsv as perfLogToCsv, perfReportFile, readEnvironment as readPerfEnvironment, registerModalEscapeInterceptor, requestAsyncEnvironment, requestWindowFront, setExposeHighlight, setShellApiClient, setShellAuthBridge, setShellNavIcons, setShellTodoProvider, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, summariseFrames, summariseLog as summarisePerfLog, toggleExposeMode, useColumnConfig, useDesktopHost, useEditHotkey, useEnclosingModalId, useFilters, useInfiniteScroll, useIsActiveWindow, useModalActive, useModalDuplicate, useModalSave, useNewHotkey, useShellAuth, useShellEntityFetcher, useSort, useTableNav, useUndo, useUndoCanEdit, useUndoable, useUndoableState, useWidgetSettings, useWindowDirty, useWindowManager, useWindowMenuItem, useWindowTarget, useWindowTitle };