@fgv/ts-res-ui-components 5.0.0-21 → 5.0.0-23

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 (192) hide show
  1. package/README.md +401 -155
  2. package/config/jest.setup.js +10 -0
  3. package/dist/ts-res-ui-components.d.ts +1657 -76
  4. package/lib/components/common/QualifierContextControl.js +4 -1
  5. package/lib/components/common/ResourceTreeView.js +4 -1
  6. package/lib/components/forms/GenericQualifierTypeEditForm.d.ts +26 -0
  7. package/lib/components/forms/GenericQualifierTypeEditForm.js +166 -0
  8. package/lib/components/forms/QualifierEditForm.d.ts +1 -1
  9. package/lib/components/forms/index.d.ts +2 -0
  10. package/lib/components/forms/index.js +1 -0
  11. package/lib/components/orchestrator/ResourceOrchestrator.d.ts +3 -0
  12. package/lib/components/orchestrator/ResourceOrchestrator.js +118 -51
  13. package/lib/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  14. package/lib/components/pickers/ResourcePicker/index.js +4 -2
  15. package/lib/components/views/CompiledView/index.js +75 -16
  16. package/lib/components/views/ConfigurationView/index.js +94 -35
  17. package/lib/components/views/FilterView/index.js +7 -4
  18. package/lib/components/views/GridView/EditableGridCell.d.ts +76 -0
  19. package/lib/components/views/GridView/EditableGridCell.js +224 -0
  20. package/lib/components/views/GridView/GridSelector.d.ts +43 -0
  21. package/lib/components/views/GridView/GridSelector.js +89 -0
  22. package/lib/components/views/GridView/MultiGridView.d.ts +85 -0
  23. package/lib/components/views/GridView/MultiGridView.js +196 -0
  24. package/lib/components/views/GridView/ResourceGrid.d.ts +38 -0
  25. package/lib/components/views/GridView/ResourceGrid.js +232 -0
  26. package/lib/components/views/GridView/SharedContextControls.d.ts +47 -0
  27. package/lib/components/views/GridView/SharedContextControls.js +95 -0
  28. package/lib/components/views/GridView/cells/BooleanCell.d.ts +44 -0
  29. package/lib/components/views/GridView/cells/BooleanCell.js +49 -0
  30. package/lib/components/views/GridView/cells/DropdownCell.d.ts +58 -0
  31. package/lib/components/views/GridView/cells/DropdownCell.js +182 -0
  32. package/lib/components/views/GridView/cells/StringCell.d.ts +57 -0
  33. package/lib/components/views/GridView/cells/StringCell.js +106 -0
  34. package/lib/components/views/GridView/cells/TriStateCell.d.ts +54 -0
  35. package/lib/components/views/GridView/cells/TriStateCell.js +112 -0
  36. package/lib/components/views/GridView/cells/index.d.ts +15 -0
  37. package/lib/components/views/GridView/cells/index.js +11 -0
  38. package/lib/components/views/GridView/index.d.ts +53 -0
  39. package/lib/components/views/GridView/index.js +212 -0
  40. package/lib/components/views/ImportView/index.js +22 -19
  41. package/lib/components/views/MessagesWindow/index.js +4 -1
  42. package/lib/components/views/ResolutionView/index.js +8 -5
  43. package/lib/contexts/ObservabilityContext.d.ts +85 -0
  44. package/lib/contexts/ObservabilityContext.js +98 -0
  45. package/lib/contexts/index.d.ts +2 -0
  46. package/lib/contexts/index.js +24 -0
  47. package/lib/hooks/useConfigurationState.d.ts +3 -3
  48. package/lib/hooks/useResolutionState.js +850 -246
  49. package/lib/hooks/useResourceData.d.ts +7 -4
  50. package/lib/hooks/useResourceData.js +185 -184
  51. package/lib/index.d.ts +5 -1
  52. package/lib/index.js +8 -1
  53. package/lib/namespaces/GridTools.d.ts +136 -0
  54. package/lib/namespaces/GridTools.js +138 -0
  55. package/lib/namespaces/ObservabilityTools.d.ts +3 -0
  56. package/lib/namespaces/ObservabilityTools.js +23 -0
  57. package/lib/namespaces/ResolutionTools.d.ts +2 -1
  58. package/lib/namespaces/ResolutionTools.js +2 -0
  59. package/lib/namespaces/index.d.ts +2 -0
  60. package/lib/namespaces/index.js +2 -0
  61. package/lib/test/integration/observability.integration.test.d.ts +2 -0
  62. package/lib/test/unit/hooks/useResourceData.test.d.ts +2 -0
  63. package/lib/test/unit/utils/downloadHelper.test.d.ts +2 -0
  64. package/lib/test/unit/workflows/resolutionWorkflows.test.d.ts +2 -0
  65. package/lib/test/unit/workflows/resourceCreation.test.d.ts +2 -0
  66. package/lib/test/unit/workflows/resultPatternExtensions.test.d.ts +2 -0
  67. package/lib/test/unit/workflows/validation.test.d.ts +2 -0
  68. package/lib/types/index.d.ts +387 -20
  69. package/lib/types/index.js +2 -1
  70. package/lib/utils/cellValidation.d.ts +113 -0
  71. package/lib/utils/cellValidation.js +248 -0
  72. package/lib/utils/downloadHelper.d.ts +66 -0
  73. package/lib/utils/downloadHelper.js +195 -0
  74. package/lib/utils/observability/factories.d.ts +29 -0
  75. package/lib/utils/observability/factories.js +58 -0
  76. package/lib/utils/observability/implementations.d.ts +61 -0
  77. package/lib/utils/observability/implementations.js +103 -0
  78. package/lib/utils/observability/index.d.ts +4 -0
  79. package/lib/utils/observability/index.js +26 -0
  80. package/lib/utils/observability/interfaces.d.ts +30 -0
  81. package/lib/utils/observability/interfaces.js +23 -0
  82. package/lib/utils/resolutionEditing.js +2 -1
  83. package/lib/utils/resourceSelector.d.ts +97 -0
  84. package/lib/utils/resourceSelector.js +195 -0
  85. package/lib/utils/resourceSelectors.d.ts +146 -0
  86. package/lib/utils/resourceSelectors.js +233 -0
  87. package/lib/utils/tsResIntegration.d.ts +6 -41
  88. package/lib/utils/tsResIntegration.js +20 -16
  89. package/lib/utils/zipLoader/zipProcessingHelpers.d.ts +3 -2
  90. package/lib/utils/zipLoader/zipProcessingHelpers.js +6 -5
  91. package/lib-commonjs/components/common/QualifierContextControl.js +4 -1
  92. package/lib-commonjs/components/common/ResourceTreeView.js +4 -1
  93. package/lib-commonjs/components/forms/GenericQualifierTypeEditForm.js +171 -0
  94. package/lib-commonjs/components/forms/index.js +3 -1
  95. package/lib-commonjs/components/orchestrator/ResourceOrchestrator.js +118 -51
  96. package/lib-commonjs/components/pickers/ResourcePicker/ResourcePickerTree.js +32 -10
  97. package/lib-commonjs/components/pickers/ResourcePicker/index.js +4 -2
  98. package/lib-commonjs/components/views/CompiledView/index.js +75 -16
  99. package/lib-commonjs/components/views/ConfigurationView/index.js +93 -34
  100. package/lib-commonjs/components/views/FilterView/index.js +7 -4
  101. package/lib-commonjs/components/views/GridView/EditableGridCell.js +232 -0
  102. package/lib-commonjs/components/views/GridView/GridSelector.js +94 -0
  103. package/lib-commonjs/components/views/GridView/MultiGridView.js +201 -0
  104. package/lib-commonjs/components/views/GridView/ResourceGrid.js +237 -0
  105. package/lib-commonjs/components/views/GridView/SharedContextControls.js +100 -0
  106. package/lib-commonjs/components/views/GridView/cells/BooleanCell.js +54 -0
  107. package/lib-commonjs/components/views/GridView/cells/DropdownCell.js +187 -0
  108. package/lib-commonjs/components/views/GridView/cells/StringCell.js +111 -0
  109. package/lib-commonjs/components/views/GridView/cells/TriStateCell.js +117 -0
  110. package/lib-commonjs/components/views/GridView/cells/index.js +18 -0
  111. package/lib-commonjs/components/views/GridView/index.js +217 -0
  112. package/lib-commonjs/components/views/ImportView/index.js +22 -19
  113. package/lib-commonjs/components/views/MessagesWindow/index.js +4 -1
  114. package/lib-commonjs/components/views/ResolutionView/index.js +8 -5
  115. package/lib-commonjs/contexts/ObservabilityContext.js +104 -0
  116. package/lib-commonjs/contexts/index.js +30 -0
  117. package/lib-commonjs/hooks/useResolutionState.js +849 -245
  118. package/lib-commonjs/hooks/useResourceData.js +184 -215
  119. package/lib-commonjs/index.js +15 -1
  120. package/lib-commonjs/namespaces/GridTools.js +161 -0
  121. package/lib-commonjs/namespaces/ObservabilityTools.js +33 -0
  122. package/lib-commonjs/namespaces/ResolutionTools.js +10 -1
  123. package/lib-commonjs/namespaces/index.js +3 -1
  124. package/lib-commonjs/types/index.js +10 -0
  125. package/lib-commonjs/utils/cellValidation.js +253 -0
  126. package/lib-commonjs/utils/downloadHelper.js +198 -0
  127. package/lib-commonjs/utils/observability/factories.js +63 -0
  128. package/lib-commonjs/utils/observability/implementations.js +109 -0
  129. package/lib-commonjs/utils/observability/index.js +36 -0
  130. package/lib-commonjs/utils/observability/interfaces.js +24 -0
  131. package/lib-commonjs/utils/resolutionEditing.js +2 -1
  132. package/lib-commonjs/utils/resourceSelector.js +200 -0
  133. package/lib-commonjs/utils/resourceSelectors.js +242 -0
  134. package/lib-commonjs/utils/tsResIntegration.js +21 -16
  135. package/lib-commonjs/utils/zipLoader/zipProcessingHelpers.js +7 -5
  136. package/package.json +7 -7
  137. package/src/components/common/QualifierContextControl.tsx +0 -338
  138. package/src/components/common/ResolutionContextOptionsControl.tsx +0 -450
  139. package/src/components/common/ResolutionResults/index.tsx +0 -481
  140. package/src/components/common/ResourceListView.tsx +0 -167
  141. package/src/components/common/ResourcePickerOptionsControl.tsx +0 -351
  142. package/src/components/common/ResourceTreeView.tsx +0 -417
  143. package/src/components/common/SourceResourceDetail/index.tsx +0 -493
  144. package/src/components/forms/HierarchyEditor.tsx +0 -285
  145. package/src/components/forms/QualifierEditForm.tsx +0 -487
  146. package/src/components/forms/QualifierTypeEditForm.tsx +0 -458
  147. package/src/components/forms/ResourceTypeEditForm.tsx +0 -437
  148. package/src/components/forms/index.ts +0 -11
  149. package/src/components/orchestrator/ResourceOrchestrator.tsx +0 -444
  150. package/src/components/pickers/ResourcePicker/README.md +0 -570
  151. package/src/components/pickers/ResourcePicker/ResourceItem.tsx +0 -127
  152. package/src/components/pickers/ResourcePicker/ResourcePickerList.tsx +0 -114
  153. package/src/components/pickers/ResourcePicker/ResourcePickerTree.tsx +0 -461
  154. package/src/components/pickers/ResourcePicker/index.tsx +0 -234
  155. package/src/components/pickers/ResourcePicker/types.ts +0 -301
  156. package/src/components/pickers/ResourcePicker/utils/treeNavigation.ts +0 -210
  157. package/src/components/views/CompiledView/index.tsx +0 -1342
  158. package/src/components/views/ConfigurationView/index.tsx +0 -848
  159. package/src/components/views/FilterView/index.tsx +0 -681
  160. package/src/components/views/ImportView/index.tsx +0 -789
  161. package/src/components/views/MessagesWindow/index.tsx +0 -325
  162. package/src/components/views/ResolutionView/EditableJsonView.tsx +0 -386
  163. package/src/components/views/ResolutionView/NewResourceModal.tsx +0 -158
  164. package/src/components/views/ResolutionView/UnifiedChangeControls.tsx +0 -163
  165. package/src/components/views/ResolutionView/index.tsx +0 -751
  166. package/src/components/views/SourceView/index.tsx +0 -291
  167. package/src/hooks/useConfigurationState.ts +0 -436
  168. package/src/hooks/useFilterState.ts +0 -150
  169. package/src/hooks/useResolutionState.ts +0 -893
  170. package/src/hooks/useResourceData.ts +0 -596
  171. package/src/hooks/useViewState.ts +0 -97
  172. package/src/index.ts +0 -68
  173. package/src/namespaces/ConfigurationTools.ts +0 -59
  174. package/src/namespaces/FilterTools.ts +0 -47
  175. package/src/namespaces/ImportTools.ts +0 -42
  176. package/src/namespaces/PickerTools.ts +0 -104
  177. package/src/namespaces/ResolutionTools.ts +0 -68
  178. package/src/namespaces/ResourceTools.ts +0 -106
  179. package/src/namespaces/TsResTools.ts +0 -49
  180. package/src/namespaces/ViewStateTools.ts +0 -91
  181. package/src/namespaces/ZipTools.ts +0 -49
  182. package/src/namespaces/index.ts +0 -49
  183. package/src/types/index.ts +0 -1273
  184. package/src/utils/configurationUtils.ts +0 -339
  185. package/src/utils/fileProcessing.ts +0 -164
  186. package/src/utils/filterResources.ts +0 -356
  187. package/src/utils/resolutionEditing.ts +0 -346
  188. package/src/utils/resolutionUtils.ts +0 -740
  189. package/src/utils/tsResIntegration.ts +0 -475
  190. package/src/utils/zipLoader/index.ts +0 -5
  191. package/src/utils/zipLoader/zipProcessingHelpers.ts +0 -46
  192. package/src/utils/zipLoader/zipUtils.ts +0 -7
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @fgv/ts-res-ui-components
2
2
 
3
- React components for building user interfaces that work with the [ts-res](https://github.com/fgv/ts-res) multidimensional resource management library.
3
+ React components for building user interfaces that work with the [ts-res](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) multidimensional resource management library.
4
4
 
5
5
  ## Overview
6
6
 
@@ -19,20 +19,10 @@ This packlet is largely AI written, and it shows.
19
19
  - **📊 Visualization**: Multiple views for exploring resource structures and compiled output
20
20
  - **⚙️ Configuration**: Visual configuration management for qualifier types, qualifiers, and resource types
21
21
  - **🎛️ Host Control**: Programmatic control of qualifier values and resource types
22
- - **📁 File Handling**: Support for directory imports, ZIP files via ts-res zip-archive packlet, and bundle loading
22
+ - **📁 File Handling**: Support for directory imports, ZIP files, and bundle loading
23
+ - **🐛 Built-in Debugging**: Comprehensive logging and observability throughout operations
23
24
  - **🎨 Modern UI**: Built with Tailwind CSS and Heroicons for a clean, responsive interface
24
25
 
25
- ### ZIP Archive Integration
26
-
27
- This library now uses the **ts-res zip-archive packlet** as the single source of truth for all ZIP operations, providing:
28
-
29
- - **Universal compatibility**: Works in both browser and Node.js environments using fflate
30
- - **Standardized format**: Common ZIP bundle format across all ts-res tools
31
- - **Simplified API**: Direct integration with ts-res ZIP archive functionality
32
- - **Processing helpers**: Utilities to integrate ZIP data with ts-res-ui-components workflows
33
-
34
- The `ImportView` component handles ZIP files automatically, and the `ZipTools` namespace provides processing helpers for custom ZIP workflows.
35
-
36
26
  ## Installation
37
27
 
38
28
  ```bash
@@ -45,133 +35,62 @@ This library requires the following peer dependencies:
45
35
 
46
36
  ```json
47
37
  {
48
- "@fgv/ts-res": "^5.0.0",
49
- "@fgv/ts-utils": "^5.0.0",
50
- "@fgv/ts-json-base": "^5.0.0",
51
- "react": "^18.0.0",
52
- "react-dom": "^18.0.0"
38
+ "@fgv/ts-res": ">=5.0.0",
39
+ "@fgv/ts-utils": ">=5.0.0",
40
+ "@fgv/ts-json-base": ">=5.0.0",
41
+ "@fgv/ts-json": ">=5.0.0",
42
+ "react": ">=18.0.0",
43
+ "react-dom": ">=18.0.0"
53
44
  }
54
45
  ```
55
46
 
56
47
  ## Quick Start
57
48
 
58
- ### Minimal Editing App (Unified Apply)
49
+ ### Basic Setup
59
50
 
60
51
  ```tsx
61
52
  import React from 'react';
62
- import { ResourceOrchestrator, ResolutionView } from '@fgv/ts-res-ui-components';
53
+ import { ResourceOrchestrator, ObservabilityProvider } from '@fgv/ts-res-ui-components';
63
54
 
64
- export default function App() {
55
+ function App() {
65
56
  return (
66
- <ResourceOrchestrator>
67
- {({ state, actions }) => (
68
- <ResolutionView
69
- resources={state.resources}
70
- resolutionState={state.resolutionState}
71
- resolutionActions={{
72
- updateContextValue: actions.updateResolutionContext,
73
- applyContext: actions.applyResolutionContext,
74
- selectResource: actions.selectResourceForResolution,
75
- setViewMode: actions.setResolutionViewMode,
76
- resetCache: actions.resetResolutionCache,
77
- saveEdit: actions.saveResourceEdit,
78
- getEditedValue: actions.getEditedValue,
79
- hasEdit: actions.hasResourceEdit,
80
- clearEdits: actions.clearResourceEdits,
81
- discardEdits: actions.discardResourceEdits,
82
- startNewResource: actions.startNewResource,
83
- updateNewResourceId: actions.updateNewResourceId,
84
- selectResourceType: actions.selectResourceType,
85
- saveNewResourceAsPending: actions.saveNewResourceAsPending,
86
- cancelNewResource: actions.cancelNewResource,
87
- removePendingResource: actions.removePendingResource,
88
- markResourceForDeletion: actions.markResourceForDeletion,
89
- applyPendingResources: actions.applyPendingResources,
90
- discardPendingResources: actions.discardPendingResources
91
- }}
92
- allowResourceCreation
93
- defaultResourceType="json"
94
- showPendingResourcesInList
95
- onMessage={actions.addMessage}
96
- />
97
- )}
98
- </ResourceOrchestrator>
57
+ <ObservabilityProvider>
58
+ <ResourceOrchestrator />
59
+ </ObservabilityProvider>
99
60
  );
100
61
  }
101
62
  ```
102
63
 
103
- ### Basic Usage with ResourceOrchestrator
104
-
105
- The `ResourceOrchestrator` component provides centralized state management for all ts-res UI functionality:
106
-
107
- ```tsx
108
- import React from 'react';
109
- import { ResourceOrchestrator, ImportView, SourceView } from '@fgv/ts-res-ui-components';
110
-
111
- function App() {
112
- return (
113
- <ResourceOrchestrator>
114
- {({ state, actions }) => (
115
- <div className="min-h-screen bg-gray-50">
116
- <div className="container mx-auto px-4 py-8">
117
- <h1 className="text-3xl font-bold mb-8">Resource Manager</h1>
118
-
119
- {!state.processedResources ? (
120
- <ImportView
121
- onImport={actions.importDirectory}
122
- onBundleImport={actions.importBundle}
123
- onZipImport={(zipData, config) => {
124
- if (config) actions.applyConfiguration(config);
125
- if (zipData.directory) actions.importDirectory(zipData.directory);
126
- else if (zipData.files?.length) actions.importFiles(zipData.files);
127
- }}
128
- />
129
- ) : (
130
- <SourceView
131
- resources={state.processedResources}
132
- onExport={actions.exportData}
133
- />
134
- )}
135
- </div>
136
- </div>
137
- )}
138
- </ResourceOrchestrator>
139
- );
140
- }
64
+ The `ResourceOrchestrator` provides a complete resource management interface with import, filtering, resolution, and editing capabilities. The `ObservabilityProvider` enables built-in logging and debugging features throughout the component tree.
141
65
 
142
- export default App;
143
- ```
66
+ ## Core Concepts
144
67
 
145
- ### Using Individual Hooks
68
+ ### Resource Orchestrator
69
+ The central component that coordinates all resource operations. It manages the complete workflow:
146
70
 
147
- For more granular control, you can use individual hooks:
71
+ - **Configuration Management**: Set up qualifier types, qualifiers, and resource types
72
+ - **Resource Import**: Load resources from files, ZIP archives, or bundles
73
+ - **Resource Filtering**: Filter resources by context to create deployment subsets
74
+ - **Resource Resolution**: Test how resources resolve for different contexts
75
+ - **Resource Editing**: Create and modify resources with real-time validation
148
76
 
149
- ```tsx
150
- import React from 'react';
151
- import { useResourceData, SourceView } from '@fgv/ts-res-ui-components';
77
+ ### Component Architecture
78
+ The library is organized into specialized namespaces, each containing related components and utilities:
152
79
 
153
- function MyResourceViewer() {
154
- const { state, actions } = useResourceData();
80
+ - **FilterTools**: Resource filtering with context-aware capabilities
81
+ - **ResolutionTools**: Resource resolution testing and editing
82
+ - **ConfigurationTools**: System configuration management
83
+ - **GridTools**: Advanced data grid for tabular resource management
84
+ - **ImportTools**: File and archive import capabilities
85
+ - **ObservabilityTools**: Logging and debugging infrastructure
155
86
 
156
- const handleFileImport = async (files: File[]) => {
157
- const importedFiles = await processFiles(files); // Your file processing logic
158
- await actions.processFiles(importedFiles);
159
- };
87
+ ### Built-in Observability
88
+ All operations include comprehensive logging for debugging and user feedback:
160
89
 
161
- return (
162
- <div>
163
- {state.isProcessing && <div>Processing...</div>}
164
- {state.error && <div className="error">{state.error}</div>}
165
- {state.processedResources && (
166
- <SourceView
167
- resources={state.processedResources}
168
- onExport={(data) => console.log('Export:', data)}
169
- />
170
- )}
171
- </div>
172
- );
173
- }
174
- ```
90
+ - **Diagnostic Logging**: Developer-focused information for troubleshooting
91
+ - **User Messaging**: User-friendly success/error notifications
92
+ - **Configurable Loggers**: Console output for development, silent for production
93
+ - **React Integration**: Observability context available throughout the component tree
175
94
 
176
95
  ## Architecture
177
96
 
@@ -515,47 +434,128 @@ This is particularly useful when:
515
434
 
516
435
  #### Resource Creation
517
436
 
518
- ResolutionView now supports creating new resources directly in the UI with a pending/apply workflow similar to context management:
437
+ ResolutionView supports both atomic and sequential resource creation workflows:
438
+
439
+ ##### Atomic Resource Creation (Recommended)
440
+
441
+ Use the new `createPendingResource` API for simple, robust resource creation:
442
+
443
+ ```tsx
444
+ // Single atomic call to create a pending resource
445
+ const result = await actions.createPendingResource({
446
+ id: 'platform.languages.az-AZ', // Full resource ID (required)
447
+ resourceTypeName: 'json', // Resource type (required)
448
+ json: { text: 'Welcome', locale: 'az-AZ' } // JSON content (optional - uses base template if omitted)
449
+ });
450
+
451
+ if (result.isSuccess()) {
452
+ console.log('Resource created and added to pending resources');
453
+ // Apply all pending changes when ready
454
+ await actions.applyPendingResources();
455
+ } else {
456
+ console.error('Failed to create resource:', result.message);
457
+ }
458
+
459
+ // Example: Creating a resource with base template (no JSON content)
460
+ const baseTemplateResult = await actions.createPendingResource({
461
+ id: 'platform.languages.fr-FR',
462
+ resourceTypeName: 'json'
463
+ // json omitted - resource type will provide base template (typically {})
464
+ });
465
+
466
+ if (baseTemplateResult.isSuccess()) {
467
+ console.log('Resource created using base template from resource type');
468
+ // You can then edit the resource using the UI or saveEdit()
469
+ }
470
+ ```
471
+
472
+ **Benefits of the atomic API:**
473
+ - **Single Operation**: No multi-step sequencing required
474
+ - **Better Error Handling**: Returns `Result<void>` with detailed error messages
475
+ - **Validation**: Comprehensive validation of ID, type, and JSON content
476
+ - **Safe**: Prevents temporary IDs and ensures uniqueness
477
+ - **Context Stamping**: Automatically applies current context as conditions
478
+
479
+ ##### Sequential Resource Creation (Legacy)
480
+
481
+ The traditional step-by-step workflow is still available:
482
+
483
+ ```tsx
484
+ // Step 1: Start a new resource
485
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
486
+ if (startResult.isFailure()) {
487
+ console.error('Failed to start resource:', startResult.message);
488
+ return;
489
+ }
490
+
491
+ // Step 2: Set the final resource ID
492
+ const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
493
+ if (idResult.isFailure()) {
494
+ console.error('Invalid resource ID:', idResult.message);
495
+ return;
496
+ }
497
+
498
+ // Step 3: Update JSON content (optional)
499
+ const jsonResult = actions.updateNewResourceJson({
500
+ text: 'Welcome',
501
+ locale: 'az-AZ'
502
+ });
503
+ if (jsonResult.isFailure()) {
504
+ console.error('Invalid JSON:', jsonResult.message);
505
+ return;
506
+ }
507
+
508
+ // Step 4: Save as pending
509
+ const saveResult = actions.saveNewResourceAsPending();
510
+ if (saveResult.isFailure()) {
511
+ console.error('Failed to save:', saveResult.message);
512
+ return;
513
+ }
514
+
515
+ // Step 5: Apply all pending changes
516
+ await actions.applyPendingResources();
517
+ ```
518
+
519
+ **Enhanced Action Return Values:**
520
+
521
+ All resource creation actions now return `Result<T>` objects following the standard Result pattern:
522
+ - Use `.isSuccess()` and `.isFailure()` to check status
523
+ - Access success values with `.value` property (contains `draft`/`pendingResources` and `diagnostics`)
524
+ - Access error messages with `.message` property
525
+ - Multiline error messages include diagnostic information
526
+
527
+ ```tsx
528
+ // Example: Working with Result values
529
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
530
+ if (startResult.isSuccess()) {
531
+ console.log('Started draft:', startResult.value.draft);
532
+ console.log('Diagnostics:', startResult.value.diagnostics);
533
+ } else {
534
+ console.error('Error:', startResult.message); // May include diagnostics
535
+ }
536
+ ```
537
+
538
+ ##### UI Integration Example
519
539
 
520
540
  ```tsx
521
- // Basic resource creation - user selects resource type
522
541
  <ResolutionView
523
542
  resources={state.processedResources}
524
543
  resolutionState={resolutionState}
525
544
  resolutionActions={resolutionActions}
526
545
  allowResourceCreation={true}
546
+ defaultResourceType="json" // Optional: Host-controlled resource type
547
+ showPendingResourcesInList={true} // Show pending resources with visual distinction
527
548
  onPendingResourcesApplied={(added, deleted) => {
549
+ console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
528
550
  // Rebuild resource manager with new resources
529
551
  const updatedResources = rebuildWithResources(added, deleted);
530
552
  setState({ processedResources: updatedResources });
531
553
  }}
532
554
  />
533
-
534
- // Host-controlled resource type
535
- <ResolutionView
536
- resources={state.processedResources}
537
- resolutionState={resolutionState}
538
- resolutionActions={resolutionActions}
539
- allowResourceCreation={true}
540
- defaultResourceType="json" // Type selector hidden, always creates JSON resources
541
- onPendingResourcesApplied={(added, deleted) => {
542
- console.log(`Applied ${added.length} additions, ${deleted.length} deletions`);
543
- }}
544
- />
545
-
546
- // With custom resource types
547
- <ResolutionView
548
- resources={state.processedResources}
549
- resolutionState={resolutionState}
550
- resolutionActions={resolutionActions}
551
- allowResourceCreation={true}
552
- resourceTypeFactory={[customType1, customType2]} // Provide custom resource types
553
- showPendingResourcesInList={true} // Show pending resources with visual distinction
554
- />
555
555
  ```
556
556
 
557
557
  **Unified Change Workflow (Edits, Additions, Deletions):**
558
- 1. Click "Add Resource" in the picker header to create new resources (if enabled)
558
+ 1. Use `createPendingResource()` or the traditional UI workflow to create resources
559
559
  2. Edit existing resources from the results pane using the JSON or custom editors
560
560
  3. Mark resources for deletion where supported
561
561
  4. All changes appear in a single "Pending Changes" bar with counts (edits/additions/deletions)
@@ -563,12 +563,70 @@ ResolutionView now supports creating new resources directly in the UI with a pen
563
563
  6. Or click "Discard Changes" to remove all pending changes
564
564
 
565
565
  **Key features:**
566
+ - **Atomic Creation**: Single API call creates complete resources
567
+ - **Enhanced Error Handling**: Detailed validation and error messages
566
568
  - **Template-based creation**: Resource types provide default templates for new resources
567
569
  - **Pending workflow**: Resources aren't added immediately, allowing review before applying
568
570
  - **Host control**: Can specify a default resource type to hide the type selector
569
571
  - **Batch operations**: Create multiple resources before applying
570
572
  - **Visual feedback**: Pending resources shown with distinct styling
571
- - **Validation**: Resource IDs are validated for uniqueness
573
+ - **ID Validation**: Comprehensive resource ID validation and uniqueness checking
574
+
575
+ ##### Resource ID Requirements
576
+
577
+ **IMPORTANT**: All pending resources use **full resource IDs** as keys, never leaf IDs:
578
+
579
+ ```tsx
580
+ // ✅ Correct: Full resource IDs
581
+ createPendingResource({
582
+ id: 'platform.languages.az-AZ', // Full path
583
+ resourceTypeName: 'json',
584
+ json: { text: 'Azərbaycan dili' }
585
+ });
586
+
587
+ // ❌ Wrong: Leaf IDs
588
+ createPendingResource({
589
+ id: 'az-AZ', // Just the leaf - will be rejected
590
+ resourceTypeName: 'json',
591
+ json: { text: 'Azərbaycan dili' }
592
+ });
593
+ ```
594
+
595
+ ##### Helper Functions for Pending Resources
596
+
597
+ ```tsx
598
+ import { ResolutionTools } from '@fgv/ts-res-ui-components';
599
+
600
+ // Get pending resources by type
601
+ const jsonResources = ResolutionTools.getPendingAdditionsByType(
602
+ resolutionState.pendingResources,
603
+ 'json'
604
+ );
605
+
606
+ // Check if a resource is pending
607
+ const isPending = ResolutionTools.isPendingAddition(
608
+ 'platform.languages.az-AZ',
609
+ resolutionState.pendingResources
610
+ );
611
+
612
+ // Work with resource IDs
613
+ const leafIdResult = ResolutionTools.deriveLeafId('platform.languages.az-AZ');
614
+ console.log(leafIdResult.value); // 'az-AZ'
615
+
616
+ const fullIdResult = ResolutionTools.deriveFullId('platform.languages', 'az-AZ');
617
+ console.log(fullIdResult.value); // 'platform.languages.az-AZ'
618
+
619
+ // Get statistics
620
+ const stats = ResolutionTools.getPendingResourceStats(resolutionState.pendingResources);
621
+ console.log(`${stats.totalCount} pending resources`);
622
+ console.log(`Types: ${Object.keys(stats.byType).join(', ')}`);
623
+
624
+ // Validate pending resource keys (diagnostic)
625
+ const validation = ResolutionTools.validatePendingResourceKeys(resolutionState.pendingResources);
626
+ if (validation.isFailure()) {
627
+ console.error('Key validation failed:', validation.message);
628
+ }
629
+ ```
572
630
 
573
631
  #### Custom Resource Editors
574
632
 
@@ -1254,6 +1312,181 @@ All components accept a `className` prop for custom styling:
1254
1312
  />
1255
1313
  ```
1256
1314
 
1315
+ ## Detailed Examples
1316
+
1317
+ ### Canonical New Resource Flow
1318
+
1319
+ The recommended approach for programmatic resource creation:
1320
+
1321
+ ```tsx
1322
+ import { ResolutionTools } from '@fgv/ts-res-ui-components';
1323
+
1324
+ // Single atomic operation (recommended)
1325
+ async function createResourceAtomic(actions) {
1326
+ const result = await actions.createPendingResource({
1327
+ id: 'platform.languages.az-AZ',
1328
+ resourceTypeName: 'json',
1329
+ json: { text: 'Welcome', locale: 'az-AZ' }
1330
+ });
1331
+
1332
+ if (result.isSuccess()) {
1333
+ console.log('Resource created successfully');
1334
+ await actions.applyPendingResources();
1335
+ } else {
1336
+ console.error('Creation failed:', result.message);
1337
+ }
1338
+ }
1339
+
1340
+ // Step-by-step workflow (if needed)
1341
+ function createResourceStepByStep(actions) {
1342
+ // 1) Start new resource
1343
+ const startResult = actions.startNewResource({ defaultTypeName: 'json' });
1344
+ if (!startResult.success) return;
1345
+
1346
+ // 2) Update resource ID
1347
+ const idResult = actions.updateNewResourceId('platform.languages.az-AZ');
1348
+ if (!idResult.success) return;
1349
+
1350
+ // 3) Select resource type (if step 1 didn't set it)
1351
+ const typeResult = actions.selectResourceType('json');
1352
+ if (!typeResult.success) return;
1353
+
1354
+ // 4) Update JSON content (optional, recommended)
1355
+ const jsonResult = actions.updateNewResourceJson({
1356
+ text: 'Welcome',
1357
+ locale: 'az-AZ'
1358
+ });
1359
+ if (!jsonResult.success) return;
1360
+
1361
+ // 5) Save as pending
1362
+ const saveResult = actions.saveNewResourceAsPending();
1363
+ if (!saveResult.success) return;
1364
+
1365
+ // Note: Do NOT call saveResourceEdit for brand-new resources
1366
+ }
1367
+ ```
1368
+
1369
+ ### Minimal Editing App (Unified Apply)
1370
+
1371
+ ```tsx
1372
+ import React from 'react';
1373
+ import { ResourceOrchestrator, ResolutionView } from '@fgv/ts-res-ui-components';
1374
+
1375
+ export default function App() {
1376
+ return (
1377
+ <ResourceOrchestrator>
1378
+ {({ state, actions }) => (
1379
+ <ResolutionView
1380
+ resources={state.resources}
1381
+ resolutionState={state.resolutionState}
1382
+ resolutionActions={{
1383
+ updateContextValue: actions.updateResolutionContext,
1384
+ applyContext: actions.applyResolutionContext,
1385
+ selectResource: actions.selectResourceForResolution,
1386
+ setViewMode: actions.setResolutionViewMode,
1387
+ resetCache: actions.resetResolutionCache,
1388
+ saveEdit: actions.saveResourceEdit,
1389
+ getEditedValue: actions.getEditedValue,
1390
+ hasEdit: actions.hasResourceEdit,
1391
+ clearEdits: actions.clearResourceEdits,
1392
+ discardEdits: actions.discardResourceEdits,
1393
+ // Enhanced resource creation actions with atomic API
1394
+ createPendingResource: actions.createPendingResource,
1395
+ startNewResource: actions.startNewResource,
1396
+ updateNewResourceId: actions.updateNewResourceId,
1397
+ selectResourceType: actions.selectResourceType,
1398
+ updateNewResourceJson: actions.updateNewResourceJson,
1399
+ saveNewResourceAsPending: actions.saveNewResourceAsPending,
1400
+ cancelNewResource: actions.cancelNewResource,
1401
+ removePendingResource: actions.removePendingResource,
1402
+ markResourceForDeletion: actions.markResourceForDeletion,
1403
+ applyPendingResources: actions.applyPendingResources,
1404
+ discardPendingResources: actions.discardPendingResources
1405
+ }}
1406
+ allowResourceCreation
1407
+ defaultResourceType="json"
1408
+ showPendingResourcesInList
1409
+ onMessage={actions.addMessage}
1410
+ />
1411
+ )}
1412
+ </ResourceOrchestrator>
1413
+ );
1414
+ }
1415
+ ```
1416
+
1417
+ ### Basic Usage with ResourceOrchestrator
1418
+
1419
+ The `ResourceOrchestrator` component provides centralized state management for all ts-res UI functionality:
1420
+
1421
+ ```tsx
1422
+ import React from 'react';
1423
+ import { ResourceOrchestrator, ImportView, SourceView } from '@fgv/ts-res-ui-components';
1424
+
1425
+ function App() {
1426
+ return (
1427
+ <ResourceOrchestrator>
1428
+ {({ state, actions }) => (
1429
+ <div className="min-h-screen bg-gray-50">
1430
+ <div className="container mx-auto px-4 py-8">
1431
+ <h1 className="text-3xl font-bold mb-8">Resource Manager</h1>
1432
+
1433
+ {!state.processedResources ? (
1434
+ <ImportView
1435
+ onImport={actions.importDirectory}
1436
+ onBundleImport={actions.importBundle}
1437
+ onZipImport={(zipData, config) => {
1438
+ if (config) actions.applyConfiguration(config);
1439
+ if (zipData.directory) actions.importDirectory(zipData.directory);
1440
+ else if (zipData.files?.length) actions.importFiles(zipData.files);
1441
+ }}
1442
+ />
1443
+ ) : (
1444
+ <SourceView
1445
+ resources={state.processedResources}
1446
+ onExport={actions.exportData}
1447
+ />
1448
+ )}
1449
+ </div>
1450
+ </div>
1451
+ )}
1452
+ </ResourceOrchestrator>
1453
+ );
1454
+ }
1455
+
1456
+ export default App;
1457
+ ```
1458
+
1459
+ ### Using Individual Hooks
1460
+
1461
+ For more granular control, you can use individual hooks:
1462
+
1463
+ ```tsx
1464
+ import React from 'react';
1465
+ import { useResourceData, SourceView } from '@fgv/ts-res-ui-components';
1466
+
1467
+ function MyResourceViewer() {
1468
+ const { state, actions } = useResourceData();
1469
+
1470
+ const handleFileImport = async (files: File[]) => {
1471
+ const importedFiles = await processFiles(files); // Your file processing logic
1472
+ await actions.processFiles(importedFiles);
1473
+ };
1474
+
1475
+ return (
1476
+ <div>
1477
+ {state.isProcessing && <div>Processing...</div>}
1478
+ {state.error && <div className="error">{state.error}</div>}
1479
+ {state.processedResources && (
1480
+ <SourceView
1481
+ resources={state.processedResources}
1482
+ onExport={(data) => console.log('Export:', data)}
1483
+ />
1484
+ )}
1485
+ </div>
1486
+ );
1487
+ }
1488
+ ```
1489
+
1257
1490
  ## Advanced Usage
1258
1491
 
1259
1492
  ### Custom Resource Processing
@@ -1349,21 +1582,25 @@ For better organization and discoverability, utility functions are organized int
1349
1582
 
1350
1583
  ```tsx
1351
1584
  import {
1352
- FilterTools, // FilterView + filtering utilities
1353
- ResolutionTools, // ResolutionView + resolution utilities
1585
+ FilterTools, // FilterView + filtering utilities
1586
+ ResolutionTools, // ResolutionView + resolution utilities
1354
1587
  ConfigurationTools, // ConfigurationView + configuration utilities
1355
- TsResTools, // SourceView, CompiledView + ts-res utilities
1356
- ViewTools, // MessagesWindow + view state utilities
1357
- ZipTools, // ImportView + ZIP processing helpers
1358
- FileTools // File processing utilities
1588
+ TsResTools, // SourceView, CompiledView + ts-res utilities
1589
+ ViewStateTools, // MessagesWindow + view state utilities
1590
+ ZipTools, // ImportView + ZIP processing helpers
1591
+ ObservabilityTools, // Observability context and loggers
1592
+ GridTools, // GridView + data grid utilities
1593
+ PickerTools, // ResourcePicker + picker utilities
1594
+ ImportTools // Import utilities and types
1359
1595
  } from '@fgv/ts-res-ui-components';
1360
1596
 
1361
1597
  // Use view components from namespaces
1362
1598
  <FilterTools.FilterView {...filterProps} />
1363
1599
  <ResolutionTools.ResolutionView {...resolutionProps} />
1364
- <ViewTools.MessagesWindow {...messageProps} />
1600
+ <ViewStateTools.MessagesWindow {...messageProps} />
1365
1601
  <TsResTools.SourceView {...sourceProps} />
1366
1602
  <ZipTools.ImportView {...importProps} />
1603
+ <GridTools.GridView {...gridProps} />
1367
1604
 
1368
1605
  // Use utility functions from namespaces
1369
1606
  const hasFilters = FilterTools.hasFilterValues(filterState.values);
@@ -1372,6 +1609,15 @@ const system = await TsResTools.createTsResSystemFromConfig(config);
1372
1609
 
1373
1610
  // ZIP processing helpers for ts-res-ui-components integration
1374
1611
  const processResult = await ZipTools.processZipLoadResult(zipData, config);
1612
+
1613
+ // Observability context and loggers
1614
+ const consoleContext = ObservabilityTools.createConsoleObservabilityContext('debug', 'info');
1615
+ const noOpContext = ObservabilityTools.createNoOpObservabilityContext();
1616
+ const customLogger = new ObservabilityTools.ConsoleUserLogger('info');
1617
+
1618
+ // Grid utilities and selectors
1619
+ const gridSelector = new GridTools.ResourceSelector();
1620
+ const validation = GridTools.validateCellValue(value, rules);
1375
1621
  ```
1376
1622
 
1377
1623
  ### Namespace Contents
@@ -1596,6 +1842,6 @@ The API documentation includes detailed examples, usage patterns, and type infor
1596
1842
  For questions and support, please:
1597
1843
 
1598
1844
  1. Check the [API documentation](./docs/index.md) for detailed component usage
1599
- 2. Review the [ts-res documentation](https://docs.ts-res.dev) for core concepts
1600
- 3. Search [existing issues](https://github.com/fgv/ts-res/issues)
1601
- 4. Create a [new issue](https://github.com/fgv/ts-res/issues/new)
1845
+ 2. Review the [ts-res documentation](https://github.com/ErikFortune/fgv/tree/main/libraries/ts-res) for core concepts
1846
+ 3. Search [existing issues](https://github.com/ErikFortune/fgv/issues)
1847
+ 4. Create a [new issue](https://github.com/ErikFortune/fgv/issues/new)
@@ -53,6 +53,16 @@ global.URL = {
53
53
  // Extend jest matchers (toBeInTheDocument, etc.)
54
54
  require('@testing-library/jest-dom');
55
55
 
56
+ // Suppress console output in tests by providing a no-op logger
57
+ // This affects both direct console calls and components that use logger dependency injection
58
+ global.testLogger = () => {}; // No-op logger for tests
59
+ global.console = {
60
+ ...console,
61
+ log: global.testLogger,
62
+ warn: global.testLogger,
63
+ error: console.error // Keep error for actual test failures
64
+ };
65
+
56
66
  // Mock document methods for file export
57
67
  global.document = {
58
68
  createElement: jest.fn(() => ({