Webbo3 Level 2 · JavaScript Development · Day 15 of 20
Project 3: Build a Product
A solo build that belongs in your portfolio, and a page of planning before a single line of code.
By the end of today you can
- Write a short PRD that turns a vague idea into a scoped build
- Choose your own product and defend the choice
- Ship something with state, storage, real UX and accessibility
- Explain the trade-offs you made and what you deliberately left out
Project 1 asked whether you could make a page work. This one asks whether you can build a product: decide what it is, decide what it is not, and finish it.
The deciding is the hard part, and it is why today starts with writing rather than typing.
1. Choose Your Product
Definition
A product is software someone would use again tomorrow: it holds records they create, works something out from them, and helps them find what they need. A page shows things; a product keeps and works on them.
Real-Life Example
A notebook where a market trader writes each sale is a page. The same notebook with a running total at the bottom of every page, and the unpaid ones circled in red, is a product.
Types: Pick One
| Product | The interesting problem in it |
|---|---|
| Personal finance tracker | Categories, monthly totals, a chart you draw yourself from divs |
| Job application tracker | Stages, dates, "no reply in 14 days" logic |
| Student management dashboard | Records, grades, filtering, an average that must always be right |
| Expense splitter | Who owes whom, which is genuinely fiddly arithmetic |
| Event manager | Dates, capacity, "sold out" and past-event states |
Or propose your own, provided it has three things: records the user creates, at least one derived value (a number worked out from the records) that must stay correct, and something worth filtering.
Picture the trader's notebook with the running total and the red circles. That is the difference between a page and a product.
2. Write the PRD First
Definition
A PRD (Product Requirements Document) is one page that says what problem you are solving, for whom, what you will build, and what you will not build. It is not corporate paperwork. It is the page that stops you building the wrong thing for six hours.
Real-Life Example
A builder does not start laying blocks and decide on the number of rooms later. The drawing comes first, and the drawing also says there is no swimming pool, so nobody digs one halfway through.
Types: The Sections
| Section | Answers |
|---|---|
| Problem | What is wrong today, in plain words |
| Who it is for | One real kind of person |
| The one thing it must do well | If it does only this, it is still worth using |
| Core features | What this build includes |
| Out of scope | What it deliberately does not include, and why |
| User stories | "As a ..., I can ..." for each thing a user does |
| How I will know it is done | Checks anyone can run |
Write it before you open your editor, and keep it in the repository as PRD.md. Lines starting with # are headings and lines starting with - are bullet points: that is Markdown, the same format as your README.
It is the section that stops you at 4pm adding login "quickly". Write down three things you are deliberately not building and why. In the assessment you will be asked about them, and "I decided that was out of scope because..." is a much better answer than a half-finished login screen.
Turning "Done" Into a Test
The PRD says the stale badge is "tested at 13, 14 and 15 days". Here is what that means in code. A date's getTime() is the number of milliseconds since 1970, so subtracting two of them gives the gap in milliseconds. A day is 24 × 60 × 60 × 1000 = 86,400,000 milliseconds. We pass "today" in as an argument, so the test gives the same answer whichever day it runs.
Write your own PRD now, before anything else. Then take one line from "How I will know it is done" and write it as check() calls, the way the stale badge was written above.
Picture the builder's drawing: it says where every room goes, and it says in writing that there is no swimming pool.
3. What It Must Include
| # | Requirement | From |
|---|---|---|
| 1 | Create, edit and delete records | Days 3, 4 |
| 2 | One state object, and render from it | Day 11 |
| 3 | Persists in localStorage, and survives corrupt data | Day 11 |
| 4 | Search and at least one filter | Day 9 |
| 5 | At least one derived value that is always correct | Day 11 |
| 6 | Split into modules | Day 6 |
| 7 | Form validation with messages that say what to do | Day 13 |
| 8 | Full keyboard operation and a visible focus ring | Day 13 |
| 9 | Empty state, and a confirmation before destructive actions | Day 9 |
| 10 | Works at phone width | Month 1 |
The Derived Value: Where the Marks Are
A derived value is a number worked out from your records: a total, an average, a count of what is overdue. It must be calculated at render time, never stored. The assessor will add a record, delete one and edit one, and watch whether the number stays correct. This is the single most common place these projects fail.
Here is the failure happening. This tracker keeps a running total in its state and adds to it on every new expense, but the delete button forgets to take anything away.
One expense of ₦3000 on screen, and a total of ₦3500. Every place that changes the list must also remember to fix the total, and one forgot. The cure is not to remember harder; it is to stop storing the total at all and work it out from the list with reduce (Day 3) every time you render.
If your total or count ever appears on the left of an = outside render(), you have stored it. That line is where the wrong number comes from. Delete it and calculate the value in render().
Surviving Rubbish in Storage
Requirement 3 says the app must survive corrupt data. The assessor will open DevTools, type rubbish into your storage key and reload. JSON.parse throws on anything that is not valid JSON, so the load function catches that and starts fresh, as on Day 11.
A Chart Without a Library
A bar chart is a row of divs, each given a width as a percentage of the biggest value. Math.max(...values) finds the biggest: the three dots spread the array into separate numbers, which is what Math.max expects.
Picture the trader's running total written in pencil and rubbed out whenever a sale is cancelled. One forgotten rub-out and every total after it is wrong. Adding up the page each time is slower to write and never wrong.
4. How to Spend the Day
| Time | Doing | Not doing |
|---|---|---|
| First 45 min | The PRD. Sketch the screen on paper. | Opening your editor |
| Next 90 min | Data model and state. Get add and list working, ugly. | Any CSS at all |
| Middle | Edit, delete, filter, search, the derived value. | Polishing |
| Next | Storage, empty state, validation. | New features |
| Last 90 min | Accessibility audit, phone width, README, deploy. | Anything new |
| Final 20 min | Delete dead code. Rename anything vague. | Adding "one more" |
Styling at hour two. It is the comfortable, familiar work you did last month, and it feels like progress. Then at 4pm the delete button does not work and there is no time. Ugly and finished beats beautiful and broken, every time, and it is marked that way.
5. The Rubric
| Criterion | Marks | What is looked at |
|---|---|---|
| PRD and scope | 15 | Is the problem real, is the scope sane, is there an honest out-of-scope list |
| It works | 25 | All ten requirements, tested by somebody who did not build it |
| State and correctness | 20 | One source of truth, derived values that stay right under editing and deleting |
| Code quality | 15 | Modules, naming, no repeated blocks, no dead code |
| UX and accessibility | 15 | Keyboard, focus, empty states, validation messages, phone width |
| Explanation | 10 | Can you justify your scope and your trade-offs |
The Questions You Will Be Asked
- Show me the derived value. Now delete a record while I watch.
- What did you leave out, and what would you build next?
- What happens if I open DevTools and put rubbish in your storage key?
- Which part of this took longest, and was it the part you expected?
- Show me a function you are not happy with, and tell me why.
That last question feels like a trap. Is it?
No, it is the opposite. Every codebase has a function its author is not happy with, and being able to point at it and say why shows you have standards and know where you fell short of them. "There isn't one" is the weak answer: it says either you have not read your own code or you cannot yet tell good from bad.
Hand In
- A GitHub repository, with PRD.md at the root and a commit history that shows the order you built things in.
- A README: what it is, a screenshot, how to run it, what is out of scope, what you would do next.
- A deployed link on GitHub Pages, Netlify or Vercel. It takes ten minutes, and from Day 18 it is compulsory anyway.
Do This Now
- Choose your product and write PRD.md, including three out-of-scope items.
- Sketch the one screen on paper.
- Build add and list with no CSS, then edit, delete, filter and search.
- Calculate your derived value inside render(), then add, edit and delete while you watch it.
- Add storage and put rubbish in the key to prove the app survives.
- Audit keyboard and phone width, write the README, deploy, and hand in.
Questions You Will Have Today
My idea is too ambitious. Do I change it or cut it down?
Cut it down, and write the cut into the out-of-scope section. Changing your idea at 11am costs you the whole morning. Shrinking it costs one paragraph, and "I scoped this down when I realised the date logic was the hard part" is a genuinely good thing to say in the assessment.
Should I use a library for charts, dates or storage?
Not this week. The point of this project is that you can do these things yourself, and you know from Day 12 that the browser does more than beginners assume. A bar chart is a row of divs with a width. A date is Intl. Storage is four methods. Reach for a library in Level 3, when you can say what it is saving you.
My derived value goes wrong after I delete something. Where do I look?
You stored it instead of calculating it. Search for anywhere the total or count is assigned outside render(). That is almost certainly the line. Delete it and compute the value in render() from the current state.
How much CSS is enough?
Readable, spaced, works at phone width, visible focus ring. That is the bar, and it is fifteen marks shared with UX. There are no marks for a colour palette. If your validation messages still say "Invalid" at 4pm, that is where the next hour goes, not into shadows.
Tick Before You Move On
- ☐ My PRD was written before any code and is in the repository
- ☐ All ten requirements work, checked by somebody else
- ☐ The derived value stays correct after adding, editing and deleting
- ☐ Corrupt storage does not break the app
- ☐ Keyboard-only operation works and Lighthouse Accessibility is above 95
- ☐ I deleted dead code and renamed everything vague before submitting
Quick Recap
Decide what it is not, in writing, before you decide what it is. Data and behaviour first; CSS last, and only when it works. The derived value must be calculated, never stored. Rubbish in storage must never break the app. Ugly and finished beats beautiful and broken.
Final Scenario
At the assessment, the assessor adds three records, deletes the middle one, edits the last, and looks at your total. It is right. They open DevTools, type rubbish into your key and reload. The app opens empty and calm.
Then they ask what you left out. You open PRD.md and read them the three lines you wrote at 9am, with the reason for each.
That is what building a product means: not the most features, but the right ones, finished.
Next week: the professional week. AI you supervise, security, shipping, the job hunt, and a final team build for a real client.