@fgv/ts-app-shell 5.1.0-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +26 -0
  2. package/dist/index.browser.js +3 -0
  3. package/dist/index.js +43 -0
  4. package/dist/packlets/ai-assist/index.js +6 -0
  5. package/dist/packlets/ai-assist/useAiAssist.js +219 -0
  6. package/dist/packlets/cascade/CascadeContainer.js +83 -0
  7. package/dist/packlets/cascade/ComparisonView.js +48 -0
  8. package/dist/packlets/cascade/EntityTabLayout.js +104 -0
  9. package/dist/packlets/cascade/MobileCascadeStack.js +63 -0
  10. package/dist/packlets/cascade/index.js +37 -0
  11. package/dist/packlets/cascade/model.js +30 -0
  12. package/dist/packlets/cascade/useCascadeOps.js +206 -0
  13. package/dist/packlets/cascade/useCascadeTransitions.js +58 -0
  14. package/dist/packlets/detail/DetailHelpers.js +103 -0
  15. package/dist/packlets/detail/index.js +6 -0
  16. package/dist/packlets/drop-zone/JsonDropZone.js +112 -0
  17. package/dist/packlets/drop-zone/index.js +6 -0
  18. package/dist/packlets/editing/EditFieldHelpers.js +130 -0
  19. package/dist/packlets/editing/MultiActionButton.js +73 -0
  20. package/dist/packlets/editing/NumericInput.js +119 -0
  21. package/dist/packlets/editing/TypeaheadInput.js +207 -0
  22. package/dist/packlets/editing/index.js +10 -0
  23. package/dist/packlets/editing/useTypeaheadMatch.js +102 -0
  24. package/dist/packlets/keyboard/index.js +7 -0
  25. package/dist/packlets/keyboard/registry.js +133 -0
  26. package/dist/packlets/keyboard/useKeyboardShortcuts.js +117 -0
  27. package/dist/packlets/messages/MessagesContext.js +76 -0
  28. package/dist/packlets/messages/MessagesLogger.js +103 -0
  29. package/dist/packlets/messages/StatusBar.js +154 -0
  30. package/dist/packlets/messages/Toast.js +68 -0
  31. package/dist/packlets/messages/index.js +11 -0
  32. package/dist/packlets/messages/model.js +56 -0
  33. package/dist/packlets/messages/useLogReporter.js +66 -0
  34. package/dist/packlets/modal/ConfirmDialog.js +78 -0
  35. package/dist/packlets/modal/Modal.js +55 -0
  36. package/dist/packlets/modal/index.js +7 -0
  37. package/dist/packlets/print/PrintEnclosure.js +60 -0
  38. package/dist/packlets/print/index.js +7 -0
  39. package/dist/packlets/print/openPrintWindow.js +112 -0
  40. package/dist/packlets/responsive/ResponsiveProvider.js +56 -0
  41. package/dist/packlets/responsive/index.js +7 -0
  42. package/dist/packlets/responsive/useResponsiveLayout.js +118 -0
  43. package/dist/packlets/selectors/EntityRow.js +276 -0
  44. package/dist/packlets/selectors/PreferredSelector.js +251 -0
  45. package/dist/packlets/selectors/index.js +24 -0
  46. package/dist/packlets/sidebar/CollectionSection.js +107 -0
  47. package/dist/packlets/sidebar/EntityList.js +164 -0
  48. package/dist/packlets/sidebar/FilterBar.js +42 -0
  49. package/dist/packlets/sidebar/FilterRow.js +182 -0
  50. package/dist/packlets/sidebar/GroupedEntityList.js +183 -0
  51. package/dist/packlets/sidebar/SearchBar.js +34 -0
  52. package/dist/packlets/sidebar/SidebarLayout.js +62 -0
  53. package/dist/packlets/sidebar/index.js +12 -0
  54. package/dist/packlets/theme/ThemeProvider.js +141 -0
  55. package/dist/packlets/theme/index.js +6 -0
  56. package/dist/packlets/top-bar/ModeSelector.js +46 -0
  57. package/dist/packlets/top-bar/TabBar.js +37 -0
  58. package/dist/packlets/top-bar/index.js +7 -0
  59. package/dist/packlets/url-sync/index.js +6 -0
  60. package/dist/packlets/url-sync/useUrlSync.js +157 -0
  61. package/eslint.config.js +22 -0
  62. package/lib/index.browser.d.ts +2 -0
  63. package/lib/index.browser.js +19 -0
  64. package/lib/index.d.ts +28 -0
  65. package/lib/index.js +59 -0
  66. package/lib/packlets/ai-assist/index.d.ts +6 -0
  67. package/lib/packlets/ai-assist/index.js +11 -0
  68. package/lib/packlets/ai-assist/useAiAssist.d.ts +77 -0
  69. package/lib/packlets/ai-assist/useAiAssist.js +223 -0
  70. package/lib/packlets/cascade/CascadeContainer.d.ts +44 -0
  71. package/lib/packlets/cascade/CascadeContainer.js +119 -0
  72. package/lib/packlets/cascade/ComparisonView.d.ts +35 -0
  73. package/lib/packlets/cascade/ComparisonView.js +54 -0
  74. package/lib/packlets/cascade/EntityTabLayout.d.ts +47 -0
  75. package/lib/packlets/cascade/EntityTabLayout.js +110 -0
  76. package/lib/packlets/cascade/MobileCascadeStack.d.ts +20 -0
  77. package/lib/packlets/cascade/MobileCascadeStack.js +99 -0
  78. package/lib/packlets/cascade/index.d.ts +12 -0
  79. package/lib/packlets/cascade/index.js +48 -0
  80. package/lib/packlets/cascade/model.d.ts +57 -0
  81. package/lib/packlets/cascade/model.js +33 -0
  82. package/lib/packlets/cascade/useCascadeOps.d.ts +111 -0
  83. package/lib/packlets/cascade/useCascadeOps.js +209 -0
  84. package/lib/packlets/cascade/useCascadeTransitions.d.ts +19 -0
  85. package/lib/packlets/cascade/useCascadeTransitions.js +62 -0
  86. package/lib/packlets/detail/DetailHelpers.d.ts +83 -0
  87. package/lib/packlets/detail/DetailHelpers.js +113 -0
  88. package/lib/packlets/detail/index.d.ts +6 -0
  89. package/lib/packlets/detail/index.js +14 -0
  90. package/lib/packlets/drop-zone/JsonDropZone.d.ts +40 -0
  91. package/lib/packlets/drop-zone/JsonDropZone.js +149 -0
  92. package/lib/packlets/drop-zone/index.d.ts +6 -0
  93. package/lib/packlets/drop-zone/index.js +10 -0
  94. package/lib/packlets/editing/EditFieldHelpers.d.ts +171 -0
  95. package/lib/packlets/editing/EditFieldHelpers.js +144 -0
  96. package/lib/packlets/editing/MultiActionButton.d.ts +45 -0
  97. package/lib/packlets/editing/MultiActionButton.js +109 -0
  98. package/lib/packlets/editing/NumericInput.d.ts +47 -0
  99. package/lib/packlets/editing/NumericInput.js +155 -0
  100. package/lib/packlets/editing/TypeaheadInput.d.ts +46 -0
  101. package/lib/packlets/editing/TypeaheadInput.js +243 -0
  102. package/lib/packlets/editing/index.d.ts +10 -0
  103. package/lib/packlets/editing/index.js +26 -0
  104. package/lib/packlets/editing/useTypeaheadMatch.d.ts +42 -0
  105. package/lib/packlets/editing/useTypeaheadMatch.js +105 -0
  106. package/lib/packlets/keyboard/index.d.ts +7 -0
  107. package/lib/packlets/keyboard/index.js +15 -0
  108. package/lib/packlets/keyboard/registry.d.ts +92 -0
  109. package/lib/packlets/keyboard/registry.js +138 -0
  110. package/lib/packlets/keyboard/useKeyboardShortcuts.d.ts +50 -0
  111. package/lib/packlets/keyboard/useKeyboardShortcuts.js +155 -0
  112. package/lib/packlets/messages/MessagesContext.d.ts +40 -0
  113. package/lib/packlets/messages/MessagesContext.js +113 -0
  114. package/lib/packlets/messages/MessagesLogger.d.ts +50 -0
  115. package/lib/packlets/messages/MessagesLogger.js +107 -0
  116. package/lib/packlets/messages/StatusBar.d.ts +22 -0
  117. package/lib/packlets/messages/StatusBar.js +190 -0
  118. package/lib/packlets/messages/Toast.d.ts +31 -0
  119. package/lib/packlets/messages/Toast.js +105 -0
  120. package/lib/packlets/messages/index.d.ts +11 -0
  121. package/lib/packlets/messages/index.js +24 -0
  122. package/lib/packlets/messages/model.d.ts +59 -0
  123. package/lib/packlets/messages/model.js +61 -0
  124. package/lib/packlets/messages/useLogReporter.d.ts +22 -0
  125. package/lib/packlets/messages/useLogReporter.js +69 -0
  126. package/lib/packlets/modal/ConfirmDialog.d.ts +39 -0
  127. package/lib/packlets/modal/ConfirmDialog.js +114 -0
  128. package/lib/packlets/modal/Modal.d.ts +22 -0
  129. package/lib/packlets/modal/Modal.js +91 -0
  130. package/lib/packlets/modal/index.d.ts +7 -0
  131. package/lib/packlets/modal/index.js +12 -0
  132. package/lib/packlets/print/PrintEnclosure.d.ts +33 -0
  133. package/lib/packlets/print/PrintEnclosure.js +96 -0
  134. package/lib/packlets/print/index.d.ts +7 -0
  135. package/lib/packlets/print/index.js +12 -0
  136. package/lib/packlets/print/openPrintWindow.d.ts +35 -0
  137. package/lib/packlets/print/openPrintWindow.js +118 -0
  138. package/lib/packlets/responsive/ResponsiveProvider.d.ts +35 -0
  139. package/lib/packlets/responsive/ResponsiveProvider.js +93 -0
  140. package/lib/packlets/responsive/index.d.ts +7 -0
  141. package/lib/packlets/responsive/index.js +13 -0
  142. package/lib/packlets/responsive/useResponsiveLayout.d.ts +48 -0
  143. package/lib/packlets/responsive/useResponsiveLayout.js +121 -0
  144. package/lib/packlets/selectors/EntityRow.d.ts +45 -0
  145. package/lib/packlets/selectors/EntityRow.js +315 -0
  146. package/lib/packlets/selectors/PreferredSelector.d.ts +50 -0
  147. package/lib/packlets/selectors/PreferredSelector.js +287 -0
  148. package/lib/packlets/selectors/index.d.ts +5 -0
  149. package/lib/packlets/selectors/index.js +29 -0
  150. package/lib/packlets/sidebar/CollectionSection.d.ts +82 -0
  151. package/lib/packlets/sidebar/CollectionSection.js +143 -0
  152. package/lib/packlets/sidebar/EntityList.d.ts +105 -0
  153. package/lib/packlets/sidebar/EntityList.js +200 -0
  154. package/lib/packlets/sidebar/FilterBar.d.ts +26 -0
  155. package/lib/packlets/sidebar/FilterBar.js +48 -0
  156. package/lib/packlets/sidebar/FilterRow.d.ts +42 -0
  157. package/lib/packlets/sidebar/FilterRow.js +218 -0
  158. package/lib/packlets/sidebar/GroupedEntityList.d.ts +59 -0
  159. package/lib/packlets/sidebar/GroupedEntityList.js +219 -0
  160. package/lib/packlets/sidebar/SearchBar.d.ts +19 -0
  161. package/lib/packlets/sidebar/SearchBar.js +40 -0
  162. package/lib/packlets/sidebar/SidebarLayout.d.ts +28 -0
  163. package/lib/packlets/sidebar/SidebarLayout.js +98 -0
  164. package/lib/packlets/sidebar/index.d.ts +12 -0
  165. package/lib/packlets/sidebar/index.js +22 -0
  166. package/lib/packlets/theme/ThemeProvider.d.ts +68 -0
  167. package/lib/packlets/theme/ThemeProvider.js +178 -0
  168. package/lib/packlets/theme/index.d.ts +6 -0
  169. package/lib/packlets/theme/index.js +11 -0
  170. package/lib/packlets/top-bar/ModeSelector.d.ts +38 -0
  171. package/lib/packlets/top-bar/ModeSelector.js +52 -0
  172. package/lib/packlets/top-bar/TabBar.d.ts +31 -0
  173. package/lib/packlets/top-bar/TabBar.js +43 -0
  174. package/lib/packlets/top-bar/index.d.ts +7 -0
  175. package/lib/packlets/top-bar/index.js +12 -0
  176. package/lib/packlets/url-sync/index.d.ts +6 -0
  177. package/lib/packlets/url-sync/index.js +12 -0
  178. package/lib/packlets/url-sync/useUrlSync.d.ts +75 -0
  179. package/lib/packlets/url-sync/useUrlSync.js +162 -0
  180. package/package.json +82 -0
@@ -0,0 +1,157 @@
1
+ /*
2
+ * Copyright (c) 2026 Erik Fortune
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in all
12
+ * copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+ /**
23
+ * Generic URL hash synchronization for two-tier mode/tab navigation.
24
+ *
25
+ * Encodes mode + tab in the URL hash: `#/{mode}/{tab}`
26
+ * Syncs bidirectionally between application state and the URL.
27
+ * Handles browser back/forward via popstate.
28
+ *
29
+ * @packageDocumentation
30
+ */
31
+ import { useEffect, useRef } from 'react';
32
+ /**
33
+ * Encodes mode + tab into a URL hash string.
34
+ * @param mode - The current mode
35
+ * @param tab - The current tab
36
+ * @returns Hash string (e.g., '#/library/ingredients')
37
+ * @public
38
+ */
39
+ export function encodeUrlHash(mode, tab) {
40
+ return `#/${mode}/${tab}`;
41
+ }
42
+ /**
43
+ * Parses a URL hash string into mode + tab, validated against the config.
44
+ * Returns undefined if the hash is invalid or empty.
45
+ * @param hash - The hash string (with or without leading '#')
46
+ * @param config - Validation configuration
47
+ * @returns Parsed mode and tab, or undefined
48
+ * @public
49
+ */
50
+ export function parseUrlHash(hash, config) {
51
+ const cleaned = hash.replace(/^#\/?/, '');
52
+ if (cleaned.length === 0) {
53
+ return undefined;
54
+ }
55
+ const parts = cleaned.split('/');
56
+ if (parts.length < 1) {
57
+ return undefined;
58
+ }
59
+ const mode = parts[0];
60
+ if (!config.validModes.includes(mode)) {
61
+ return undefined;
62
+ }
63
+ const validTabs = config.validTabs[mode];
64
+ if (parts.length >= 2) {
65
+ const tab = parts[1];
66
+ if (validTabs.includes(tab)) {
67
+ return { mode, tab };
68
+ }
69
+ }
70
+ // Valid mode but no valid tab — use default
71
+ return { mode, tab: config.defaultTabs[mode] };
72
+ }
73
+ // ============================================================================
74
+ // URL Sync Hook
75
+ // ============================================================================
76
+ /**
77
+ * Hook that synchronizes two-tier mode/tab navigation state with the URL hash.
78
+ *
79
+ * - On mount: reads the URL hash and applies it to the store
80
+ * - On state change: updates the URL hash (pushes history entry)
81
+ * - On popstate (back/forward): reads the URL hash and applies it to the store
82
+ *
83
+ * Should be called once at the app root level.
84
+ *
85
+ * @param config - Validation configuration (modes, tabs, defaults)
86
+ * @param state - Current navigation state
87
+ * @param callbacks - State mutation callbacks
88
+ * @public
89
+ */
90
+ export function useUrlSync(config, state, callbacks) {
91
+ const { mode, activeTab } = state;
92
+ const { setMode, setTab } = callbacks;
93
+ // Track whether we're currently applying a URL change to avoid circular updates
94
+ const isApplyingUrl = useRef(false);
95
+ // Track whether we've done the initial load
96
+ const initialized = useRef(false);
97
+ // Keep refs to the latest values so the mount effect captures them without
98
+ // needing them in its dependency array (the effect must only run once).
99
+ const configRef = useRef(config);
100
+ configRef.current = config;
101
+ const modeRef = useRef(mode);
102
+ modeRef.current = mode;
103
+ const activeTabRef = useRef(activeTab);
104
+ activeTabRef.current = activeTab;
105
+ const setModeRef = useRef(setMode);
106
+ setModeRef.current = setMode;
107
+ const setTabRef = useRef(setTab);
108
+ setTabRef.current = setTab;
109
+ // ---- Initial load: URL → store ----
110
+ useEffect(() => {
111
+ if (initialized.current) {
112
+ return;
113
+ }
114
+ initialized.current = true;
115
+ const parsed = parseUrlHash(window.location.hash, configRef.current);
116
+ if (parsed) {
117
+ isApplyingUrl.current = true;
118
+ setModeRef.current(parsed.mode);
119
+ setTabRef.current(parsed.tab);
120
+ isApplyingUrl.current = false;
121
+ }
122
+ else {
123
+ // No valid hash — write current state to URL
124
+ const hash = encodeUrlHash(modeRef.current, activeTabRef.current);
125
+ window.history.replaceState(null, '', hash);
126
+ }
127
+ // Intentionally empty: this effect must run exactly once on mount.
128
+ // All values are accessed via refs to avoid stale closures.
129
+ }, []);
130
+ // ---- Store → URL: push history on navigation changes ----
131
+ useEffect(() => {
132
+ if (isApplyingUrl.current) {
133
+ return;
134
+ }
135
+ const hash = encodeUrlHash(mode, activeTab);
136
+ if (window.location.hash !== hash) {
137
+ window.history.pushState(null, '', hash);
138
+ }
139
+ }, [mode, activeTab]);
140
+ // ---- URL → store: handle popstate (back/forward) ----
141
+ useEffect(() => {
142
+ const handlePopState = () => {
143
+ const parsed = parseUrlHash(window.location.hash, config);
144
+ if (parsed) {
145
+ isApplyingUrl.current = true;
146
+ setMode(parsed.mode);
147
+ setTab(parsed.tab);
148
+ isApplyingUrl.current = false;
149
+ }
150
+ };
151
+ window.addEventListener('popstate', handlePopState);
152
+ return () => {
153
+ window.removeEventListener('popstate', handlePopState);
154
+ };
155
+ }, [config, setMode, setTab]);
156
+ }
157
+ //# sourceMappingURL=useUrlSync.js.map
@@ -0,0 +1,22 @@
1
+ // ESLint 9 flat config
2
+ const nodeProfile = require('@rushstack/eslint-config/flat/profile/node');
3
+ const packletsPlugin = require('@rushstack/eslint-config/flat/mixins/packlets');
4
+ const tsdocPlugin = require('@rushstack/eslint-config/flat/mixins/tsdoc');
5
+ const reactHooksPlugin = require('eslint-plugin-react-hooks');
6
+
7
+ module.exports = [
8
+ ...nodeProfile,
9
+ packletsPlugin,
10
+ ...tsdocPlugin,
11
+ {
12
+ plugins: {
13
+ 'react-hooks': reactHooksPlugin
14
+ },
15
+ rules: {
16
+ ...reactHooksPlugin.configs.recommended.rules,
17
+ '@rushstack/packlets/mechanics': 'warn',
18
+ // Disable no-new-null rule for React components where null is conventional
19
+ '@rushstack/no-new-null': 'off'
20
+ }
21
+ }
22
+ ];
@@ -0,0 +1,2 @@
1
+ export * from './index';
2
+ //# sourceMappingURL=index.browser.d.ts.map
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // Browser entry point - re-exports everything from main index
18
+ __exportStar(require("./index"), exports);
19
+ //# sourceMappingURL=index.browser.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ /**
2
+ * \@fgv/ts-app-shell - Shared React UI primitives for application shells.
3
+ *
4
+ * Provides reusable components for:
5
+ * - Column cascade (master-detail drill-down)
6
+ * - Compact sidebar with flyout filter selectors
7
+ * - Toast notifications and log message panel
8
+ * - Command palette
9
+ * - Keybinding registry
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ export * from './packlets/top-bar';
14
+ export * from './packlets/messages';
15
+ export * from './packlets/url-sync';
16
+ export * from './packlets/modal';
17
+ export * from './packlets/keyboard';
18
+ export * from './packlets/sidebar';
19
+ export * from './packlets/cascade';
20
+ export * from './packlets/selectors';
21
+ export * from './packlets/drop-zone';
22
+ export * from './packlets/editing';
23
+ export * from './packlets/detail';
24
+ export * from './packlets/ai-assist';
25
+ export * from './packlets/print';
26
+ export * from './packlets/theme';
27
+ export * from './packlets/responsive';
28
+ //# sourceMappingURL=index.d.ts.map
package/lib/index.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * \@fgv/ts-app-shell - Shared React UI primitives for application shells.
4
+ *
5
+ * Provides reusable components for:
6
+ * - Column cascade (master-detail drill-down)
7
+ * - Compact sidebar with flyout filter selectors
8
+ * - Toast notifications and log message panel
9
+ * - Command palette
10
+ * - Keybinding registry
11
+ *
12
+ * @packageDocumentation
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ // Top bar components
30
+ __exportStar(require("./packlets/top-bar"), exports);
31
+ // Messages infrastructure (context, toasts, status bar)
32
+ __exportStar(require("./packlets/messages"), exports);
33
+ // URL hash synchronization for mode/tab navigation
34
+ __exportStar(require("./packlets/url-sync"), exports);
35
+ // Modal dialog
36
+ __exportStar(require("./packlets/modal"), exports);
37
+ // Keyboard shortcut registry
38
+ __exportStar(require("./packlets/keyboard"), exports);
39
+ // Sidebar layout, search, filters, and entity list
40
+ __exportStar(require("./packlets/sidebar"), exports);
41
+ // Column cascade container
42
+ __exportStar(require("./packlets/cascade"), exports);
43
+ // Selectors (PreferredSelector popover)
44
+ __exportStar(require("./packlets/selectors"), exports);
45
+ // JSON drop zone (generic drop/paste target with converter validation)
46
+ __exportStar(require("./packlets/drop-zone"), exports);
47
+ // Generic edit field primitives for entity editors
48
+ __exportStar(require("./packlets/editing"), exports);
49
+ // Generic detail-view primitive components
50
+ __exportStar(require("./packlets/detail"), exports);
51
+ // AI assist hook (generic, context-free)
52
+ __exportStar(require("./packlets/ai-assist"), exports);
53
+ // Print popup window enclosure
54
+ __exportStar(require("./packlets/print"), exports);
55
+ // Theme provider and hook
56
+ __exportStar(require("./packlets/theme"), exports);
57
+ // Responsive layout detection and context
58
+ __exportStar(require("./packlets/responsive"), exports);
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ /**
2
+ * AI assist packlet — generic hook for AI-assisted entity generation.
3
+ * @packageDocumentation
4
+ */
5
+ export { checkForAiErrorObject, useAiAssist, type IAiAssistAction, type IAiAssistResult, type IUseAiAssistParams, type IUseAiAssistResult } from './useAiAssist';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ /**
3
+ * AI assist packlet — generic hook for AI-assisted entity generation.
4
+ * @packageDocumentation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.useAiAssist = exports.checkForAiErrorObject = void 0;
8
+ var useAiAssist_1 = require("./useAiAssist");
9
+ Object.defineProperty(exports, "checkForAiErrorObject", { enumerable: true, get: function () { return useAiAssist_1.checkForAiErrorObject; } });
10
+ Object.defineProperty(exports, "useAiAssist", { enumerable: true, get: function () { return useAiAssist_1.useAiAssist; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,77 @@
1
+ import { AiAssist } from '@fgv/ts-extras';
2
+ import { type Logging, Result } from '@fgv/ts-utils';
3
+ /**
4
+ * Parameters for the generic useAiAssist hook.
5
+ * @public
6
+ */
7
+ export interface IUseAiAssistParams {
8
+ /** Resolved AI assist settings (provider list + default). */
9
+ readonly settings: AiAssist.IAiAssistSettings | undefined;
10
+ /** Keystore for API key resolution (or undefined if no keystore). */
11
+ readonly keyStore: AiAssist.IAiAssistKeyStore | undefined;
12
+ /** Optional logger for request/response observability. */
13
+ readonly logger?: Logging.ILogger;
14
+ }
15
+ /**
16
+ * A single available AI assist action.
17
+ * @public
18
+ */
19
+ export interface IAiAssistAction {
20
+ /** The provider identifier */
21
+ readonly provider: AiAssist.AiProviderId;
22
+ /** Display label (e.g. "AI Assist | Copy", "AI Assist | Grok") */
23
+ readonly label: string;
24
+ /** Whether this is the default (first) action */
25
+ readonly isDefault: boolean;
26
+ /** Whether the provider is currently available (keystore unlocked, secret present) */
27
+ readonly isAvailable: boolean;
28
+ /** Reason the provider is unavailable, if any */
29
+ readonly unavailableReason?: string;
30
+ }
31
+ /**
32
+ * Result of executing an AI assist action.
33
+ * @public
34
+ */
35
+ export interface IAiAssistResult<TEntity> {
36
+ /** The generated entity */
37
+ readonly entity: TEntity;
38
+ /** Source indicator */
39
+ readonly source: 'ai';
40
+ }
41
+ /**
42
+ * Return type of the useAiAssist hook.
43
+ * @public
44
+ */
45
+ export interface IUseAiAssistResult {
46
+ /** Available actions based on settings + keystore state */
47
+ readonly actions: ReadonlyArray<IAiAssistAction>;
48
+ /** Whether a direct assist call is in progress */
49
+ readonly isWorking: boolean;
50
+ /**
51
+ * Execute a copy-paste action: copies the combined prompt to clipboard.
52
+ * @returns Success with `'copied'`, or failure.
53
+ */
54
+ readonly copyPrompt: (prompt: AiAssist.AiPrompt) => Promise<Result<'copied'>>;
55
+ /**
56
+ * Execute a direct API action: calls the provider, validates the response, returns the entity.
57
+ * @returns Success with the validated entity, or failure.
58
+ */
59
+ readonly generateDirect: <TEntity>(provider: AiAssist.AiProviderId, prompt: AiAssist.AiPrompt, convert: (from: unknown) => Result<TEntity>, tools?: ReadonlyArray<AiAssist.AiServerToolConfig>) => Promise<Result<IAiAssistResult<TEntity>>>;
60
+ }
61
+ /**
62
+ * Checks whether a parsed AI response is an error object (with an "error" field)
63
+ * rather than a valid entity. AI prompts instruct the model to return
64
+ * `{ "error": "...", "term": "..." }` when it cannot confidently generate the entity.
65
+ *
66
+ * @param parsed - The parsed JSON from the AI response
67
+ * @returns A failure Result with the error message if it's an error object, or undefined if not
68
+ */
69
+ export declare function checkForAiErrorObject(parsed: unknown): Result<never> | undefined;
70
+ /**
71
+ * Generic hook providing AI assist actions based on supplied settings and keystore.
72
+ * @param params - Settings and keystore to use (typically from a workspace or app context)
73
+ * @returns Available actions and execution functions
74
+ * @public
75
+ */
76
+ export declare function useAiAssist(params: IUseAiAssistParams): IUseAiAssistResult;
77
+ //# sourceMappingURL=useAiAssist.d.ts.map
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ /*
3
+ * Copyright (c) 2026 Erik Fortune
4
+ *
5
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ * of this software and associated documentation files (the "Software"), to deal
7
+ * in the Software without restriction, including without limitation the rights
8
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ * copies of the Software, and to permit persons to whom the Software is
10
+ * furnished to do so, subject to the following conditions:
11
+ *
12
+ * The above copyright notice and this permission notice shall be included in all
13
+ * copies or substantial portions of the Software.
14
+ *
15
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ * SOFTWARE.
22
+ */
23
+ Object.defineProperty(exports, "__esModule", { value: true });
24
+ exports.checkForAiErrorObject = checkForAiErrorObject;
25
+ exports.useAiAssist = useAiAssist;
26
+ /**
27
+ * Generic AI assist hook — parameterized by settings and keystore, no app-specific context.
28
+ *
29
+ * Provides a list of available AI assist actions and functions for copy-paste
30
+ * and direct API generation flows.
31
+ *
32
+ * @packageDocumentation
33
+ */
34
+ const react_1 = require("react");
35
+ const ts_extras_1 = require("@fgv/ts-extras");
36
+ const ts_utils_1 = require("@fgv/ts-utils");
37
+ // ============================================================================
38
+ // Helpers
39
+ // ============================================================================
40
+ /**
41
+ * Checks whether a parsed AI response is an error object (with an "error" field)
42
+ * rather than a valid entity. AI prompts instruct the model to return
43
+ * `{ "error": "...", "term": "..." }` when it cannot confidently generate the entity.
44
+ *
45
+ * @param parsed - The parsed JSON from the AI response
46
+ * @returns A failure Result with the error message if it's an error object, or undefined if not
47
+ */
48
+ function checkForAiErrorObject(parsed) {
49
+ if (typeof parsed === 'object' &&
50
+ parsed !== null &&
51
+ 'error' in parsed &&
52
+ typeof parsed.error === 'string') {
53
+ const errorObj = parsed;
54
+ const term = typeof errorObj.term === 'string' ? ` (term: "${errorObj.term}")` : '';
55
+ return (0, ts_utils_1.fail)(`AI declined to generate${term}: ${errorObj.error}`);
56
+ }
57
+ return undefined;
58
+ }
59
+ // ============================================================================
60
+ // Hook
61
+ // ============================================================================
62
+ /**
63
+ * Generic hook providing AI assist actions based on supplied settings and keystore.
64
+ * @param params - Settings and keystore to use (typically from a workspace or app context)
65
+ * @returns Available actions and execution functions
66
+ * @public
67
+ */
68
+ function useAiAssist(params) {
69
+ const { settings, keyStore, logger } = params;
70
+ const [isWorking, setIsWorking] = (0, react_1.useState)(false);
71
+ const actions = (0, react_1.useMemo)(() => {
72
+ var _a, _b, _c;
73
+ const providers = (_a = settings === null || settings === void 0 ? void 0 : settings.providers) !== null && _a !== void 0 ? _a : [{ provider: 'copy-paste' }];
74
+ const enabledSet = new Set(providers.map((p) => p.provider));
75
+ const defaultProvider = (settings === null || settings === void 0 ? void 0 : settings.defaultProvider) && enabledSet.has(settings.defaultProvider)
76
+ ? settings.defaultProvider
77
+ : (_c = (_b = providers[0]) === null || _b === void 0 ? void 0 : _b.provider) !== null && _c !== void 0 ? _c : 'copy-paste';
78
+ return providers.map((config) => {
79
+ var _a, _b;
80
+ let isAvailable = true;
81
+ let unavailableReason;
82
+ if (config.provider !== 'copy-paste') {
83
+ // API-based providers need a secret name and unlocked keystore with that secret
84
+ if (!config.secretName) {
85
+ isAvailable = false;
86
+ unavailableReason = 'No API key secret configured';
87
+ }
88
+ else if (!keyStore) {
89
+ isAvailable = false;
90
+ unavailableReason = 'No keystore available';
91
+ }
92
+ else if (!keyStore.isUnlocked) {
93
+ isAvailable = false;
94
+ unavailableReason = 'Keystore is locked';
95
+ }
96
+ else {
97
+ const hasSecret = keyStore.hasSecret(config.secretName);
98
+ if (hasSecret.isFailure() || !hasSecret.value) {
99
+ isAvailable = false;
100
+ unavailableReason = `API key "${config.secretName}" not found in keystore`;
101
+ }
102
+ }
103
+ }
104
+ const label = (_b = (_a = ts_extras_1.AiAssist.getProviderDescriptor(config.provider).orDefault()) === null || _a === void 0 ? void 0 : _a.buttonLabel) !== null && _b !== void 0 ? _b : config.provider;
105
+ return {
106
+ provider: config.provider,
107
+ label,
108
+ isDefault: config.provider === defaultProvider,
109
+ isAvailable,
110
+ unavailableReason
111
+ };
112
+ });
113
+ }, [settings, keyStore]);
114
+ const copyPrompt = (0, react_1.useCallback)(async (prompt) => {
115
+ try {
116
+ await navigator.clipboard.writeText(prompt.combined);
117
+ return (0, ts_utils_1.succeed)('copied');
118
+ }
119
+ catch (_a) {
120
+ return (0, ts_utils_1.fail)('Failed to copy prompt to clipboard');
121
+ }
122
+ }, []);
123
+ const generateDirect = (0, react_1.useCallback)(async (provider, prompt, convert, tools) => {
124
+ // Find the provider config and descriptor
125
+ const providerConfig = settings === null || settings === void 0 ? void 0 : settings.providers.find((p) => p.provider === provider);
126
+ if (!providerConfig) {
127
+ return (0, ts_utils_1.fail)(`Provider "${provider}" not configured`);
128
+ }
129
+ const descriptorResult = ts_extras_1.AiAssist.getProviderDescriptor(provider);
130
+ if (descriptorResult.isFailure()) {
131
+ return (0, ts_utils_1.fail)(descriptorResult.message);
132
+ }
133
+ const descriptor = descriptorResult.value;
134
+ if (!providerConfig.secretName) {
135
+ return (0, ts_utils_1.fail)(`Provider "${provider}" has no secret name configured`);
136
+ }
137
+ if (!keyStore) {
138
+ return (0, ts_utils_1.fail)('No keystore available');
139
+ }
140
+ // Get API key from keystore
141
+ const apiKeyResult = keyStore.getApiKey(providerConfig.secretName);
142
+ if (apiKeyResult.isFailure()) {
143
+ return (0, ts_utils_1.fail)(`Failed to get API key: ${apiKeyResult.message}`);
144
+ }
145
+ setIsWorking(true);
146
+ try {
147
+ const maxAttempts = 3;
148
+ const correctionMessages = [];
149
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
150
+ // Resolve effective tools: per-call override > settings defaults > none
151
+ const effectiveTools = ts_extras_1.AiAssist.resolveEffectiveTools(descriptor, providerConfig.tools, tools);
152
+ // Call the API — through proxy if configured, otherwise direct
153
+ const completionParams = {
154
+ descriptor,
155
+ apiKey: apiKeyResult.value,
156
+ prompt,
157
+ additionalMessages: correctionMessages.length > 0 ? correctionMessages : undefined,
158
+ modelOverride: providerConfig.model,
159
+ logger,
160
+ tools: effectiveTools.length > 0 ? effectiveTools : undefined
161
+ };
162
+ const useProxy = !!(settings === null || settings === void 0 ? void 0 : settings.proxyUrl) && (settings.proxyAllProviders === true || descriptor.corsRestricted);
163
+ const responseResult = useProxy
164
+ ? await ts_extras_1.AiAssist.callProxiedCompletion(settings.proxyUrl, completionParams)
165
+ : await ts_extras_1.AiAssist.callProviderCompletion(completionParams);
166
+ if (responseResult.isFailure()) {
167
+ logger === null || logger === void 0 ? void 0 : logger.error(`AI completion failed: ${responseResult.message}`);
168
+ return (0, ts_utils_1.fail)(responseResult.message);
169
+ }
170
+ const { content: rawResponse, truncated } = responseResult.value;
171
+ logger === null || logger === void 0 ? void 0 : logger.detail(`AI response received (${rawResponse.length} chars, truncated=${truncated})`);
172
+ // Truncated responses are almost certainly malformed JSON — fail early
173
+ if (truncated) {
174
+ logger === null || logger === void 0 ? void 0 : logger.warn('AI response truncated due to token limits');
175
+ return (0, ts_utils_1.fail)('AI response was truncated due to token limits — try a shorter prompt');
176
+ }
177
+ // Strip markdown code fences and parse JSON
178
+ const stripped = rawResponse
179
+ .trim()
180
+ .replace(/^```(?:\w+)?\s*\n?([\s\S]*?)\n?\s*```$/, '$1')
181
+ .trim();
182
+ let parsed;
183
+ try {
184
+ parsed = JSON.parse(stripped);
185
+ }
186
+ catch (err) {
187
+ const detail = err instanceof Error ? err.message : String(err);
188
+ // JSON parse failures are not retryable — the model isn't producing valid JSON at all
189
+ return (0, ts_utils_1.fail)(`AI returned invalid JSON: ${detail}`);
190
+ }
191
+ // Check for AI error object (model declined to generate)
192
+ const aiError = checkForAiErrorObject(parsed);
193
+ if (aiError !== undefined) {
194
+ return aiError;
195
+ }
196
+ // Validate with the provided converter
197
+ const entityResult = convert(parsed);
198
+ if (entityResult.isSuccess()) {
199
+ return (0, ts_utils_1.succeed)({ entity: entityResult.value, source: 'ai' });
200
+ }
201
+ // Validation failed — if we have retries left, send a correction
202
+ logger === null || logger === void 0 ? void 0 : logger.warn(`AI response validation failed (attempt ${attempt + 1}/${maxAttempts}): ${entityResult.message}`);
203
+ if (attempt < maxAttempts - 1) {
204
+ correctionMessages.push({ role: 'assistant', content: rawResponse }, {
205
+ role: 'user',
206
+ content: `The JSON you returned failed validation with the following error:\n\n${entityResult.message}\n\nPlease fix the JSON and return ONLY the corrected JSON object, nothing else.`
207
+ });
208
+ }
209
+ else {
210
+ // Out of retries
211
+ return (0, ts_utils_1.fail)(`AI response validation failed after ${maxAttempts} attempts: ${entityResult.message}`);
212
+ }
213
+ }
214
+ // Unreachable, but TypeScript needs it
215
+ return (0, ts_utils_1.fail)('AI generation failed');
216
+ }
217
+ finally {
218
+ setIsWorking(false);
219
+ }
220
+ }, [settings, keyStore, logger]);
221
+ return { actions, isWorking, copyPrompt, generateDirect };
222
+ }
223
+ //# sourceMappingURL=useAiAssist.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Column cascade container — horizontal scroll of detail columns.
3
+ * @packageDocumentation
4
+ */
5
+ import React from 'react';
6
+ /**
7
+ * Describes a single column in the cascade.
8
+ * @public
9
+ */
10
+ export interface ICascadeColumn {
11
+ /** Unique key for React reconciliation */
12
+ readonly key: string;
13
+ /** Breadcrumb label for this column */
14
+ readonly label: string;
15
+ /** Column content */
16
+ readonly content: React.ReactNode;
17
+ }
18
+ /**
19
+ * Props for the CascadeContainer component.
20
+ * @public
21
+ */
22
+ export interface ICascadeContainerProps {
23
+ /** Ordered columns (left-to-right) */
24
+ readonly columns: ReadonlyArray<ICascadeColumn>;
25
+ /** Callback to pop the cascade back to a specific depth (0 = clear all) */
26
+ readonly onPopTo: (depth: number) => void;
27
+ /** Minimum column width in CSS units (default: '400px') */
28
+ readonly minColumnWidth?: string;
29
+ /** Callback when the user clicks inside a cascade column (signals list should collapse) */
30
+ readonly onFocus?: () => void;
31
+ /** Label for the breadcrumb root link (default: 'List') */
32
+ readonly rootLabel?: string;
33
+ }
34
+ /**
35
+ * Horizontal scroll container for the column cascade.
36
+ *
37
+ * Renders a breadcrumb trail at the top and horizontally-scrollable
38
+ * detail columns below. Auto-scrolls to the rightmost column when
39
+ * a new column is pushed.
40
+ *
41
+ * @public
42
+ */
43
+ export declare function CascadeContainer(props: ICascadeContainerProps): React.ReactElement | null;
44
+ //# sourceMappingURL=CascadeContainer.d.ts.map