Skip to content

web: render the drain's over-budget hold on the upload row #1075

Description

@FSM1

SnapshotDescriptor.blocked carries the drain's over-budget hold — the op it is holding and the bytes it needs — and blueprint/engine.md makes it a snapshot field rather than an event precisely because it is a state that clears, so a lost "resumed" would strand the UI on a blockage that is gone.

What already landed

The command-refusal half of this is delivered (#1071). When the command is refused over budget, crates/wasm/src/host.rs maps EngineError::OverBudget to the stable code overBudget, apps/web/src/hooks/useDropUpload.ts:237 captures it onto the upload row, and apps/web/src/components/file-browser/UploadListItem.tsx:32 renders it as transient rather than as terminal failure.

blocked itself now has one reader: apps/web/src/engine/introspection.ts:84 folds view.blocked === null into the e2e settled predicate. That is a test seam, not a rendered surface.

What remains

The snapshot-sourced hold. The error code only fires when the command is refused at the boundary; it says nothing about an op the drain is already holding. A queued op the drain holds emits no opProgress, so it sits at queued forever with no explanation and no action — which is the case blocked exists to describe.

Scope

  • Read blocked (and retainedRecords, which explains staged bytes the session cannot account for) from the snapshot the store already holds — packages/client/src/worker/protocol.ts:107,114, projected through apps/web/src/engine/snapshotStore.ts.
  • Match blocked.opId to the upload row and render the hold with neededBytes, distinct from queued, from the overBudget command refusal already rendered, and from a terminal failure.
  • Decide where the hold is owned: the upload rows are transient hook state today (useDropUpload), so this needs the snapshot store and the upload rows to meet somewhere. That choice is the substance of this issue — and UploadListItem already takes a code, so the join point is the row's props, not a second store.

Gate

web unit.


Corrected 2026-08-06 against main @ 3d087d673. Stale before this edit: "Nothing in apps/web reads it" (introspection.ts:84 reads view.blocked), and the implicit claim that no over-budget state reaches the upload row at all — the command-refusal path landed with #1071. Rescoped to the residual: the snapshot-sourced blocked.opId/neededBytes hold, which the error-code path does not cover.

Metadata

Metadata

Assignees

No one assigned

    Labels

    comp:webpackages/client + apps/web — WASM host, browser seams, React UIv2-buildv2 rewrite build slice

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions