A render can break an interaction
A page replaces its entire form whenever quantity changes. The cursor jumps and keyboard focus disappears. The displayed number may be correct, yet the interface is difficult to use.
Separate the editable form from the result region. Bind events once. Update the result after a calculation and preserve the active form controls during normal typing. Replace an item list when its data changes, not the whole document.
export function renderPhase(document, state) {
const result = document.querySelector('#quote-breakdown');
const status = document.querySelector('#action-status');
result.hidden = state.phase !== 'ready';
status.textContent = state.phase === 'dirty'
? 'Inputs changed. Calculate to update the quote.'
: state.phase === 'ready' ? 'Quote updated.' : '';
}
Your existing quote renderer supplies the amount content; this small function communicates whether that content is current. Create both elements before calling it. Do not infer phase by checking the text of a heading.
Build an item list
For each item, create an li and set its textContent. Use replaceChildren to clear the previous list once before appending the new rows. If the list later contains editable controls, preserve identity with stable item ids rather than replacing every control during typing.
Do not use array positions as permanent business identifiers. Removing the first item changes later positions without changing which items they represent.
Independent experiment
Type a long customer name. Edit quantity, then tab to the next field. Verify that your render has not recreated the input. Submit and confirm the result changes. Trigger an invalid quote and then restore valid input: the old error must disappear when a valid quote is shown.
Correction criteria
The focused input remains the same element during editing. Dirty state does not present an old amount as current. Success and failure are mutually consistent. You can explain every DOM replacement and why it is necessary.
Change request
"Announce the total every time I type." That can overwhelm screen-reader users. Prefer announcing a completed calculation or meaningful status transition. Use a persistent status region and update its text; do not repeatedly move focus for background updates.
Review
Rendering is the responsibility of making the screen agree with state. It must also preserve the person's ongoing interaction. This is one reason later component frameworks track identity and update boundaries, but you can already practice the underlying discipline in native JavaScript.