What you are building
Pick one: a habit tracker, a marks calculator, or a quest list. Before writing code, describe five exact inputs and expected results, including an empty case, a boundary value, and invalid text.
Use HTML for structure and CSS for presentation. Keep the application state in JavaScript data, update it through named functions, and render the screen from that state so saving, validation, and later changes have one source of truth.
Teal entries are build checks. The four magenta entries explain an application-design problem and link to the relevant Writing an Application lesson.
Project milestones
-
Create and open the app's first page.
One file on your machine, a heading with the name you chose, and nothing working yet. It is a small thing to see. It proves the whole chain: you wrote something and a machine showed it to you.
-
Put one value on the screen with code rather than typing it in the page.
A total that says 0, where the 0 is written nowhere on the page. A line of your code puts it there when the page opens. Change that line to 7, open the page again, and the screen says 7. From here on everything visible comes from something the program knows.
-
Read an input and render the new value.
A box to type in, a button marked Add, and the words you typed showing up underneath. Press it four times and there are four things on screen. Then close the tab, which is where it goes wrong.
-
Reproduce state loss after a reload.
Nothing you did wrong. Everything the program was holding sat in the machine's working memory, which is handed back the moment the page closes. Your four items were never written down anywhere.
What you needA browser gives every app a small store of its own that survives closing, called local storage. It holds text and nothing else, so a list of items has to be turned into one long line of text on the way in and read back into a list on the way out. Turning data into text like that is called serialising it.
step 12Remembering after you close it -
Save and restore the app state.
Both still there, with whatever you ticked still ticked. Test it honestly: quit the whole browser, not just the tab, and open the file cold. This is the first point at which the thing is worth using rather than worth showing someone.
-
Replace repeated markup with data-driven rendering.
You wrote the block that draws one item, then copied it for the second, third and fourth. Now you decide every item should show its points as well. You edit four blocks, one of them still shows no points, and finding out which one takes you ten minutes.
What you needTwo things that arrive together. A list holds many values under one name, in order, so your items stop being four separate variables. A loop runs one block of code once for each value in that list, so the block that draws an item is written once and exists once.
step 7Holding many things at once -
Render any number of items with one function.
Keep the truth in one place, as data, and have one piece of code whose only job is to draw the screen from it. Change the data, run it again. Test it by adding a tenth item without touching the drawing code at all.
-
Keep each state-changing rule in one function.
Awarding points is three lines: add to the total, save it, redraw the screen. Those three lines are now inside the Add button, the tick box, the undo button and the code that runs at start-up. You change what a tick is worth, you fix three of the four, and the app now quietly disagrees with itself.
What you needA function gives a name to a set of steps, so all four places say the same short thing and there is exactly one copy of the actual work. The name earns its keep as much as the saving does:
step 8Naming a recipeaward(10)says what is happening where three lines of arithmetic did not. -
Reject invalid input without corrupting saved state.
Two separate ways in. An empty box adds a row with no words in it, which you cannot tick and cannot get rid of. And anything typed into a box arrives as text, so asking for the word ten as a number gives
NaN, which is what a machine answers when it was asked for arithmetic on something that is not a number. Once that is in the total, every sum after it is NaN too.What you needA check before you use the value, which is what a conditional is for: one question with a yes or no answer, and code that runs on only one of the two answers. In most languages it is spelled
step 5Making a decisionif. Refuse the empty box before it becomes a row, and turn text into a number before adding to it rather than after. -
Pass the invalid-input test set.
Try to break it on purpose: an empty box, a title five hundred characters long, the word ten where points go, minus five, a title made only of spaces. Every one of those should leave the app usable and saying what it refused, which is not the same as quietly dropping it.
-
Show a useful summary when the app opens.
Nothing you pressed. On opening it reads its own saved data and works out something you never typed: how many of today's habits are still undone, the mark you need in the last paper, the points you earned this week. That is the build finished, and it is the difference between somewhere to put things and an app.
Review the app in four passes
Review the same app for normal use, invalid input, responsiveness, and damaged saved data.
Make it work
Done, above. You add, you tick, and it is all still there tomorrow.
Make it correct
Add two items with the same title, tick one, and check the other did not tick. Tick an item, untick it, tick it again, and check the total says 10 rather than 30. Add an item and then compare what is saved against what is on screen.
Make it fast
Write a loop that adds two thousand items, then tick one and count how long the screen takes to change. One tick is redrawing two thousand rows and saving two thousand items. Find out which of those two is the slow one before you change either.
Make it survive
Open your app's store by hand and replace the saved text with the word banana. Reload. A blank screen and an error is the wrong answer, and starting empty because the saved text could not be read back as a list is the right one.
Course links for each pass
Each of those three later passes has a step behind it. Take them when you want them and not before.
-
Telling a wrong screen from wrong data.
Make it correct
A wrong screen has one of two causes: the data is wrong, or the data is right and nobody redrew. Keeping the truth in one place, as data, and having one function whose only job is to draw the screen from it, is what makes those two possible to tell apart at all.
step 11Drawing the screen from data -
Finding out what is actually slow.
Make it fast
A guess about which part is slow is wrong more often than it is right, and rewriting the wrong part costs you a readable program for no gain. Measuring where a program spends its time instead of reasoning about it is called profiling.
step 1Measure first, do not guess -
Surviving what is already in the store.
Make it survive
Reading the store back is where an app meets data it did not write, and the reading itself can fail before any check you wrote gets a chance to run. What you need is a guard that treats unreadable and wrong-shaped as the same answer, and starts empty for both rather than trying to rescue what is there.
step 12Remembering after you close it
Optional extension: add one model-backed feature
Finish and test the ordinary app first. Then choose one narrow language task, such as turning a note into a structured item, grouping similar items, or drafting a weekly summary. Keep the saved data, calculations and input validation in normal code so the app remains useful when the model is unavailable.
- Write twenty examples before choosing a model.
Include empty text, long text, conflicting instructions, unusual names and examples where the correct response is to ask for clarification. Write the expected structured result and split the examples into development and final evaluation sets.
- Put the model behind one function with a typed result.
For example,
extractItem(note)returns a title and integer points. Parse the generated object, reject missing or out-of-range fields and display the original note for correction. Model text never writes directly into storage. - Compare local, remote and no-model paths.
Measure latency, cost and correctness on the same examples. If a remote model is used, state what data leaves the browser and request consent. Add timeout, retry limits and a manual form that always works.
- Run the sealed examples and keep the failures.
Report exact-match or field-level accuracy, invalid-output rate, latency and cost. Turn every production correction into a reviewed regression case without retaining private text unnecessarily.
Relevant lessons: choose prompting, retrieval or weight updates, bound tool use, evaluate stateful model systems, and review generated changes.
Related projects and courses
The app runs where you built it. Getting it in front of somebody else is a separate job: what reaches them is a set of files handed out by a server, a machine whose whole job is answering requests for files, and anything that quietly depended on your own folder stops working there. Step 14, what happens when someone opens it, is that job.
A box that holds a value, a list you can walk, and a function with a name are what every program in this stream is made of. A chat window, a shop and a spreadsheet are all the same three moves you have just made, repeated at a size that needs other people. The next thing to build is one that keeps its data somewhere other than the machine it is shown on.
All builds or read Writing an Application straight through, which covers the same ground in order and finishes by building the whole app in one go.