What we are building, and the rules it must follow
Nine modules of pieces. This one builds a single application that uses all of them, and it is the thing you show somebody when they ask what you can do.
Read this lesson before writing any code. Deciding what you are building — and what you are not — is most of the difference between finishing and abandoning.
The problem
Anna runs a tiffin service in Kharadi. Forty-odd customers, three kinds of thali, deliveries every day. She currently tracks it in a notebook, and the notebook has three problems: she cannot search it, she recalculates the day's takings by hand every evening, and if she leaves it at home she is stuck.
You are building the page she uses on her phone, standing in a kitchen, with one hand free.
That last sentence decides more than it looks like it does. It is why the layout must work at 390px, why the tap targets have to be big enough, and why "it works on my laptop" is not the finish line.
What it does
Add an order. Customer name, phone, pincode, number of plates, and which thali. Rejected with a clear message if any of it is wrong.
Show today's orders, each with the meal, the plate count, the pincode and what it comes to.
Mark an order delivered, and undo that.
Remove an order.
Search and filter — by name, phone or pincode, and by pending or delivered.
Show the day's totals — how many orders, how many still pending, and the money.
Survive a reload. Close the tab, come back, the orders are there.
Get the rates from a file rather than hard-coding them, so the prices can change without editing the code.
What it deliberately does not do
Naming this is as important as the list above. Every one of these is a real decision, not an omission.
No accounts and no login. One person, one phone. Adding accounts means a server, and this is a front-end course.
No server. Data lives in localStorage. That has consequences you will meet
in module 10's third lesson, and the page is honest about them.
No editing an order. Remove and re-add. This is a defensible product decision for a six-field form, and it is the first exercise at the end.
No dates or history. Today's orders only. Adding "which day" changes the data shape, the filtering and the summary all at once — a good extension, and too much for a first build.
No framework, no build step, no dependencies. Plain HTML, CSS and ES modules. You have spent nine modules learning what a framework does for you; do it by hand once.
Scope creep is the thing that kills projects like this. Build exactly this list, get it working, and then extend it. A finished small thing beats an unfinished ambitious one, and it is much better to show someone.
The rules it must follow
These are the course's arguments, and the build is where they have to hold.
State is the truth; the page is a picture of it. One array of orders. Every change updates it, saves it, and re-renders. Nothing is ever read back out of the DOM to find out what is going on.
Money is counted in paise. 80.10 * 3 is 240.29999999999998. Every amount
is a whole number of paise, converted only for display, formatted with Intl so
it groups as ₹12,34,567 and not ₹1,234,567.
Convert at the boundary. Everything a form gives you is a string. It becomes a number once, during validation, and is a number everywhere after that.
textContent, never innerHTML. Customer names are typed by a person and
the rate card comes over a network. Both are untrusted.
Every failure has a state. The rate card can 404, be the wrong thing entirely, or never arrive. Storage can be corrupt. The page says something useful for each, and the orders still render.
One listener for the list. Delegation, so rows added later work without being wired up.
The shape of the code
Nine files, each with one reason to change:
index.html the markup
styles.css the styling
data/rates.json the rate card, fetched at startup
src/
app.js wiring: listeners and startup
state.js the store — the array, and everything that changes it
storage.js localStorage with a fallback that cannot crash the page
api.js fetchJson: timeout, response.ok, three kinds of failure
errors.js the error types and their messages
validate.js form validation, converting at the boundary
render.js building the DOM from state
format.js rupees and dates
The imports point one way. app.js imports the others; nothing imports
app.js. That is how you avoid circular imports by construction rather than by
debugging them later.
If a file starts needing to know about two unrelated things, that is the signal
to split it. render.js should change when the design changes; api.js should
change when the server does. Those are different days.
How the three build lessons go
Building the interface — markup, rendering from state, the form, and the delegated list. At the end of it the page works, in memory.
The data layer — the store, localStorage, and fetching the rate card with
every failure handled. At the end of it the page survives a reload and a broken
network.
Polish and deploy — validation messages, keyboard and screen-reader access, the mobile layout, and a live URL.
Type it. Do not paste it. You will make mistakes, and fixing your own mistakes is the entire mechanism by which this becomes yours. Every bug in this course was found by running something, and yours will be too.
What finished looks like
A URL you can send someone. It works on a phone, it does something useful, and it does not break when the network does.
That is a portfolio piece. Not because a tiffin tracker is impressive, but because handling the empty state, the error state and the offline case is exactly what separates somebody who has finished a tutorial from somebody who can be given work.
Check your work
The user is a shop owner on a phone in a kitchen — which is why 390px and tap targets are requirements rather than polish.
Seven features are in scope; accounts, a server, editing, history and frameworks are explicitly out. Naming what you are not building is what stops scope creep.
One array is the truth. The page is rendered from it, never read back into it.
Money in paise, converted only for display, because floating point cannot
hold 80.10 exactly.
Everything from a form is a string and is converted once, at validation.
textContent for anything a person typed or a server sent.
Nine files, imports pointing one way, so no import cycle is possible.
Practice
Before writing any code:
- Write the data shape down. What exactly is one order? Name every field and its type. Decide where the money lives — on the order, or derived from the meal? Both are defensible; know which you chose and why.
- Sketch the page on paper at phone width. Where does the form go relative to the list? What does a single row contain?
- Write down the four states of the list: nothing yet, loading the rates, an error, and orders shown. What does each look like? Then add a fifth — a search matching nothing — and decide how it differs from "no orders yet".
- List every way the page can fail that is not the user's fault. You should get at least four.
- Decide what happens when the rate card cannot be loaded but there are already orders in storage. There is a right answer and it is not "show nothing".
- Write one sentence saying what this is, as you would to someone else: "A page for a tiffin service owner to …". If you cannot finish that sentence in one line, the scope is still too large.
Next: building the interface, and rendering it from state.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship