Until today the stalls were an array typed into data.js. Now they come from the Harbour Market API: GET /api/stalls answers with a JSON array. Every editor here has a real-looking server behind it (the Network tab next to the console shows each request), and it restarts on every run. The API is documented in api.html.
The pattern. const response = await fetch(url) → check response.ok → await response.json(). Two awaits: the first gives you the status and headers, the second the body. These editors run a classic script, so there is no top-level await: use .then() or an async function.
The market has ten stalls now. The page shows the three someone typed in.
Write loadStalls(): fetch /api/stalls and return the parsed body. Then render what it resolves to instead of the hard-coded array. card() and render(stalls) are done (🔒). Open the Network tab and click the request.
The page asked for a stall that doesn't exist and happily rendered an error message as if it were data.
fetch only rejects on a network failure. Make getJSON(url) reject with an Error for every status outside 200–299, with the status code and the server's message (from the error body) in its message: "404 No stall with the id …". Successful responses still resolve with the body.
Clicking Food does nothing, and a request for ?tag=all would be a 400: the API doesn't know that tag.
Write stallsUrl(tag) with URLSearchParams: no query string for "all", ?tag=food otherwise. Then the click listener: press(tag), load that tag, render. getJSON from case 2 is in the locked part.
Homework · bonus
The API sorts with ?sort=price, ?sort=-rating or ?sort=name. Add the select's value to the URL, combined with the tag, and re-load on change. The order of the cards is the server's order: don't sort in the browser.