0 / 91
Week 4 · Day 28 of 91

Week 4 inventory application

JavaScript Foundations II and the Browser

Objective

Combine DOM, events, state, modules, array methods, and storage.

Events are interrupts, promises represent future results, and the DOM is the browser’s live model of the page.

  • feature slicing
  • manual test cases
  • UI empty and error states

Why this matters

Six days of parts become one thing today. Array methods produce the reports, modules keep the files apart, the DOM draws the page, events drive it, one array holds the truth, and localStorage makes it survive a refresh. Nothing new is being introduced — today is about assembly, and about the two habits that separate a demo from an application: building in thin slices, and testing what you built on purpose rather than by poking at it.

By the end of the hour you have a component inventory you would actually use, and a written checklist that proves it works.

Feature slicing

The tempting way to build is by layer: write all the HTML, then all the state, then all the rendering. It fails at the end, all at once, and you have no idea which layer is wrong.

Feature slicing is the opposite. You take one feature and build it all the way through — input, state change, render, persistence, verify — before starting the next. Each slice is small, each one is testable the moment it lands, and when something breaks you know it was the slice you just finished.

Today's six slices, in a deliberate order:

  1. Add — form to state to list to storage. (You have this.)
  2. Delete — remove from state, redraw, save. (You have this.)
  3. Low-stock badge — derived in render, no new state.
  4. Search — filter the rendered list by name.
  5. Category filter — a second, combinable filter.
  6. Export to JSON — read state, produce a file.

Notice that 3, 4, and 5 add no new state at all. They are views over the array you already have, which is the payoff for yesterday's discipline.

Bring up one signal path first

You do not populate every component on a new board and then apply power. You bring up the supply rail, confirm it, add the MCU, confirm it enumerates, then blink one LED end to end. Once that single path works, every later stage is added against something known good. A feature slice is that first working path: thin, complete, and verified before you widen it.

Deriving the visible list

Search and filter must never change components. The array is the inventory; a search box is a question about it. Delete an item while a search is active and the item must disappear from storage; type in the search box and nothing must change in storage at all.

So keep the controls' values as small, separate UI state, and compute the visible list on each render:

function getVisible() {
  const query = document.querySelector("#search-input").value.trim().toLowerCase();
  const category = document.querySelector("#category-filter").value;

  return components
    .filter((item) => item.name.toLowerCase().includes(query))
    .filter((item) => category === "all" || item.category === category);
}

includes returns true when one string contains another. Lowercasing both sides makes the search case-insensitive. An empty query lowercases to "", and every string contains "", so an empty box shows everything — no special case needed.

Then render draws getVisible() instead of components, and both controls fire it:

document.querySelector("#search-input").addEventListener("input", render);
document.querySelector("#category-filter").addEventListener("change", render);

The input event fires on every keystroke, so the list narrows as you type. change fires when a <select> value is committed.

One count is now ambiguous

With a filter active, "3 components" could mean three in the inventory or three matching. Decide which and say so: `${visible.length} of ${components.length} components` removes the ambiguity entirely. A label that is right only sometimes is a bug you shipped on purpose.

Empty and error states

A list with nothing in it is not one situation, it is three, and they need three different messages. Showing a blank area for all of them is the single most common way a working app looks broken.

Situation What the person should see
No components stored at all "No components yet. Add your first one above."
Components exist, none match the filters "No components match your search." plus a way to clear it
Stored data was unreadable "Saved data could not be read, so the list was reset."

The first is a beginning, the second is a dead end the person can escape, the third is a failure they deserve to know about. Yesterday's loadComponents already returns [] when the stored value is corrupt — today you surface that instead of hiding it, by returning a flag alongside the data or setting a message when the catch block runs.

Three empty shelves

A shelf with a "new stock arriving Monday" card, a shelf whose label says "size 9 — try size 10 in aisle 4", and a shelf that is empty because the delivery van crashed. Identical from a distance. Completely different things to tell the person standing in front of it.

Manual test cases

A manual test case is three written lines: the steps, the expected result, and what actually happened. Written down, in advance, so that "it works" becomes a claim with evidence behind it.

They matter most where state, view, and storage can disagree. Deleting is the clearest example, because one action has to land in four places, and all four need checking: the data state (is the item gone from components?), the rendered list (is the row gone from the page?), the persisted storage (is it gone after a refresh?), and the empty state (does the right message appear if that was the last item?). Check only the screen and you can ship a delete that comes back on reload.

| # | Steps | Expected | Actual |
|---|---|---|---|
| 1 | Add "Resistor 10k", qty 42 | Row appears, count reads 1 of 1 | |
| 2 | Refresh the page | Row still present | |
| 3 | Delete it, then refresh | List empty, "No components yet" shown | |
| 4 | Add 3 items, search "cap" | Only matching rows, count reads 1 of 3 | |
| 5 | Search "zzz" | "No components match your search." | |
| 6 | Submit with empty name | Message shown, nothing added | |
| 7 | Add item with qty 3 | Row shows LOW badge | |
| 8 | Click Export | File downloads, opens as valid JSON | |

A test plan, not a poke

You do not certify a board by waving a probe at it. You write the procedure first — apply 5 V, measure this pin, expect 3.3 V ±5% — and record the reading in the actual column. The value of the document is that it was written before the measurement, so you cannot quietly change what you expected. A test case is that procedure.

Walkthrough

Build the last slice, export to JSON, which is the one mechanic you have not met.

Add a button to index.html:

<button id="export-button" type="button">Export JSON</button>

And in your module:

function exportJson() {
  const text = JSON.stringify(components, null, 2);
  const blob = new Blob([text], { type: "application/json" });
  const url = URL.createObjectURL(blob);

  const link = document.createElement("a");
  link.href = url;
  link.download = "inventory.json";
  link.click();

  URL.revokeObjectURL(url);
}

document.querySelector("#export-button").addEventListener("click", exportJson);

Line by line: JSON.stringify(components, null, 2) produces indented text a human can read. A Blob is a chunk of file-like data held in memory. URL.createObjectURL mints a temporary URL pointing at that blob, which lasts until the page unloads — revokeObjectURL releases it as soon as you are done. The <a> element is created, never added to the page, and clicked in code; its download attribute is what makes the browser save the file instead of navigating to it.

Click it. Your browser downloads inventory.json. Open it in your editor: it is your array, and because it is valid JSON, you could paste it straight back into localStorage.

Checkpoint

Export with an empty inventory. You get a file containing []. Decide whether that is acceptable or whether the button should be disabled when components.length === 0 — and whichever you choose, write it as test case 9.

Your turn

Build the deliverable: a usable browser inventory app and a short demo checklist.

  1. Write test-plan.md first, using the table above. Fill in expected results; leave Actual blank. Writing it before building is the whole exercise.
  2. Add the low-stock badge in render: quantity below 10 gets a LOW span and a low class. Derive it — no isLow field in the array.
  3. Add the search input and getVisible, and change render to draw the visible list. Confirm typing narrows the list and storage is untouched (check the Application panel).
  4. Add the category <select id="category-filter"> with an all option plus one option per category. Confirm search and filter combine rather than overriding each other.
  5. Add the count line reading X of Y components, and all three empty states from the table above. Trigger each one deliberately.
  6. Add the export button from the walkthrough.
  7. Run every case in test-plan.md, filling in the Actual column honestly. Any mismatch is a bug: fix it, then rerun that case and the two around it.
  8. Commit with Day 13's habits — small, described commits — and update your README with what the app does and how to run it (npx serve).

You are done when

Every row of test-plan.md has an Actual column matching Expected, the app survives a refresh, and you can demo add, search, filter, delete, and export in under two minutes without touching the Console.

Reviewer mode — after your test plan is filled in

Today's mode is reviewer: hand over finished work and ask what is wrong with it. What a code review should primarily produce is specific, actionable findings with evidence — not general praise, not a complete rewrite every time, and not hidden changes.

"Review this app for state bugs, unclear names, inaccessible controls, and missing edge cases."

Turn each finding into a new numbered test case that reproduces it. If you cannot write the reproduction steps, you have not understood the finding well enough to accept it.

Common pitfalls

  • Filtering the array instead of the view. components = components.filter(...) in a search handler deletes everything that did not match, and one save later it is permanent. Search builds a new list to draw; it never reassigns the state.
  • One empty state for three situations. "No results" shown to a brand-new user reads as broken. Distinguish empty inventory, empty search, and failed load.
  • Testing only the screen. After deleting, also check components in the Console and refresh the page. State, view, and storage each need their own confirmation.
  • Widening before a slice works. Adding search while delete is half-finished means two unverified features and no idea which one broke. Finish and test each slice, then start the next.

Verify it yourself

Open today's reference, MDN's DOM scripting introduction, and its surrounding pages.

  1. Your delete buttons are icons or short labels in a list. Find MDN's guidance on accessible names for buttons and check whether a screen reader would know which component each button deletes. If not, fix it — aria-label with the component name is the usual answer — and write down what you changed.
  2. This lesson used input for the search box and change for the select. Find both events in MDN and confirm, in one sentence each, when they fire. Would change on the search box behave differently?

Add both answers to notes/day-28.md. Week 5 moves to build tools and TypeScript, and it starts from this app — an application you have tested is a much better thing to build on than one you have merely finished.

The hour

  1. 0–5 min Recall

    Without notes, state yesterday’s main idea and one unresolved question.

  2. 5–20 min Learn

    Read only the listed concept notes and official reference sections needed today.

  3. 20–48 min Build

    Finish search, category filter, add, delete, low-stock badge, and export-to-JSON.

  4. 48–55 min Explain and verify

    Run the result, inspect evidence, and explain the data/control flow in your own words.

  5. 55–60 min Quiz and commit

    Complete the quiz, record one lesson, and commit the verified change when applicable.

What to hand in

Deliverable

A usable browser inventory app and a short demo checklist.

Working with AI today

AI as skeptical reviewer

Provide existing work and ask for concrete defects, risks, missing tests, and unsupported assumptions—not praise.

Review this app for state bugs, unclear names, inaccessible controls, and missing edge cases.

References

End-of-day quiz

Q1 What should be tested after deleting an item?
Q2 Which result best proves today’s work is complete?
Q3 What should an AI code review primarily produce?

Explain-back gate

Pass the quiz above to unlock completion.

Quiz + explain-back checks required.