Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/run-rerenders-failed-production.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@milaboratories/pl-middle-layer": patch
---

Make Run re-render a block whose production failed outright.

The Run button is enabled whenever a block's production carries an error, but the mutator recognised only one of the two shapes such a failure takes. `productionHasErrors` read the field's `status`, which is derived from the resource the field points at — so it saw a value resource that exists and carries an error, and missed a field whose own error slot is filled and that therefore has no value resource at all. In that second shape `renderProduction` found nothing to re-render for the block and committed an empty transaction: the button was live, the click was accepted, and the block never re-ran.

Field-level errors are now carried through `ProjectMutator.load` alongside the value reference and counted by `productionHasErrors`, so both shapes reach `requireProductionRendering` and the enable condition the desktop uses for the button once again matches the condition the mutator renders on.
32 changes: 27 additions & 5 deletions lib/node/pl-middle-layer/src/mutator/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ interface BlockFieldState {
ref?: AnyRef;
status?: FieldStatus;
value?: Uint8Array;
/**
* True when the project field's own error slot is filled, i.e. the render failed outright
* and left no value resource behind. Distinct from `status === "Error"`, which describes a
* value resource that exists but carries an error.
*/
fieldError?: boolean;
}

/** Either failure shape a production field can be in: no value resource, or an errored one. */
function hasError(state: BlockFieldState | undefined): boolean {
return state?.fieldError === true || state?.status === "Error";
}

type BlockFieldStates = Partial<Record<ProjectField["fieldName"], BlockFieldState>>;
Expand Down Expand Up @@ -258,11 +269,19 @@ class BlockInfo {
return this.fields.prodCtx !== undefined;
}

/**
* True if the rendered production failed, in either shape {@link hasError} covers.
*
* Must stay in step with the `outputError` the desktop derives in
* `middle_layer/project_overview.ts`, because that flag is what enables the Run button.
* Any failure counted there but not here leaves Run enabled yet inert: `renderProduction`
* finds nothing to re-render and commits an empty transaction.
*/
get productionHasErrors(): boolean {
return (
this.fields.prodUiCtx?.status === "Error" ||
this.fields.prodOutput?.status === "Error" ||
this.fields.prodCtx?.status === "Error"
hasError(this.fields.prodUiCtx) ||
hasError(this.fields.prodOutput) ||
hasError(this.fields.prodCtx)
);
}

Expand Down Expand Up @@ -1809,9 +1828,12 @@ export class ProjectMutator {
blockInfoStates.set(projectField.blockId, info);
}

// `f.error` is carried separately from `f.value`: a field that errored has no value
// resource to read a status off, so this is the only trace the failure leaves here.
const fieldError = isNotNullSignedResourceId(f.error);
info.fields[projectField.fieldName] = isNullSignedResourceId(f.value)
? { modCount: 0 }
: { modCount: 0, ref: f.value };
? { modCount: 0, fieldError }
: { modCount: 0, ref: f.value, fieldError };
}

//
Expand Down
Loading