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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -55,6 +55,51 @@ This library requires the following peer dependencies:
55
55
 
56
56
  ## Quick Start
57
57
 
58
+ ### Minimal Editing App (Unified Apply)
59
+
60
+ ```tsx
61
+ import React from 'react';
62
+ import { ResourceOrchestrator, ResolutionView } from '@fgv/ts-res-ui-components';
63
+
64
+ export default function App() {
65
+ 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>
99
+ );
100
+ }
101
+ ```
102
+
58
103
  ### Basic Usage with ResourceOrchestrator
59
104
 
60
105
  The `ResourceOrchestrator` component provides centralized state management for all ts-res UI functionality:
@@ -75,7 +120,11 @@ function App() {
75
120
  <ImportView
76
121
  onImport={actions.importDirectory}
77
122
  onBundleImport={actions.importBundle}
78
- onZipImport={actions.importZipWithConfig}
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
+ }}
79
128
  />
80
129
  ) : (
81
130
  <SourceView
@@ -680,9 +680,10 @@ declare function createTsResSystemFromConfig(systemConfig?: Config.Model.ISystem
680
680
  * <ResolutionTools.UnifiedChangeControls
681
681
  * editCount={state.editedResources.size}
682
682
  * isApplying={state.isApplyingEdits}
683
- * hasEdits={state.hasUnsavedEdits}
684
- * onApplyEdits={actions.applyEdits}
685
- * onDiscardEdits={actions.clearEdits}
683
+ * addCount={state.pendingResources.size}
684
+ * deleteCount={state.pendingResourceDeletions.size}
685
+ * onApplyAll={actions.applyPendingResources}
686
+ * onDiscardAll={() => { actions.clearEdits(); actions.discardPendingResources(); }}
686
687
  * />
687
688
  * </div>
688
689
  * )}
@@ -2073,8 +2074,8 @@ declare interface MessagesWindowProps {
2073
2074
  * const newValue = { text: 'Updated welcome message' };
2074
2075
  * actions.saveResourceEdit('user.welcome', newValue);
2075
2076
  *
2076
- * // Apply all edits
2077
- * await actions.applyResourceEdits();
2077
+ * // Apply all pending changes (edits + new resources)
2078
+ * await actions.applyPendingResources();
2078
2079
  * ```
2079
2080
  *
2080
2081
  * @example
@@ -2133,7 +2134,6 @@ export declare interface OrchestratorActions {
2133
2134
  getEditedValue: (resourceId: string) => JsonValue | undefined;
2134
2135
  hasResourceEdit: (resourceId: string) => boolean;
2135
2136
  clearResourceEdits: () => void;
2136
- applyResourceEdits: () => Promise<void>;
2137
2137
  discardResourceEdits: () => void;
2138
2138
  startNewResource: (defaultTypeName?: string) => void;
2139
2139
  updateNewResourceId: (id: string) => void;
@@ -2825,8 +2825,6 @@ declare interface ResolutionActions {
2825
2825
  hasEdit: (resourceId: string) => boolean;
2826
2826
  /** Clear all pending edits */
2827
2827
  clearEdits: () => void;
2828
- /** Apply all edits to the resource system */
2829
- applyEdits: () => Promise<void>;
2830
2828
  /** Discard all pending edits */
2831
2829
  discardEdits: () => void;
2832
2830
  /** Start creating a new resource */
@@ -309,7 +309,7 @@ export const ResourceOrchestrator = ({ children, initialConfiguration, qualifier
309
309
  getEditedValue: resolutionData.actions.getEditedValue,
310
310
  hasResourceEdit: resolutionData.actions.hasEdit,
311
311
  clearResourceEdits: resolutionData.actions.clearEdits,
312
- applyResourceEdits: resolutionData.actions.applyEdits,
312
+ // Edits applied through unified applyPendingResources
313
313
  discardResourceEdits: resolutionData.actions.discardEdits,
314
314
  // Resource creation actions
315
315
  startNewResource: resolutionData.actions.startNewResource,
@@ -175,9 +175,10 @@ export interface EditableJsonViewProps {
175
175
  * <ResolutionTools.UnifiedChangeControls
176
176
  * editCount={state.editedResources.size}
177
177
  * isApplying={state.isApplyingEdits}
178
- * hasEdits={state.hasUnsavedEdits}
179
- * onApplyEdits={actions.applyEdits}
180
- * onDiscardEdits={actions.clearEdits}
178
+ * addCount={state.pendingResources.size}
179
+ * deleteCount={state.pendingResourceDeletions.size}
180
+ * onApplyAll={actions.applyPendingResources}
181
+ * onDiscardAll={() => { actions.clearEdits(); actions.discardPendingResources(); }}
181
182
  * />
182
183
  * </div>
183
184
  * )}
@@ -155,9 +155,10 @@ import { validateEditedResource } from '../../../utils/resolutionEditing';
155
155
  * <ResolutionTools.UnifiedChangeControls
156
156
  * editCount={state.editedResources.size}
157
157
  * isApplying={state.isApplyingEdits}
158
- * hasEdits={state.hasUnsavedEdits}
159
- * onApplyEdits={actions.applyEdits}
160
- * onDiscardEdits={actions.clearEdits}
158
+ * addCount={state.pendingResources.size}
159
+ * deleteCount={state.pendingResourceDeletions.size}
160
+ * onApplyAll={actions.applyPendingResources}
161
+ * onDiscardAll={() => { actions.clearEdits(); actions.discardPendingResources(); }}
161
162
  * />
162
163
  * </div>
163
164
  * )}
@@ -1,7 +1,7 @@
1
1
  import React, { useState, useCallback, useMemo } from 'react';
2
2
  import { Runtime } from '@fgv/ts-res';
3
3
  import { createResolverWithContext, resolveResourceDetailed, getAvailableQualifiers, hasPendingContextChanges } from '../utils/resolutionUtils';
4
- import { validateEditedResource, computeResourceDelta, rebuildSystemWithEdits, rebuildSystemWithChanges, extractResolutionContext, checkEditConflicts } from '../utils/resolutionEditing';
4
+ import { validateEditedResource, computeResourceDelta, rebuildSystemWithChanges, extractResolutionContext } from '../utils/resolutionEditing';
5
5
  /**
6
6
  * Hook for managing resource resolution state and editing operations.
7
7
  *
@@ -447,59 +447,7 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
447
447
  onMessage?.('info', 'All unsaved edits discarded');
448
448
  }
449
449
  }, [hasUnsavedEdits, onMessage]);
450
- const applyEdits = useCallback(async () => {
451
- if (!processedResources || editedResources.size === 0) {
452
- onMessage?.('warning', 'No edits to apply');
453
- return;
454
- }
455
- if (!onSystemUpdate) {
456
- onMessage?.('error', 'System update callback not provided');
457
- return;
458
- }
459
- setIsApplyingEdits(true);
460
- try {
461
- // Extract current resolution context (filter out undefined values)
462
- const cleanedContextValues = {};
463
- Object.entries(effectiveContext).forEach(([key, value]) => {
464
- if (value !== undefined) {
465
- cleanedContextValues[key] = value;
466
- }
467
- });
468
- const currentContext = extractResolutionContext(currentResolver, cleanedContextValues);
469
- // Check for potential conflicts
470
- const conflictCheck = checkEditConflicts(processedResources.system.resourceManager, editedResourcesInternal, currentContext);
471
- // Show warnings about potential conflicts
472
- conflictCheck.warnings.forEach((warning) => onMessage?.('warning', warning));
473
- if (conflictCheck.conflicts.length > 0) {
474
- onMessage?.('error', `Conflicts detected: ${conflictCheck.conflicts.join(', ')}`);
475
- return;
476
- }
477
- // Rebuild the system with edits
478
- const rebuildResult = await rebuildSystemWithEdits(processedResources.system, editedResourcesInternal, currentContext);
479
- if (rebuildResult.isFailure()) {
480
- onMessage?.('error', `Failed to apply edits: ${rebuildResult.message}`);
481
- return;
482
- }
483
- // Update the system through the callback
484
- onSystemUpdate(rebuildResult.value);
485
- // Clear edits after successful application
486
- setEditedResourcesInternal(new Map());
487
- onMessage?.('success', `Successfully applied ${editedResourcesInternal.size} edit(s)`);
488
- }
489
- catch (error) {
490
- onMessage?.('error', `Error applying edits: ${error instanceof Error ? error.message : String(error)}`);
491
- }
492
- finally {
493
- setIsApplyingEdits(false);
494
- }
495
- }, [
496
- processedResources,
497
- editedResourcesInternal,
498
- onSystemUpdate,
499
- currentResolver,
500
- onMessage,
501
- effectiveContext
502
- ]);
450
+ // Removed applyEdits in favor of unified applyPendingResources
503
451
  // Resource creation actions
504
452
  const startNewResource = useCallback((defaultTypeName) => {
505
453
  const defaultType = defaultTypeName
@@ -616,9 +564,10 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
616
564
  onMessage?.('info', `Marked resource ${resourceId} for deletion`);
617
565
  }, [onMessage]);
618
566
  const applyPendingResources = useCallback(async () => {
619
- if (!hasPendingResourceChanges || !processedResources || !onSystemUpdate) {
620
- if (!hasPendingResourceChanges) {
621
- onMessage?.('warning', 'No pending resource changes to apply');
567
+ const hasAnyChanges = editedResourcesInternal.size > 0 || pendingResources.size > 0 || pendingResourceDeletions.size > 0;
568
+ if (!hasAnyChanges || !processedResources || !onSystemUpdate) {
569
+ if (!hasAnyChanges) {
570
+ onMessage?.('warning', 'No pending changes to apply');
622
571
  }
623
572
  else if (!processedResources) {
624
573
  onMessage?.('error', 'No resource system available');
@@ -629,6 +578,7 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
629
578
  return;
630
579
  }
631
580
  try {
581
+ setIsApplyingEdits(true);
632
582
  // Extract current resolution context (filter out undefined values)
633
583
  const cleanedContextValues = {};
634
584
  Object.entries(effectiveContext).forEach(([key, value]) => {
@@ -663,7 +613,7 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
663
613
  return;
664
614
  }
665
615
  onSystemUpdate(rebuildResult.value);
666
- onMessage?.('success', `Applied ${newResourcesArray.length} additions and ${pendingResourceDeletions.size} deletions`);
616
+ onMessage?.('success', `Applied ${editedResourcesInternal.size} edits, ${newResourcesArray.length} additions, and ${pendingResourceDeletions.size} deletions`);
667
617
  // Clear pending additions after successful application (deletions still deferred)
668
618
  setPendingResources(new Map());
669
619
  setPendingResourceDeletions(new Set());
@@ -675,8 +625,10 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
675
625
  catch (error) {
676
626
  onMessage?.('error', `Failed to apply pending resources: ${error instanceof Error ? error.message : String(error)}`);
677
627
  }
628
+ finally {
629
+ setIsApplyingEdits(false);
630
+ }
678
631
  }, [
679
- hasPendingResourceChanges,
680
632
  pendingResources,
681
633
  pendingResourceDeletions,
682
634
  processedResources,
@@ -723,7 +675,6 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
723
675
  getEditedValue,
724
676
  hasEdit,
725
677
  clearEdits,
726
- applyEdits,
727
678
  discardEdits,
728
679
  // Resource creation actions
729
680
  startNewResource,
@@ -745,7 +696,6 @@ export function useResolutionState(processedResources, onMessage, onSystemUpdate
745
696
  getEditedValue,
746
697
  hasEdit,
747
698
  clearEdits,
748
- applyEdits,
749
699
  discardEdits,
750
700
  startNewResource,
751
701
  updateNewResourceId,
@@ -436,8 +436,6 @@ export interface ResolutionActions {
436
436
  hasEdit: (resourceId: string) => boolean;
437
437
  /** Clear all pending edits */
438
438
  clearEdits: () => void;
439
- /** Apply all edits to the resource system */
440
- applyEdits: () => Promise<void>;
441
439
  /** Discard all pending edits */
442
440
  discardEdits: () => void;
443
441
  /** Start creating a new resource */
@@ -1117,8 +1115,8 @@ export interface OrchestratorState {
1117
1115
  * const newValue = { text: 'Updated welcome message' };
1118
1116
  * actions.saveResourceEdit('user.welcome', newValue);
1119
1117
  *
1120
- * // Apply all edits
1121
- * await actions.applyResourceEdits();
1118
+ * // Apply all pending changes (edits + new resources)
1119
+ * await actions.applyPendingResources();
1122
1120
  * ```
1123
1121
  *
1124
1122
  * @example
@@ -1177,7 +1175,6 @@ export interface OrchestratorActions {
1177
1175
  getEditedValue: (resourceId: string) => JsonValue | undefined;
1178
1176
  hasResourceEdit: (resourceId: string) => boolean;
1179
1177
  clearResourceEdits: () => void;
1180
- applyResourceEdits: () => Promise<void>;
1181
1178
  discardResourceEdits: () => void;
1182
1179
  startNewResource: (defaultTypeName?: string) => void;
1183
1180
  updateNewResourceId: (id: string) => void;
@@ -313,7 +313,7 @@ const ResourceOrchestrator = ({ children, initialConfiguration, qualifierTypeFac
313
313
  getEditedValue: resolutionData.actions.getEditedValue,
314
314
  hasResourceEdit: resolutionData.actions.hasEdit,
315
315
  clearResourceEdits: resolutionData.actions.clearEdits,
316
- applyResourceEdits: resolutionData.actions.applyEdits,
316
+ // Edits applied through unified applyPendingResources
317
317
  discardResourceEdits: resolutionData.actions.discardEdits,
318
318
  // Resource creation actions
319
319
  startNewResource: resolutionData.actions.startNewResource,
@@ -159,9 +159,10 @@ const resolutionEditing_1 = require("../../../utils/resolutionEditing");
159
159
  * <ResolutionTools.UnifiedChangeControls
160
160
  * editCount={state.editedResources.size}
161
161
  * isApplying={state.isApplyingEdits}
162
- * hasEdits={state.hasUnsavedEdits}
163
- * onApplyEdits={actions.applyEdits}
164
- * onDiscardEdits={actions.clearEdits}
162
+ * addCount={state.pendingResources.size}
163
+ * deleteCount={state.pendingResourceDeletions.size}
164
+ * onApplyAll={actions.applyPendingResources}
165
+ * onDiscardAll={() => { actions.clearEdits(); actions.discardPendingResources(); }}
165
166
  * />
166
167
  * </div>
167
168
  * )}
@@ -451,59 +451,7 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
451
451
  onMessage?.('info', 'All unsaved edits discarded');
452
452
  }
453
453
  }, [hasUnsavedEdits, onMessage]);
454
- const applyEdits = (0, react_1.useCallback)(async () => {
455
- if (!processedResources || editedResources.size === 0) {
456
- onMessage?.('warning', 'No edits to apply');
457
- return;
458
- }
459
- if (!onSystemUpdate) {
460
- onMessage?.('error', 'System update callback not provided');
461
- return;
462
- }
463
- setIsApplyingEdits(true);
464
- try {
465
- // Extract current resolution context (filter out undefined values)
466
- const cleanedContextValues = {};
467
- Object.entries(effectiveContext).forEach(([key, value]) => {
468
- if (value !== undefined) {
469
- cleanedContextValues[key] = value;
470
- }
471
- });
472
- const currentContext = (0, resolutionEditing_1.extractResolutionContext)(currentResolver, cleanedContextValues);
473
- // Check for potential conflicts
474
- const conflictCheck = (0, resolutionEditing_1.checkEditConflicts)(processedResources.system.resourceManager, editedResourcesInternal, currentContext);
475
- // Show warnings about potential conflicts
476
- conflictCheck.warnings.forEach((warning) => onMessage?.('warning', warning));
477
- if (conflictCheck.conflicts.length > 0) {
478
- onMessage?.('error', `Conflicts detected: ${conflictCheck.conflicts.join(', ')}`);
479
- return;
480
- }
481
- // Rebuild the system with edits
482
- const rebuildResult = await (0, resolutionEditing_1.rebuildSystemWithEdits)(processedResources.system, editedResourcesInternal, currentContext);
483
- if (rebuildResult.isFailure()) {
484
- onMessage?.('error', `Failed to apply edits: ${rebuildResult.message}`);
485
- return;
486
- }
487
- // Update the system through the callback
488
- onSystemUpdate(rebuildResult.value);
489
- // Clear edits after successful application
490
- setEditedResourcesInternal(new Map());
491
- onMessage?.('success', `Successfully applied ${editedResourcesInternal.size} edit(s)`);
492
- }
493
- catch (error) {
494
- onMessage?.('error', `Error applying edits: ${error instanceof Error ? error.message : String(error)}`);
495
- }
496
- finally {
497
- setIsApplyingEdits(false);
498
- }
499
- }, [
500
- processedResources,
501
- editedResourcesInternal,
502
- onSystemUpdate,
503
- currentResolver,
504
- onMessage,
505
- effectiveContext
506
- ]);
454
+ // Removed applyEdits in favor of unified applyPendingResources
507
455
  // Resource creation actions
508
456
  const startNewResource = (0, react_1.useCallback)((defaultTypeName) => {
509
457
  const defaultType = defaultTypeName
@@ -620,9 +568,10 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
620
568
  onMessage?.('info', `Marked resource ${resourceId} for deletion`);
621
569
  }, [onMessage]);
622
570
  const applyPendingResources = (0, react_1.useCallback)(async () => {
623
- if (!hasPendingResourceChanges || !processedResources || !onSystemUpdate) {
624
- if (!hasPendingResourceChanges) {
625
- onMessage?.('warning', 'No pending resource changes to apply');
571
+ const hasAnyChanges = editedResourcesInternal.size > 0 || pendingResources.size > 0 || pendingResourceDeletions.size > 0;
572
+ if (!hasAnyChanges || !processedResources || !onSystemUpdate) {
573
+ if (!hasAnyChanges) {
574
+ onMessage?.('warning', 'No pending changes to apply');
626
575
  }
627
576
  else if (!processedResources) {
628
577
  onMessage?.('error', 'No resource system available');
@@ -633,6 +582,7 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
633
582
  return;
634
583
  }
635
584
  try {
585
+ setIsApplyingEdits(true);
636
586
  // Extract current resolution context (filter out undefined values)
637
587
  const cleanedContextValues = {};
638
588
  Object.entries(effectiveContext).forEach(([key, value]) => {
@@ -667,7 +617,7 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
667
617
  return;
668
618
  }
669
619
  onSystemUpdate(rebuildResult.value);
670
- onMessage?.('success', `Applied ${newResourcesArray.length} additions and ${pendingResourceDeletions.size} deletions`);
620
+ onMessage?.('success', `Applied ${editedResourcesInternal.size} edits, ${newResourcesArray.length} additions, and ${pendingResourceDeletions.size} deletions`);
671
621
  // Clear pending additions after successful application (deletions still deferred)
672
622
  setPendingResources(new Map());
673
623
  setPendingResourceDeletions(new Set());
@@ -679,8 +629,10 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
679
629
  catch (error) {
680
630
  onMessage?.('error', `Failed to apply pending resources: ${error instanceof Error ? error.message : String(error)}`);
681
631
  }
632
+ finally {
633
+ setIsApplyingEdits(false);
634
+ }
682
635
  }, [
683
- hasPendingResourceChanges,
684
636
  pendingResources,
685
637
  pendingResourceDeletions,
686
638
  processedResources,
@@ -727,7 +679,6 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
727
679
  getEditedValue,
728
680
  hasEdit,
729
681
  clearEdits,
730
- applyEdits,
731
682
  discardEdits,
732
683
  // Resource creation actions
733
684
  startNewResource,
@@ -749,7 +700,6 @@ function useResolutionState(processedResources, onMessage, onSystemUpdate) {
749
700
  getEditedValue,
750
701
  hasEdit,
751
702
  clearEdits,
752
- applyEdits,
753
703
  discardEdits,
754
704
  startNewResource,
755
705
  updateNewResourceId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fgv/ts-res-ui-components",
3
- "version": "5.0.0-20",
3
+ "version": "5.0.0-21",
4
4
  "description": "Reusable React components for ts-res resource visualization and management",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -23,11 +23,11 @@
23
23
  "@heroicons/react": "~2.2.0",
24
24
  "tslib": "^2.8.1",
25
25
  "json-edit-react": "~1.28.2",
26
- "@fgv/ts-res": "5.0.0-20",
27
- "@fgv/ts-utils": "5.0.0-20",
28
- "@fgv/ts-json": "5.0.0-20",
29
- "@fgv/ts-json-base": "5.0.0-20",
30
- "@fgv/ts-extras": "5.0.0-20"
26
+ "@fgv/ts-res": "5.0.0-21",
27
+ "@fgv/ts-utils": "5.0.0-21",
28
+ "@fgv/ts-json": "5.0.0-21",
29
+ "@fgv/ts-extras": "5.0.0-21",
30
+ "@fgv/ts-json-base": "5.0.0-21"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "react": ">=18.0.0",
@@ -62,7 +62,7 @@
62
62
  "@microsoft/api-documenter": "^7.26.31",
63
63
  "@microsoft/api-extractor": "^7.52.10",
64
64
  "@testing-library/dom": "^10.4.0",
65
- "@fgv/ts-utils-jest": "5.0.0-20"
65
+ "@fgv/ts-utils-jest": "5.0.0-21"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "heft build --clean",
@@ -411,7 +411,7 @@ export const ResourceOrchestrator: React.FC<ResourceOrchestratorProps> = ({
411
411
  getEditedValue: resolutionData.actions.getEditedValue,
412
412
  hasResourceEdit: resolutionData.actions.hasEdit,
413
413
  clearResourceEdits: resolutionData.actions.clearEdits,
414
- applyResourceEdits: resolutionData.actions.applyEdits,
414
+ // Edits applied through unified applyPendingResources
415
415
  discardResourceEdits: resolutionData.actions.discardEdits,
416
416
 
417
417
  // Resource creation actions
@@ -180,9 +180,10 @@ export interface EditableJsonViewProps {
180
180
  * <ResolutionTools.UnifiedChangeControls
181
181
  * editCount={state.editedResources.size}
182
182
  * isApplying={state.isApplyingEdits}
183
- * hasEdits={state.hasUnsavedEdits}
184
- * onApplyEdits={actions.applyEdits}
185
- * onDiscardEdits={actions.clearEdits}
183
+ * addCount={state.pendingResources.size}
184
+ * deleteCount={state.pendingResourceDeletions.size}
185
+ * onApplyAll={actions.applyPendingResources}
186
+ * onDiscardAll={() => { actions.clearEdits(); actions.discardPendingResources(); }}
186
187
  * />
187
188
  * </div>
188
189
  * )}
@@ -568,80 +568,7 @@ export function useResolutionState(
568
568
  }
569
569
  }, [hasUnsavedEdits, onMessage]);
570
570
 
571
- const applyEdits = useCallback(async () => {
572
- if (!processedResources || editedResources.size === 0) {
573
- onMessage?.('warning', 'No edits to apply');
574
- return;
575
- }
576
-
577
- if (!onSystemUpdate) {
578
- onMessage?.('error', 'System update callback not provided');
579
- return;
580
- }
581
-
582
- setIsApplyingEdits(true);
583
-
584
- try {
585
- // Extract current resolution context (filter out undefined values)
586
- const cleanedContextValues: Record<string, string> = {};
587
- Object.entries(effectiveContext).forEach(([key, value]) => {
588
- if (value !== undefined) {
589
- cleanedContextValues[key] = value;
590
- }
591
- });
592
-
593
- const currentContext = extractResolutionContext(
594
- currentResolver as Runtime.ResourceResolver,
595
- cleanedContextValues
596
- );
597
-
598
- // Check for potential conflicts
599
- const conflictCheck = checkEditConflicts(
600
- processedResources.system.resourceManager,
601
- editedResourcesInternal,
602
- currentContext
603
- );
604
-
605
- // Show warnings about potential conflicts
606
- conflictCheck.warnings.forEach((warning) => onMessage?.('warning', warning));
607
-
608
- if (conflictCheck.conflicts.length > 0) {
609
- onMessage?.('error', `Conflicts detected: ${conflictCheck.conflicts.join(', ')}`);
610
- return;
611
- }
612
-
613
- // Rebuild the system with edits
614
- const rebuildResult = await rebuildSystemWithEdits(
615
- processedResources.system,
616
- editedResourcesInternal,
617
- currentContext
618
- );
619
-
620
- if (rebuildResult.isFailure()) {
621
- onMessage?.('error', `Failed to apply edits: ${rebuildResult.message}`);
622
- return;
623
- }
624
-
625
- // Update the system through the callback
626
- onSystemUpdate(rebuildResult.value);
627
-
628
- // Clear edits after successful application
629
- setEditedResourcesInternal(new Map());
630
-
631
- onMessage?.('success', `Successfully applied ${editedResourcesInternal.size} edit(s)`);
632
- } catch (error) {
633
- onMessage?.('error', `Error applying edits: ${error instanceof Error ? error.message : String(error)}`);
634
- } finally {
635
- setIsApplyingEdits(false);
636
- }
637
- }, [
638
- processedResources,
639
- editedResourcesInternal,
640
- onSystemUpdate,
641
- currentResolver,
642
- onMessage,
643
- effectiveContext
644
- ]);
571
+ // Removed applyEdits in favor of unified applyPendingResources
645
572
 
646
573
  // Resource creation actions
647
574
  const startNewResource = useCallback(
@@ -792,9 +719,11 @@ export function useResolutionState(
792
719
  );
793
720
 
794
721
  const applyPendingResources = useCallback(async () => {
795
- if (!hasPendingResourceChanges || !processedResources || !onSystemUpdate) {
796
- if (!hasPendingResourceChanges) {
797
- onMessage?.('warning', 'No pending resource changes to apply');
722
+ const hasAnyChanges =
723
+ editedResourcesInternal.size > 0 || pendingResources.size > 0 || pendingResourceDeletions.size > 0;
724
+ if (!hasAnyChanges || !processedResources || !onSystemUpdate) {
725
+ if (!hasAnyChanges) {
726
+ onMessage?.('warning', 'No pending changes to apply');
798
727
  } else if (!processedResources) {
799
728
  onMessage?.('error', 'No resource system available');
800
729
  } else if (!onSystemUpdate) {
@@ -804,6 +733,7 @@ export function useResolutionState(
804
733
  }
805
734
 
806
735
  try {
736
+ setIsApplyingEdits(true);
807
737
  // Extract current resolution context (filter out undefined values)
808
738
  const cleanedContextValues: Record<string, string> = {};
809
739
  Object.entries(effectiveContext).forEach(([key, value]) => {
@@ -851,7 +781,7 @@ export function useResolutionState(
851
781
 
852
782
  onMessage?.(
853
783
  'success',
854
- `Applied ${newResourcesArray.length} additions and ${pendingResourceDeletions.size} deletions`
784
+ `Applied ${editedResourcesInternal.size} edits, ${newResourcesArray.length} additions, and ${pendingResourceDeletions.size} deletions`
855
785
  );
856
786
 
857
787
  // Clear pending additions after successful application (deletions still deferred)
@@ -866,9 +796,10 @@ export function useResolutionState(
866
796
  'error',
867
797
  `Failed to apply pending resources: ${error instanceof Error ? error.message : String(error)}`
868
798
  );
799
+ } finally {
800
+ setIsApplyingEdits(false);
869
801
  }
870
802
  }, [
871
- hasPendingResourceChanges,
872
803
  pendingResources,
873
804
  pendingResourceDeletions,
874
805
  processedResources,
@@ -919,7 +850,6 @@ export function useResolutionState(
919
850
  getEditedValue,
920
851
  hasEdit,
921
852
  clearEdits,
922
- applyEdits,
923
853
  discardEdits,
924
854
  // Resource creation actions
925
855
  startNewResource,
@@ -942,7 +872,6 @@ export function useResolutionState(
942
872
  getEditedValue,
943
873
  hasEdit,
944
874
  clearEdits,
945
- applyEdits,
946
875
  discardEdits,
947
876
  startNewResource,
948
877
  updateNewResourceId,
@@ -475,8 +475,6 @@ export interface ResolutionActions {
475
475
  hasEdit: (resourceId: string) => boolean;
476
476
  /** Clear all pending edits */
477
477
  clearEdits: () => void;
478
- /** Apply all edits to the resource system */
479
- applyEdits: () => Promise<void>;
480
478
  /** Discard all pending edits */
481
479
  discardEdits: () => void;
482
480
  /** Start creating a new resource */
@@ -1173,8 +1171,8 @@ export interface OrchestratorState {
1173
1171
  * const newValue = { text: 'Updated welcome message' };
1174
1172
  * actions.saveResourceEdit('user.welcome', newValue);
1175
1173
  *
1176
- * // Apply all edits
1177
- * await actions.applyResourceEdits();
1174
+ * // Apply all pending changes (edits + new resources)
1175
+ * await actions.applyPendingResources();
1178
1176
  * ```
1179
1177
  *
1180
1178
  * @example
@@ -1245,7 +1243,7 @@ export interface OrchestratorActions {
1245
1243
  getEditedValue: (resourceId: string) => JsonValue | undefined;
1246
1244
  hasResourceEdit: (resourceId: string) => boolean;
1247
1245
  clearResourceEdits: () => void;
1248
- applyResourceEdits: () => Promise<void>;
1246
+ // Removed: unified apply via applyPendingResources
1249
1247
  discardResourceEdits: () => void;
1250
1248
 
1251
1249
  // Resource creation actions