Skip to content
zero2vibecodelearn vibe coding
Level 0 · A habit tracker, start to finish 9 min Reviewed by the author · Oct 22, 2026

The ugly version, then make it remember

Two turns: a page that works end to end with no styling and no memory, then the change that makes it survive a reload. Each one is run, read and committed before the next.

Level 0 · 0 / 4 lessons0%
In this lesson

No hands-on task in this lesson — just read it through and answer the questions.

You have a folder with a README and one commit. Now the code starts, and the rule for this lesson is the one from building turn by turn: get an ugly version working end to end before you improve anything.

Step 2: the ugly version

Create index.html and app.js. A form with a text field and an Add button adds a habit to a list on the page. Each habit has one checkbox for today. Record each tick as the day in YYYY-MM-DD form, in my local time zone. Keep everything in memory for now: no saving, no styling.

No saving and no styling are deliberate. Saving is the next turn. Styling is three turns away. This turn is only about the path from typing a habit to ticking it.

Run it

Open index.html in your browser. Add two habits. Tick one. Reload the page.

Both habits are gone. That is correct for this step: the prompt said in memory only. If they survived, the agent did more than you asked, and the diff will show where.

Read the diff

You do not need to understand every line. Look for two things.

How the name reaches the page. Find the line that puts the habit’s name into the list. If it uses innerHTML with the name in it, a habit called <b>Read</b> turns bold instead of showing the brackets: the browser is treating what you typed as code. Ask for textContent instead. It is a one-line fix and a habit worth having.

How today’s date is made. If you see toISOString(), that is the date in UTC, not where you are. Depending on your time zone, a tick late in the evening or early in the morning lands on the wrong day, and your streak breaks for no visible reason. Ask for the date to be built from the local year, month and day.

Quick check

The code makes today's date with toISOString(). What goes wrong?

Commit it

Checkpoint: step 2 in the reference repository. It uses textContent for the name and builds the day from the local date, with a comment saying why. Your version will look different. The behaviour should not.

Step 3: make it remember

Save the habits in localStorage so they survive a reload. If what is stored is missing or broken, start with an empty list instead of crashing.

The second sentence matters. Saved data is something your code reads back later, and by then it may have been cleared, half-written or edited by hand. A page that crashes on bad data is a page that shows a blank screen the day it happens, with nothing to tell you why.

Run it

Add a habit, tick it, reload. Still there. Close the tab, open the file again. Still there.

Now break it on purpose. Open your browser’s developer tools, find the storage panel and the Local Storage entry for the page, and change the saved value to nonsense, a word with no brackets. Reload. You should get an empty list, not a blank page and not an error.

Read the diff

localStorage only stores text, so the habits have to be turned into text on the way in and back on the way out. You will almost certainly see JSON.stringify when saving and JSON.parse when loading. Check that the parsing sits inside something that catches errors, a try block, because JSON.parse on nonsense throws, and an uncaught throw stops the script.

Also check what is saved: the list of habits and the days they were ticked. Nothing else should be there.

Last, check the name it is saved under, the storage key. It should be specific to this app. Every page on the same site shares one storage area, so a generic key like data is one another app could overwrite.

Commit it

Checkpoint: step 3 in the reference repository. The loader returns an empty list for anything it cannot read, and saving is wrapped too, so a browser that blocks storage still gets a working page that simply does not remember.

If a turn goes wrong

What you have now

A page that works, remembers, and looks terrible. That is the right place to be. The next lesson adds the parts that make it worth opening every day, one turn each.

My agent added styling in step 2 anyway. Is that a problem?

Only in that the diff is bigger than it needed to be. You can keep it, or ask the agent to take it out so this commit does one thing. What matters is that you noticed, because the next unrequested extra may not be harmless.

Do I need a local server to open index.html?

No. For this project, opening the file directly in the browser works. If your browser refuses to save anything when the page is opened as a file, the published version in the last lesson will behave normally.

Why does the agent keep wanting to add a framework?

Because many examples it learned from use one. Your not-doing list says plain HTML, CSS and JavaScript, so point at it and decline. A framework would add a build step and dependencies this project does not need.

Check your understanding

Answered 0/3
Mode:

Why build a version with no saving and no styling first?

The diff shows the habit name being put on the page with innerHTML. What do you ask for?

How do you check that broken saved data does not break the page?

Finish the task in the terminal and answer the questions to complete the lesson.

📝 My notes

Saved automatically in this browser · shows up on the Notes page.

Lesson Q&A

Why build a version with no saving and no styling first?

So every later step can be checked by running something that already works end to end — Once the whole path works, however ugly, each turn changes one thing on top of something you have already seen working. That is what makes a turn checkable.

The diff shows the habit name being put on the page with innerHTML. What do you ask for?

textContent instead, because the name is text the user typed, not markup — With innerHTML, a name containing angle brackets becomes real HTML. Here it is your own browser, so the harm is small, but the habit of treating typed text as text is the one that matters later.

How do you check that broken saved data does not break the page?

Change the stored value to nonsense in the browser's developer tools and reload — The agent saying "handles errors gracefully" is a claim. Breaking it on purpose and reloading is evidence, and it takes thirty seconds.

The code makes today's date with toISOString(). What goes wrong?

A habit is done on your day, not on the day in London. The bug only shows up at certain hours, which is why it is worth catching in the diff instead of waiting for it.

Move between lessons with ← → · search with ⌘K

Written and reviewed by Mikhail Kuzmitskii