Skip to content

Commit 3f27f57

Browse files
authored
refactor: encapsulate execution error store writes behind record actions (#13636)
## Summary Encapsulate `executionErrorStore`'s raw error state behind record actions and deduplicate the slot-matching/prompt-error-shape logic that callers had copy-pasted around it. ## Changes - **What**: - `executionErrorStore`: new `recordNodeErrors` / `recordExecutionError` / `recordPromptError` actions; `lastNodeErrors` / `lastExecutionError` / `lastPromptError` are now exposed as read-only computeds, so external writes are a compile-time error. The empty-record → `null` normalization lives in exactly one place, which also lets `hasNodeError` and `clearExecutionStartErrors` drop their now-dead emptiness checks (invariant: the record is always `null` or non-empty). - Direct-assignment call sites migrated: `app.ts` (queuePrompt success + `PromptExecutionError` paths), `executionStore.ts` (execution/service-level/cloud-validation errors), `subgraphStore.ts` (blueprint validation — key shape and empty-`errors` semantics intentionally unchanged). - `executionErrorUtil.ts`: extracted `normalizePromptError` (shared by the HTTP catch path and `classifyCloudValidationError`, which previously duplicated the object/string branching) and added `errorsForSlot` / `hasErrorForSlot`, replacing six inline copies of the `extra_info.input_name` matching across the store, `useNodeErrorFlagSync`, and `useProcessedWidgets`. - `app.queuePrompt()`'s public boolean result is preserved byte-for-byte: a local `queueResultOverride` keeps the pre-normalization semantics for the `PromptExecutionError` path (absent/`null` `node_errors` → `true`, `{}`/populated → `false`, last processed queue item wins across multi-item runs). Regression tests pin all three branches, including a multi-item concurrency case. - Tests: 12 suites that seeded error state by direct assignment now seed through the record actions. `TabErrors.test.ts` moved off `createTestingPinia({ initialState })` — `initialState` cannot seed computed-exposed state and was silently no-oping — to `stubActions: false` with action-based seeding, which also surfaced and fixed schema-incomplete fixtures (`executed`, missing error `type`/`details`) and a missing `refreshMissingModels` on the app mock. - **Breaking**: none. `app.lastNodeErrors` (deprecated getter) and `workspaceStore.lastNodeErrors` are read-only surfaces and keep working; after a validation failure with an empty record they now read `null` instead of `{}`, matching the advertised `Record | null` type and the behavior the success path already had. ## Review Focus - Behavior equivalence of the `queuePrompt` return contract: `queueResultOverride` is set only in the `PromptExecutionError` catch (`!nodeErrors`, computed before normalization) and reset on each successful iteration; every other exit falls through to the store-derived value as before. The `node_errors` field is typed optional but arrives from an unvalidated `JSON.parse` on the error path, so `null` is runtime-reachable — hence `!nodeErrors` rather than an `undefined` check, with a `null`-seeding regression test. - The store's null-or-non-empty invariant: `recordNodeErrors` and the internal clear path are the only writers; both normalize.
1 parent 1c396f6 commit 3f27f57

20 files changed

Lines changed: 608 additions & 454 deletions

src/components/error/ErrorOverlay.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ describe('ErrorOverlay', () => {
152152
renderOverlay()
153153

154154
const executionErrorStore = useExecutionErrorStore()
155-
executionErrorStore.lastNodeErrors = {
155+
executionErrorStore.recordNodeErrors({
156156
'1': makeNodeError(['Only error'])
157-
}
157+
})
158158
executionErrorStore.showErrorOverlay()
159159
await nextTick()
160160

@@ -189,9 +189,9 @@ describe('ErrorOverlay', () => {
189189
renderOverlay({ appMode: true })
190190

191191
const executionErrorStore = useExecutionErrorStore()
192-
executionErrorStore.lastNodeErrors = {
192+
executionErrorStore.recordNodeErrors({
193193
'1': makeNodeError(['Only error'])
194-
}
194+
})
195195
executionErrorStore.showErrorOverlay()
196196
await nextTick()
197197

src/components/error/useErrorOverlayState.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,9 @@ describe('useErrorOverlayState', () => {
131131
mountOverlayState()
132132

133133
const executionErrorStore = useExecutionErrorStore()
134-
executionErrorStore.lastNodeErrors = {
134+
executionErrorStore.recordNodeErrors({
135135
'1': makeNodeError(['Only error'])
136-
}
136+
})
137137
executionErrorStore.showErrorOverlay()
138138
await nextTick()
139139

@@ -168,9 +168,9 @@ describe('useErrorOverlayState', () => {
168168
mountOverlayState()
169169

170170
const executionErrorStore = useExecutionErrorStore()
171-
executionErrorStore.lastNodeErrors = {
171+
executionErrorStore.recordNodeErrors({
172172
'1': makeNodeError(['Required input is missing'])
173-
}
173+
})
174174
executionErrorStore.showErrorOverlay()
175175
await nextTick()
176176

@@ -207,9 +207,9 @@ describe('useErrorOverlayState', () => {
207207
mountOverlayState()
208208

209209
const executionErrorStore = useExecutionErrorStore()
210-
executionErrorStore.lastNodeErrors = {
210+
executionErrorStore.recordNodeErrors({
211211
'1': makeNodeError(['Raw validation error'])
212-
}
212+
})
213213
executionErrorStore.showErrorOverlay()
214214
await nextTick()
215215

@@ -248,7 +248,7 @@ describe('useErrorOverlayState', () => {
248248
mountOverlayState()
249249

250250
const executionErrorStore = useExecutionErrorStore()
251-
executionErrorStore.lastExecutionError = {
251+
executionErrorStore.recordExecutionError({
252252
prompt_id: 'prompt',
253253
node_id: 1,
254254
node_type: 'KSampler',
@@ -257,7 +257,7 @@ describe('useErrorOverlayState', () => {
257257
exception_type: 'torch.OutOfMemoryError',
258258
traceback: [],
259259
timestamp: Date.now()
260-
}
260+
})
261261
executionErrorStore.showErrorOverlay()
262262
await nextTick()
263263

@@ -474,9 +474,9 @@ describe('useErrorOverlayState', () => {
474474
mountOverlayState()
475475

476476
const executionErrorStore = useExecutionErrorStore()
477-
executionErrorStore.lastNodeErrors = {
477+
executionErrorStore.recordNodeErrors({
478478
'1': makeNodeError(['Only error'])
479-
}
479+
})
480480
executionErrorStore.showErrorOverlay()
481481
await nextTick()
482482

src/components/rightSidePanel/errors/ErrorGroupList.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,7 @@ const LOADER_NODE = { id: '2', title: 'LoaderNode' }
6363

6464
function seedTwoErrorGroups(pinia: TestingPinia) {
6565
const executionErrorStore = useExecutionErrorStore(pinia)
66-
executionErrorStore.lastNodeErrors = fromAny<
67-
typeof executionErrorStore.lastNodeErrors,
68-
unknown
69-
>({
66+
executionErrorStore.recordNodeErrors({
7067
'1': {
7168
class_type: 'KSampler',
7269
dependent_outputs: [],
@@ -83,7 +80,11 @@ function seedTwoErrorGroups(pinia: TestingPinia) {
8380
class_type: 'CLIPLoader',
8481
dependent_outputs: [],
8582
errors: [
86-
{ type: 'weird_error', message: 'Something odd happened', details: '' }
83+
{
84+
type: 'weird_error',
85+
message: 'Something odd happened',
86+
details: ''
87+
}
8788
]
8889
}
8990
})

0 commit comments

Comments
 (0)