Who decided on one unit when the quantity was cleared? In an existing product-management application, a small fallback concealed invalid input. I repaired the connection so that an unfinished edit could remain visible without becoming a ready review draft.

The first repair covered edits and review drafts. A follow-up found the same kind of guessing while normalizing orders received from an API. The added account below follows that earlier input boundary; it does not describe processing real commercial orders.

A default belongs to starting a new record

Showing one unit when a new order is created is a different decision from inserting one after someone clears an existing quantity. The first establishes a starting point. The second changes an edit. I began by separating those responsibilities in the existing order-entry path.

When I exercised the actual input-callback expression with a stub, both an empty string and zero were passed on as one. This was a synthetic event test of the connected input logic, not a live browser interaction or an operation on a recipient record. The program was deciding the meaning of an edit before the person had finished entering it.

I kept the explicit initial quantity of one for a fresh order. During editing, however, blank input, zero and fractional text now remain available for correction. A draft can preserve an invalid value without being eligible to proceed. Separating those promises made it possible to tighten validation without erasing the edit.

“Not below one” was an incomplete acceptance rule

I then examined values entering from outside the field itself. The existing check only asked whether the numeric conversion was below one. With the other fields filled by valid synthetic values, missing quantities, nonnumeric text, non-finite values and fractions exposed cases that passed the quantity check and produced a ready review draft.

The draft builder concealed another part of the problem. Some missing or nonnumeric values were guessed as one, while infinity and fractional values could remain in drafts marked ready. What looked like a convenient fallback actually combined two defects: inventing an unconfirmed quantity and accepting a quantity outside the intended integer contract.

Validation needed to explain what was wrong, and the draft builder needed to preserve that decision. Instead of inspecting one changed conditional in isolation, I sent the same counterexamples through the input, validator and draft path. A passing result in one layer was not evidence that the next layer received an acceptable value.

One function defines what this quantity means

The shared interpreter accepts a number or a nonempty numeric string. It converts the value and returns it only when the result is a positive safe integer; otherwise it returns null. Checking the input type first prevents booleans and arrays from being interpreted as quantities. Blank strings are rejected before conversion as well.

This is not a strict decimal-digit spelling rule. In the actual regression cases, the text 2.0 becomes the integer 2, and 2e2 becomes 200. The value 1.5 is rejected because it is not an integer. I documented supported examples so that a rule about the interpreted value would not be mistaken for a narrower rule about the characters someone may type.

A safe integer is not evidence of available stock or an appropriate purchasing limit. This repair did not add product-specific minimums, packaging multiples or maximum order sizes. A business that counts length or weight in fractional units needs a different contract. Reusing the idea starts with agreeing on units and permitted values, rather than copying a function name.

The review draft keeps an unknown quantity unknown

The validator and draft builder call the same interpreter. An invalid quantity produces quantity:null, validation.ready:false and a quantity-check issue in the review draft. Valid quantities become numbers. The source order object is left unchanged, preserving the material the operator needs to make a correction.

Null means neither zero units nor one unit. It means that an acceptable quantity has not been established. If another consumer replaces that null with a fallback of one, the defect can reappear at the next boundary. I did not verify every downstream consumer in this task; an integration must check how it handles null and the accompanying issue before relying on the draft.

The existing application allows a draft with issues to be copied or downloaded for review, and I retained that behavior. Producing a document, making it reviewable and authorizing a real order are different states. This repair concerns client validation and review output. It did not implement server authorization, purchasing or payment controls.

I followed the counterexamples through the actual connection

The separate development task ran eight quantity tests. They covered accepted integers and numeric strings; missing, blank, zero, negative, fractional, nonnumeric and wrong-type inputs; and the null value and correction issue in the resulting draft. They also checked preservation of unrelated valid fields and the behavior of repeated calls with frozen input objects.

For the editing path, I extracted the actual callback expression from the source and connected it to a state-update stub. Blank, zero and fractional edits remained intact through the existing order-normalization function, then failed the validator and draft checks. A valid edit of the text 3 remained text in editing state and became the number 3 in the draft. That comparison made the boundary between editing and interpretation explicit.

These were not typing tests in a rendered browser or operations on a real clipboard. The first development task also passed nineteen CSV tests, an isolated build and twenty-six mock API checks, recorded as distinct scopes. The initial article reviewed that evidence; this API-input follow-up likewise reads a separate completed development run without rerunning the native product. Independent code-review judgments remain separate evidence from test execution.

The restore test exposed its runtime dependency

For the first quantity repair, I archived the before-and-after source with its test context, extracted both archives and compared hashes for seventeen files. The first restored test run nevertheless stopped because a required installed library was unavailable. Identical source bytes and an executable environment were separate checks.

I retained the first failure and both archives. After connecting only the restored folder to the existing installed packages, all eight restored quantity tests passed. I installed no new packages and changed no original dependency installation. The lockfile and installed-version information were preserved separately. This demonstrates source restoration using an existing runtime, not a verified first installation on a new computer.

The development task also preserved the bytes and modification times of 114 protected files, including operating data, the original build, server code and adapters. A source repair was not described as a replacement of the running deployment. A useful recovery record needs to say both what was recovered and which environment made the verification possible.

The quantity was changing before validation could see it

Preserving an empty field in the editor is insufficient if another entry path invents one unit before validation. Running the actual API-order normalizer with synthetic input showed a missing quantity becoming one. An explicit top-level zero accompanied by an item quantity of four became four. This was a received record, not a person starting a fresh local order.

An item quantity of two followed by a nonnumeric item left a partial total of two. Another counterexample exceeded the safe-integer limit even though each item was individually safe. Tightening the review draft alone cannot recover missing information that an earlier step has already converted into an apparently ordinary number. I therefore connected the existing quantity interpreter at the incoming-data boundary.

Separate an absent field from an invalid supplied value

If the received object owns a quantity field, that value is interpreted first. Zero, blank text, booleans and invalid numeric values become null rather than being replaced by an item total. A plausible number in another field should not conceal the fact that the supplied value was invalid. Testing own-field presence is different from testing whether its value is truthy.

Only when the quantity field itself is absent does the normalizer consider the item list as an alternative input. That precedence is the contract for this connection. It does not cross-check a valid top-level quantity against the item total. The existing numeric-string compatibility remains, so support for decimal or exponent spellings must not be confused with stricter contracts used by other data formats.

A partial sum is not the quantity of the whole order

When the item list is the input, it must be nonempty and every item quantity must be valid. If any quantity is unknown, the complete aggregate becomes null. Converting a bad item to zero would make the total of the remaining valid items look like the quantity of the entire order. Counting item issues and establishing an order total do not belong in the same unqualified reduce expression.

After interpreting each item, the helper checks the running sum for safe-integer representation. That is a calculation-boundary check, not a business purchasing limit or product-allocation policy. Mapping a multi-product order to products and allocating its quantities were not changed. The code below is the actual implementation of the two interpreters; it makes no external request and stores no order.

Explore the implementation

Actual pure quantity interpreters used by the API input connection

export function normalizeOrderQuantity(value) {
  if (typeof value !== "number" && typeof value !== "string") return null;
  if (typeof value === "string" && !value.trim()) return null;
  const quantity = Number(value);
  return Number.isSafeInteger(quantity) && quantity > 0 ? quantity : null;
}

// Imported orders never borrow the explicit default of a new local order.
export function normalizeApiOrderQuantity(order) {
  if (!order || typeof order !== "object" || Array.isArray(order)) return null;
  if (Object.hasOwn(order, "quantity")) return normalizeOrderQuantity(order.quantity);
  if (!Array.isArray(order.items) || !order.items.length) return null;
  let total = 0;
  for (const item of order.items) {
    const quantity = normalizeOrderQuantity(item?.quantity);
    if (quantity === null || !Number.isSafeInteger(total + quantity)) return null;
    total += quantity;
  }
  return total;
}

Keep local edits alongside an unknown server value

An unknown newly received quantity should not erase a local edit already under review. The existing merge policy gives edited fields precedence when localEdits is present. I preserved that policy and tested that the incoming quantity remains null in serverSnapshot.quantity while syncConflict is marked. A locally edited empty string does not become one or get replaced by the server item total.

An unedited record receives the normalized value, so null requires attention through the existing validator and review draft. A valid local edit and an unknown server value can also coexist. The conflict flag itself is not a server lock or a purchasing block. Choosing which material to preserve remains a separate responsibility from deciding who may proceed and on what evidence.

Follow the connection through normalization and merging

The follow-up added sixteen checks that use the shared helper and extract the actual application normalizer and merger, connecting their results into review-draft creation. They cover valid top-level precedence, totals of valid items, a bad item, empty lists, aggregate limits and local-edit preservation. They also verify frozen-input preservation and the explicit default of one for a fresh local order.

The follow-up total of forty-three client checks already includes the eight earlier quantity checks, nineteen CSV checks and sixteen new synchronization checks. The earlier eight are not added again. Twenty-six isolated mock API checks, a separate code review and an isolated build also passed, without establishing successful live provider synchronization or account and order operations. This content revision reads the source and results rather than rerunning the native product.

The archive also needs the source read by its tests

The follow-up restore rehearsal exposed a different omission. The first archive lacked two input-panel source files read directly by the existing tests. This was missing context in the recovery package, not a failing native-product behavior. I retained that archive and its missing-file logs, then continued with newly named before-and-after packages containing the referenced source.

The complete packages supplied twenty-seven extracted files for hash comparison; twenty-seven tests ran against the earlier source and forty-three against the repaired source. Only the restore folders referenced the existing installed runtime, so this was not a clean-install verification. The follow-up also preserved 116 protected files, operating data and the original built application. The first repair’s runtime dependency and this package’s missing test-source context remain distinct failures with their own resolutions.

The existing module demonstrates a related, narrower contract

I did not need another archive containing a small quantity validator. The existing revision-bound workflow module already provides an independent synthetic implementation that keeps invalid input as a draft, prevents preparation and requires a fresh confirmation after a meaningful edit. It is useful for the next question: how long should a previous confirmation remain valid?

The two contracts are not interchangeable. The public module uses length-limited text drafts and integer spellings from one through twenty. Its range and notation are narrower than the native interpreter described here, and I did not install that state machine into the original product. Decimal or exponent spellings should not be expected to pass both implementations identically. Reuse requires reading that difference and deciding how the target business should behave.

For this revision I again checked the existing public ZIP bytes and hash, then ran its twenty-five checks and 4,320 synthetic action sequences from a fresh extraction. There is no new module or upload. This state model does not calculate API item totals and was not installed into the native product. Its results are separate from the first eight quantity checks, the later forty-three client checks and any rendered-browser verification. It remains useful for keeping editing, interpretation, reconfirmation and execution explicit.