🍬 Treat Yourself · App Map

A take-home guide to what was built, where things live, and one practice worth reusing.

What this app is

Treat Yourself is a single web page that helps someone notice their added sugar without guilt. You answer two questions and get a personal daily limit with its source. Then you tap treats to log them, a sugar scale fills toward your limit, and Honey the bear reacts kindly. Every treat has an ⓘ button showing where its number came from. Your profile, goal, and today's log stay in your browser and the log starts fresh each day.

It is plain HTML, CSS, and JavaScript modules, with no framework, no server, and no dependencies. Seven files do the work:

One action, end to end: tapping a treat

How a treat tap flows through the code A tap on a treat card goes to logTreat in app.js, which saves the log through storage.js to localStorage, then renderScale calls scaleState in logic.js and honeySVG in honey.js, and the sugar scale, readout, log row, and Honey update on screen. You tap a treat a .treat-add button js/app.js logTreat(treat) js/storage.js saveLog(...) localStorage ty.log js/app.js renderScale() js/logic.js scaleState(total, limit) js/honey.js honeySVG(mood) On screen the scale fills and changes color, the readout updates, a log row appears, Honey changes mood and waves

Pink boxes are what you see. White boxes are code. The green box is the browser's storage, which is the only place anything is saved.

Reference route: follow a treat tap through the code

This is a reference route to read at your own pace, not a record of a guided tour.

Stop 1 · The tap: js/app.js, search for function logTreat A click on the treat grid is caught by one listener that finds the card's data-treat id, looks the treat up in TREATS, and calls logTreat. That function pushes a copy of the treat (name, emoji, grams, time) onto state.log, calls persistLog() to save it, then re-renders the log and the scale. The ⓘ button is handled first in the same listener and returns early, which is why it never logs anything.
Stop 2 · The math: js/logic.js, search for export function scaleState Given today's total and the daily limit, it returns everything the screen needs: the percent, grams left or over, the fill height, the color step (mint, butter, peach, rose at 40, 70, and 100 percent), and Honey's mood. It has no idea a web page exists, which is why tests/logic.test.js can check it with plain numbers.
Stop 3 · The screen: js/app.js, search for function renderScale It calls scaleState, then writes the results into the tube, the readout, and Honey's speech bubble, swapping Honey's drawing with honeySVG(mood) only when the mood changes, and adding the is-waving class so the CSS wave animation plays.
Where it is saved and loaded

js/storage.js wraps every localStorage call in try/catch and checks the shape of what it reads. On start, init() in js/app.js loads the profile, goal, and log; if the saved log's day is not today's todayKey(), the log starts empty. checkDay() repeats that check every 60 seconds and whenever the tab becomes visible again.

To change this, start here

Want to…Start at
Add or fix a treatjs/treats.js, the TREATS array. Copy an existing record and keep its source link honest. npm test will fail if a record is missing its serving, basis, or https source.
Change a daily limit or its sourcejs/logic.js, recommendedLimit and SOURCES. Then update the limit table in tests/logic.test.js and the README.
Change when the scale turns a colorjs/logic.js, the color thresholds inside scaleState, and the matching test "color steps at 40, 70, and 100 percent".
Change what Honey saysjs/app.js, honeyMessage.
Change the lookstyles.css, the color tokens at the top of the file under :root.
Change the goal range (1 to 300)js/logic.js, GOAL_MIN and GOAL_MAX, plus the min/max on the input in index.html and the error copy.

A practice to reuse: write the failure down, then test it with garbage

What happened here. The spec listed, under Important Failure Modes, that saved data might be "corrupt or an old shape" and that the app should "ignore it and start fresh at the Profile screen instead of crashing." That one sentence became a test before the feature was finished: tests/storage.test.js feeds ty.profile the text garbage{{{ and a profile with an impossible value, and expects loadProfile() to return null. The build checklist then verified it in the browser by writing garbage into storage and reloading, and the Profile screen appeared with no error.

Why it helps. Naming the failure in the plan made the "done" condition observable: not "storage is robust," but "this exact bad input produces this exact safe result." That is cheap to test and hard to argue with.

On another project. When a spec says data comes from somewhere you don't control (a file, an API, a form, a saved state), write one line about what happens when it is wrong, then write the test that hands the code that wrong thing. Do it before the happy-path UI is polished, while the data shape is still fresh.

Run it again

From the project folder:

python3 -m http.server 8000

Then open http://localhost:8000. Run the tests with:

npm test

This map is a snapshot of the code at commit 169fb39. If the code changes later, the function names and paths above may drift. Reopen this file any time from devpost/app-map.html.