poi-plugin-remodel-assistant 1.2.3 → 1.2.4

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 (67) hide show
  1. package/data/changelog.json +7 -0
  2. package/data/maintenance-history.json +12 -0
  3. package/i18n/en-US.json +38 -7
  4. package/i18n/ja-JP.json +38 -7
  5. package/i18n/zh-CN.json +38 -7
  6. package/i18n/zh-TW.json +38 -7
  7. package/index.js +1 -5
  8. package/package.json +1 -1
  9. package/views/app/item-info-area.js +13 -13
  10. package/views/app/plugin-bootstrap-area.js +266 -21
  11. package/views/new-interface/app/decision-snapshot-cache.js +35 -18
  12. package/views/new-interface/app/decision-snapshot-jobs.js +229 -53
  13. package/views/new-interface/app/decision-snapshot-request-gate.js +84 -1
  14. package/views/new-interface/app/decision-snapshot-service.js +2 -0
  15. package/views/new-interface/app/new-interface-area.js +340 -157
  16. package/views/new-interface/chain/domain/upgrade-chain-domain.js +6 -3
  17. package/views/new-interface/chain/full/upgrade-chain-canvas.js +4 -5
  18. package/views/new-interface/data/modals/data-transfer-modal.js +5 -6
  19. package/views/new-interface/data/modals/problem-diagnostics-modal.js +63 -8
  20. package/views/new-interface/decisions/domain/decision-domain.js +2 -2
  21. package/views/new-interface/decisions/domain/decision-snapshot-domain.js +51 -37
  22. package/views/new-interface/decisions/domain/stable-state-validation-domain.js +512 -0
  23. package/views/new-interface/equipment/components/equipment-detail.js +9 -10
  24. package/views/new-interface/equipment/components/equipment-list.js +1 -18
  25. package/views/new-interface/equipment/components/equipment-workspace.js +5 -6
  26. package/views/new-interface/equipment/domain/equipment-blacklist-domain.js +8 -6
  27. package/views/new-interface/incremental/plan-decision-index.js +56 -35
  28. package/views/new-interface/plan/components/current-start-candidate-selector.js +11 -7
  29. package/views/new-interface/plan/components/current-start-planner-modal.js +4 -3
  30. package/views/new-interface/plan/domain/consumable-plan-domain.js +19 -2
  31. package/views/new-interface/plan/domain/current-plan-execution-domain.js +451 -143
  32. package/views/new-interface/plan/domain/current-plan-identity-domain.js +59 -10
  33. package/views/new-interface/plan/domain/current-start-assignment-domain.js +548 -165
  34. package/views/new-interface/plan/domain/current-start-candidate-domain.js +337 -221
  35. package/views/new-interface/plan/domain/current-start-selection-domain.js +2 -22
  36. package/views/new-interface/plan/domain/final-plan-resolution-domain.js +205 -0
  37. package/views/new-interface/plan/domain/improvement-cost-domain.js +70 -43
  38. package/views/new-interface/plan/domain/improvement-plan-domain.js +87 -4
  39. package/views/new-interface/plan/domain/plan-facts-domain.js +129 -107
  40. package/views/new-interface/plan/domain/plan-identity-domain.js +55 -0
  41. package/views/new-interface/plan/domain/plan-route-decision-domain.js +29 -0
  42. package/views/new-interface/plan/domain/plan-route-domain.js +1 -5
  43. package/views/new-interface/shared/components/detail-panel.js +11 -22
  44. package/views/new-interface/shared/domain/material-inventory-domain.js +25 -1
  45. package/views/new-interface/workbench/components/material-detail-modal.js +1 -1
  46. package/views/new-interface/workbench/components/unresolved-current-starts-modal.js +1 -1
  47. package/views/new-interface/workbench/components/workbench-detail.js +12 -48
  48. package/views/new-interface/workbench/domain/material-drilldown-domain.js +4 -1
  49. package/views/new-interface/workbench/model/current-upgrade-cost-model.js +0 -8
  50. package/views/shared/__kcra-trace__.js +31 -0
  51. package/views/shared/data-transfer.js +58 -3
  52. package/views/shared/derived-cache-resource.js +49 -0
  53. package/views/shared/legacy-assignment-migration.js +57 -0
  54. package/views/shared/legacy-assignment-recovery.js +190 -0
  55. package/views/shared/personal-profile-source.js +39 -2
  56. package/views/shared/personal-profile-storage.js +7 -30
  57. package/views/shared/plan-storage.js +311 -208
  58. package/views/shared/plugin-maintenance.js +92 -0
  59. package/views/shared/plugin-migration-lock.js +98 -0
  60. package/views/shared/plugin-resource-paths.js +53 -0
  61. package/views/shared/plugin-state-commands.js +281 -0
  62. package/views/shared/plugin-state-migration.js +400 -0
  63. package/views/shared/plugin-state-schema.js +821 -0
  64. package/views/shared/plugin-state-storage.js +528 -0
  65. package/views/shared/problem-diagnostics.js +30 -40
  66. package/views/shared/runtime-data-package.js +16 -3
  67. package/views/shared/selectors.js +21 -6
@@ -5,14 +5,34 @@ exports.PluginBootstrapArea = void 0;
5
5
 
6
6
  var _react = _interopRequireWildcard(require("react"));
7
7
 
8
+ var _reactBootstrap = require("react-bootstrap");
9
+
8
10
  var _itemInfoArea = require("./item-info-area");
9
11
 
10
12
  var _dataPackageDomain = require("../shared/domain/data-package-domain");
11
13
 
12
14
  var _runtimeDataPackage = require("../shared/runtime-data-package");
13
15
 
16
+ var _pluginStateMigration = require("../shared/plugin-state-migration");
17
+
18
+ var _pluginStateStorage = require("../shared/plugin-state-storage");
19
+
20
+ var _pluginMaintenance = require("../shared/plugin-maintenance");
21
+
22
+ var _derivedCacheResource = require("../shared/derived-cache-resource");
23
+
24
+ var _pluginMigrationLock = require("../shared/plugin-migration-lock");
25
+
26
+ var _pluginMeta = require("../shared/plugin-meta");
27
+
14
28
  function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }
15
29
 
30
+ const {
31
+ __: translate
32
+ } = window.i18n[_pluginMeta.PLUGIN_ID];
33
+
34
+ const __ = (key, ...args) => translate(`bootstrap.${key}`, ...args);
35
+
16
36
  class PluginBootstrapArea extends _react.Component {
17
37
  constructor(...args) {
18
38
  var _temp;
@@ -22,47 +42,272 @@ class PluginBootstrapArea extends _react.Component {
22
42
  enumerable: true,
23
43
  writable: true,
24
44
  value: {
25
- runtimeDataRevision: (0, _dataPackageDomain.getDataPackageRevision)()
45
+ phase: 'starting',
46
+ ready: false,
47
+ closeConfirmStage: 0,
48
+ error: null,
49
+ runtimeDataRevision: 0,
50
+ pluginStateRevision: 0
51
+ }
52
+ }), Object.defineProperty(this, "showMigrationScreen", {
53
+ configurable: true,
54
+ enumerable: true,
55
+ writable: true,
56
+ value: () => new Promise(resolve => {
57
+ if (!this.migrationCloseGuard) {
58
+ this.migrationCloseGuard = (0, _pluginMigrationLock.installPluginMigrationCloseGuard)({
59
+ hostWindow: window,
60
+ onCloseAttempt: () => {
61
+ if (!this.mounted || this.state.phase !== 'migrating') return;
62
+ this.setState(previous => ({
63
+ closeConfirmStage: previous.closeConfirmStage > 0 ? previous.closeConfirmStage : 1
64
+ }));
65
+ }
66
+ });
67
+ }
68
+
69
+ this.setState({
70
+ phase: 'migrating',
71
+ closeConfirmStage: 0
72
+ }, () => {
73
+ const scheduleFrame = typeof window.requestAnimationFrame === 'function' ? window.requestAnimationFrame.bind(window) : callback => setTimeout(callback, 0);
74
+ scheduleFrame(() => setTimeout(resolve, 0));
75
+ });
76
+ })
77
+ }), Object.defineProperty(this, "finishMigrationScreen", {
78
+ configurable: true,
79
+ enumerable: true,
80
+ writable: true,
81
+ value: () => {
82
+ if (this.migrationCloseGuard) this.migrationCloseGuard.dispose();
83
+ this.migrationCloseGuard = null;
84
+ if (this.mounted) this.setState({
85
+ closeConfirmStage: 0
86
+ });
87
+ }
88
+ }), Object.defineProperty(this, "handleContinueMigration", {
89
+ configurable: true,
90
+ enumerable: true,
91
+ writable: true,
92
+ value: () => this.setState({
93
+ closeConfirmStage: 0
94
+ })
95
+ }), Object.defineProperty(this, "handleEscalateMigrationClose", {
96
+ configurable: true,
97
+ enumerable: true,
98
+ writable: true,
99
+ value: () => this.setState({
100
+ closeConfirmStage: 2
101
+ })
102
+ }), Object.defineProperty(this, "handleConfirmMigrationClose", {
103
+ configurable: true,
104
+ enumerable: true,
105
+ writable: true,
106
+ value: () => {
107
+ if (this.migrationCloseGuard) this.migrationCloseGuard.forceClose();
108
+ }
109
+ }), Object.defineProperty(this, "handleRecoverLegacyState", {
110
+ configurable: true,
111
+ enumerable: true,
112
+ writable: true,
113
+ value: async () => {
114
+ try {
115
+ await this.showMigrationScreen();
116
+ (0, _derivedCacheResource.releaseDerivedPluginRuntimeMemory)();
117
+ (0, _pluginStateMigration.recoverPluginStateFromLegacyConfig)(window.config);
118
+ if (!this.mounted) return;
119
+ this.setState({
120
+ ready: false,
121
+ error: null
122
+ }, () => {
123
+ this.bootstrap({
124
+ migrationScreenActive: true
125
+ });
126
+ });
127
+ } catch (error) {
128
+ this.finishMigrationScreen();
129
+ if (this.mounted) this.setState({
130
+ phase: 'error',
131
+ error
132
+ });
133
+ }
134
+ }
135
+ }), Object.defineProperty(this, "startRuntimeUpdater", {
136
+ configurable: true,
137
+ enumerable: true,
138
+ writable: true,
139
+ value: () => {
140
+ if (!this.mounted || this.runtimeDataUpdateTimer) return;
141
+ (0, _runtimeDataPackage.ensureRuntimeDataPackageUpdated)().catch(() => null);
142
+ this.runtimeDataUpdateTimer = setInterval(() => {
143
+ (0, _runtimeDataPackage.ensureRuntimeDataPackageUpdated)().catch(() => null);
144
+ }, _runtimeDataPackage.RUNTIME_DATA_CHECK_INTERVAL_MS);
145
+ }
146
+ }), Object.defineProperty(this, "bootstrap", {
147
+ configurable: true,
148
+ enumerable: true,
149
+ writable: true,
150
+ value: async ({
151
+ migrationScreenActive: existingMigrationScreen = false
152
+ } = {}) => {
153
+ let migrationScreenActive = Boolean(existingMigrationScreen);
154
+
155
+ try {
156
+ if (!migrationScreenActive && (0, _pluginStateMigration.isPluginStateMigrationRequired)()) {
157
+ migrationScreenActive = true;
158
+ await this.showMigrationScreen();
159
+ (0, _derivedCacheResource.releaseDerivedPluginRuntimeMemory)();
160
+ }
161
+
162
+ (0, _pluginStateMigration.bootstrapPluginState)(window.config);
163
+ await (0, _runtimeDataPackage.resolveRuntimeDataPackageSnapshot)();
164
+ const maintenance = await (0, _pluginMaintenance.runPendingPluginMaintenance)();
165
+ if (!this.mounted) return;
166
+ this.unsubscribeRuntimeData = (0, _dataPackageDomain.subscribeDataPackage)(snapshot => {
167
+ if (!this.mounted) return;
168
+ this.setState({
169
+ runtimeDataRevision: snapshot.revision
170
+ });
171
+ });
172
+ this.unsubscribePluginState = (0, _pluginStateStorage.subscribePluginState)(snapshot => {
173
+ if (!this.mounted) return;
174
+ this.setState({
175
+ pluginStateRevision: Number(snapshot.revision) || 0
176
+ });
177
+ });
178
+ const state = (0, _pluginStateStorage.getPluginStateSnapshot)();
179
+ if (migrationScreenActive) this.finishMigrationScreen();
180
+ this.setState({
181
+ phase: 'ready',
182
+ ready: true,
183
+ error: null,
184
+ runtimeDataRevision: (0, _dataPackageDomain.getDataPackageRevision)(),
185
+ pluginStateRevision: Number(state.revision) || 0
186
+ }, () => {
187
+ if (maintenance.finalized) this.startRuntimeUpdater();
188
+ });
189
+ } catch (error) {
190
+ if (migrationScreenActive) this.finishMigrationScreen();
191
+ if (!this.mounted) return;
192
+ this.setState({
193
+ phase: 'error',
194
+ error
195
+ });
196
+ }
26
197
  }
27
198
  }), _temp;
28
199
  }
29
200
 
30
201
  componentDidMount() {
31
202
  this.mounted = true;
32
- this.unsubscribeRuntimeData = (0, _dataPackageDomain.subscribeDataPackage)(snapshot => {
33
- if (!this.mounted) return;
34
- this.setState({
35
- runtimeDataRevision: snapshot.revision
36
- });
37
- });
38
- const currentRevision = (0, _dataPackageDomain.getDataPackageRevision)();
39
-
40
- if (currentRevision !== this.state.runtimeDataRevision) {
41
- this.setState({
42
- runtimeDataRevision: currentRevision
43
- });
44
- }
45
-
46
- (0, _runtimeDataPackage.ensureRuntimeDataPackageUpdated)().catch(() => null);
47
- this.runtimeDataUpdateTimer = setInterval(() => {
48
- (0, _runtimeDataPackage.ensureRuntimeDataPackageUpdated)().catch(() => null);
49
- }, _runtimeDataPackage.RUNTIME_DATA_CHECK_INTERVAL_MS);
203
+ this.bootstrap();
50
204
  }
51
205
 
52
206
  componentWillUnmount() {
53
207
  this.mounted = false;
54
208
  if (this.unsubscribeRuntimeData) this.unsubscribeRuntimeData();
55
209
  this.unsubscribeRuntimeData = null;
210
+ if (this.unsubscribePluginState) this.unsubscribePluginState();
211
+ this.unsubscribePluginState = null;
56
212
  if (this.runtimeDataUpdateTimer) clearInterval(this.runtimeDataUpdateTimer);
57
213
  this.runtimeDataUpdateTimer = null;
214
+ if (this.migrationCloseGuard) this.migrationCloseGuard.dispose();
215
+ this.migrationCloseGuard = null;
58
216
  }
59
217
 
60
218
  render() {
61
219
  const {
62
- runtimeDataRevision
220
+ phase,
221
+ ready,
222
+ error,
223
+ runtimeDataRevision,
224
+ pluginStateRevision,
225
+ closeConfirmStage
63
226
  } = this.state;
227
+
228
+ if (phase === 'migrating') {
229
+ return _react.default.createElement("div", {
230
+ className: "plugin-data-migration-screen",
231
+ style: {
232
+ minHeight: 320,
233
+ display: 'flex',
234
+ alignItems: 'center',
235
+ justifyContent: 'center',
236
+ padding: 32,
237
+ textAlign: 'center'
238
+ }
239
+ }, _react.default.createElement("div", {
240
+ style: {
241
+ width: 'min(720px, 92vw)'
242
+ }
243
+ }, _react.default.createElement("h1", {
244
+ style: {
245
+ fontSize: 30,
246
+ marginBottom: 18
247
+ }
248
+ }, __('Updating plugin data')), _react.default.createElement("p", {
249
+ style: {
250
+ fontSize: 18,
251
+ lineHeight: 1.7
252
+ }
253
+ }, __('Plans and equipment assignments are being migrated and verified.')), _react.default.createElement("p", {
254
+ style: {
255
+ fontSize: 18,
256
+ fontWeight: 600,
257
+ lineHeight: 1.7
258
+ }
259
+ }, __('Please do not close Poi, disable, or restart the plugin until this finishes.')), _react.default.createElement("p", null, __('The plugin will continue automatically after the new data is reloaded successfully.')), closeConfirmStage === 1 && _react.default.createElement("div", {
260
+ style: {
261
+ marginTop: 28
262
+ }
263
+ }, _react.default.createElement("p", null, __('Data update is still running. Continue waiting is recommended.')), _react.default.createElement("div", {
264
+ style: {
265
+ display: 'flex',
266
+ justifyContent: 'space-between',
267
+ gap: 24
268
+ }
269
+ }, _react.default.createElement(_reactBootstrap.Button, {
270
+ bsStyle: "danger",
271
+ onClick: this.handleEscalateMigrationClose
272
+ }, __('Close anyway')), _react.default.createElement(_reactBootstrap.Button, {
273
+ bsStyle: "primary",
274
+ autoFocus: true,
275
+ onClick: this.handleContinueMigration
276
+ }, __('Continue waiting')))), closeConfirmStage === 2 && _react.default.createElement("div", {
277
+ style: {
278
+ marginTop: 28
279
+ }
280
+ }, _react.default.createElement("p", null, __('Confirm interrupting the data update?')), _react.default.createElement("div", {
281
+ style: {
282
+ display: 'flex',
283
+ justifyContent: 'space-between',
284
+ gap: 24
285
+ }
286
+ }, _react.default.createElement(_reactBootstrap.Button, {
287
+ bsStyle: "primary",
288
+ autoFocus: true,
289
+ onClick: this.handleContinueMigration
290
+ }, __('Return and wait')), _react.default.createElement(_reactBootstrap.Button, {
291
+ bsStyle: "danger",
292
+ onClick: this.handleConfirmMigrationClose
293
+ }, __('Confirm close Poi'))))));
294
+ }
295
+
296
+ if (error) {
297
+ const recoveryRequired = String(error && error.message || '') === 'plugin-state-recovery-required';
298
+ return _react.default.createElement("div", {
299
+ className: "plugin-bootstrap-error"
300
+ }, _react.default.createElement("div", null, String(error && error.message || error)), recoveryRequired ? _react.default.createElement("div", {
301
+ className: "plugin-bootstrap-recovery"
302
+ }, _react.default.createElement("p", null, __('Plugin State is damaged. Automatic legacy fallback is disabled to protect newer plans.')), _react.default.createElement("p", null, __('Restore migration-time configuration only if you accept losing changes made after migration.')), _react.default.createElement(_reactBootstrap.Button, {
303
+ onClick: this.handleRecoverLegacyState
304
+ }, __('Restore migration-time configuration'))) : null);
305
+ }
306
+
307
+ if (!ready) return null;
64
308
  return _react.default.createElement(_itemInfoArea.ItemInfoArea, {
65
- runtimeDataRevision: runtimeDataRevision
309
+ runtimeDataRevision: runtimeDataRevision,
310
+ pluginStateRevision: pluginStateRevision
66
311
  });
67
312
  }
68
313
 
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.SNAPSHOT_INPUT_KEYS = exports.FOUNDATION_INPUT_KEYS = exports.createDecisionFoundationCache = exports.readDecisionFoundationCache = exports.clearProcessDecisionSnapshotCache = exports.writeProcessDecisionSnapshotCache = exports.readProcessDecisionSnapshotCache = exports.createDecisionSnapshotCache = exports.readDecisionSnapshotCache = exports.sameDecisionSnapshotInputs = void 0;
4
+ exports.SNAPSHOT_INPUT_KEYS = exports.SNAPSHOT_FEEDBACK_CONTEXT_KEYS = exports.clearProcessDecisionSnapshotCache = exports.writeProcessDecisionSnapshotCache = exports.readProcessDecisionSnapshotCache = exports.createDecisionSnapshotCache = exports.readDecisionSnapshotCache = exports.sameDecisionSnapshotFeedbackContext = exports.sameDecisionSnapshotInputs = void 0;
5
5
 
6
6
  var _pluginMeta = require("../../shared/plugin-meta");
7
7
 
@@ -9,12 +9,12 @@ var _equipmentBlacklistDomain = require("../equipment/domain/equipment-blacklist
9
9
 
10
10
  function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
11
11
 
12
- const SNAPSHOT_INPUT_KEYS = ['dataRevision', 'dataIdentity', 'plans', 'initialDay', 'masterEquips', 'actualEquipLevels', 'actualEquipInventory', 'actualOwnedShipMasterIds', 'decisionSelections', 'planRouteSelections', 'currentInventoryBindings', 'equipmentBlacklist', 'routeAcquisitionIndex'];
12
+ const SNAPSHOT_INPUT_KEYS = ['dataRevision', 'dataIdentity', 'plans', 'initialDay', 'masterEquips', 'masterUseitems', 'equipTypes', 'staticItems', 'actualEquipLevels', 'actualEquipInventory', 'actualUseitems', 'actualResourceBudget', 'equipmentRealityReady', 'actualOwnedShipMasterIds', 'decisionSelections', 'planRouteSelections', 'currentInventoryBindings', 'equipmentBlacklist', 'routeAcquisitionIndex'];
13
13
  exports.SNAPSHOT_INPUT_KEYS = SNAPSHOT_INPUT_KEYS;
14
- const FOUNDATION_INPUT_KEYS = ['dataRevision', 'masterEquips'];
15
- exports.FOUNDATION_INPUT_KEYS = FOUNDATION_INPUT_KEYS;
14
+ const SNAPSHOT_FEEDBACK_CONTEXT_KEYS = SNAPSHOT_INPUT_KEYS.filter(key => key !== 'currentInventoryBindings');
15
+ exports.SNAPSHOT_FEEDBACK_CONTEXT_KEYS = SNAPSHOT_FEEDBACK_CONTEXT_KEYS;
16
16
  const HOST_SESSION_SNAPSHOT_CACHE_KEY = Symbol.for('poi-plugin-remodel-assistant.decision-snapshot-session-cache');
17
- const HOST_SESSION_SNAPSHOT_CACHE_SCHEMA = 1;
17
+ const HOST_SESSION_SNAPSHOT_CACHE_SCHEMA = 6;
18
18
  let processDecisionSnapshotCache = null;
19
19
 
20
20
  const hostSession = () => typeof globalThis === 'undefined' ? null : globalThis;
@@ -56,9 +56,14 @@ const stableValue = value => {
56
56
  return result;
57
57
  };
58
58
 
59
- const stableSignature = value => JSON.stringify(stableValue(value || {}));
59
+ const stableSignature = value => JSON.stringify(stableValue(value || {})); // `masterEquips` is intentionally excluded from semantic deep hashing. It is a
60
+ // large static host/runtime projection whose lifecycle is already represented by
61
+ // `dataRevision` / `dataIdentity`; hashing the whole master map on every cache hit
62
+ // turns a page switch into O(master-data-size) work. Reference comparison is the
63
+ // cheap secondary guard for unexpected replacement under the same data identity.
60
64
 
61
- const CONTENT_IDENTITY_KEYS = new Set(['plans', 'actualEquipLevels', 'actualEquipInventory', 'actualOwnedShipMasterIds', 'decisionSelections', 'planRouteSelections', 'currentInventoryBindings', 'routeAcquisitionIndex']);
65
+
66
+ const CONTENT_IDENTITY_KEYS = new Set(['plans', 'actualEquipLevels', 'actualEquipInventory', 'actualUseitems', 'actualResourceBudget', 'actualOwnedShipMasterIds', 'decisionSelections', 'planRouteSelections', 'currentInventoryBindings', 'routeAcquisitionIndex']);
62
67
 
63
68
  const inputIdentity = (key, value) => {
64
69
  if (key === 'equipmentBlacklist') return (0, _equipmentBlacklistDomain.equipmentBlacklistSignature)(value);
@@ -66,7 +71,19 @@ const inputIdentity = (key, value) => {
66
71
  return value;
67
72
  };
68
73
 
69
- const inputIdentities = (inputs, keys) => Object.fromEntries(keys.map(key => [key, inputIdentity(key, inputs[key])]));
74
+ const inputIdentities = (inputs, keys) => Object.fromEntries(keys.map(key => [key, inputIdentity(key, inputs[key])])); // The feedback fuse is evaluated only when a Snapshot proposes a binding
75
+ // transition, so it can afford semantic comparison for the large master map.
76
+ // Do not reuse the hot-path cache identity here: equal master data reconstructed
77
+ // into a fresh object is still the same Decision context and must not reset the
78
+ // assignment-transition history.
79
+
80
+
81
+ const feedbackContextIdentity = (key, value) => key === 'masterEquips' ? stableSignature(value) : inputIdentity(key, value);
82
+
83
+ const sameFeedbackContextInputs = (left, right, keys) => {
84
+ if (!left || !right) return false;
85
+ return keys.every(key => feedbackContextIdentity(key, left[key]) === feedbackContextIdentity(key, right[key]));
86
+ };
70
87
 
71
88
  const sameInputs = (left, right, keys) => {
72
89
  if (!left || !right) return false;
@@ -84,10 +101,18 @@ const createCache = (inputs, value, keys = SNAPSHOT_INPUT_KEYS) => _extends({},
84
101
  value
85
102
  });
86
103
 
87
- const sameDecisionSnapshotInputs = (left, right) => sameInputs(left, right, SNAPSHOT_INPUT_KEYS);
104
+ const sameDecisionSnapshotInputs = (left, right) => sameInputs(left, right, SNAPSHOT_INPUT_KEYS); // Binding State is both a Decision input and an explicit publish result. The
105
+ // Allocation algorithm never feeds that result back into the same round; this
106
+ // comparison exists only so the safety fuse can distinguish a legitimate later
107
+ // Snapshot from an accidental binding-only renderer loop.
108
+
88
109
 
89
110
  exports.sameDecisionSnapshotInputs = sameDecisionSnapshotInputs;
90
111
 
112
+ const sameDecisionSnapshotFeedbackContext = (left, right) => sameFeedbackContextInputs(left, right, SNAPSHOT_FEEDBACK_CONTEXT_KEYS);
113
+
114
+ exports.sameDecisionSnapshotFeedbackContext = sameDecisionSnapshotFeedbackContext;
115
+
91
116
  const readDecisionSnapshotCache = (cache, inputs) => _pluginMeta.FORCE_FULL_DECISION_REBUILD ? null : readCache(cache, inputs, SNAPSHOT_INPUT_KEYS);
92
117
 
93
118
  exports.readDecisionSnapshotCache = readDecisionSnapshotCache;
@@ -125,12 +150,4 @@ const clearProcessDecisionSnapshotCache = () => {
125
150
  clearHostSessionDecisionSnapshotCache();
126
151
  };
127
152
 
128
- exports.clearProcessDecisionSnapshotCache = clearProcessDecisionSnapshotCache;
129
-
130
- const readDecisionFoundationCache = (cache, inputs) => _pluginMeta.FORCE_FULL_DECISION_REBUILD ? null : readCache(cache, inputs, FOUNDATION_INPUT_KEYS);
131
-
132
- exports.readDecisionFoundationCache = readDecisionFoundationCache;
133
-
134
- const createDecisionFoundationCache = (inputs, value) => createCache(inputs, value, FOUNDATION_INPUT_KEYS);
135
-
136
- exports.createDecisionFoundationCache = createDecisionFoundationCache;
153
+ exports.clearProcessDecisionSnapshotCache = clearProcessDecisionSnapshotCache;