<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Anirban Majumdar</title>
    <description>The latest articles on Forem by Anirban Majumdar (@anirseven).</description>
    <link>https://forem.com/anirseven</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F661142%2Ff351b9b8-d87f-4385-b8a3-28e69c3935ac.jpeg</url>
      <title>Forem: Anirban Majumdar</title>
      <link>https://forem.com/anirseven</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/anirseven"/>
    <language>en</language>
    <item>
      <title>`@playwright/cli` Is Not `npx playwright test` — And That Difference Is Costing Your AI Agent Thousands of Tokens</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Tue, 05 May 2026 07:26:40 +0000</pubDate>
      <link>https://forem.com/anirseven/playwrightcli-is-not-npx-playwright-test-and-that-difference-is-costing-your-ai-agent-277f</link>
      <guid>https://forem.com/anirseven/playwrightcli-is-not-npx-playwright-test-and-that-difference-is-costing-your-ai-agent-277f</guid>
      <description>&lt;p&gt;As an SDET or Automation Engineer, failing tests are part of the daily grind. With the rise of Agentic AI, fixing scripts is easier than ever—but there’s a catch that tutorials rarely mention: Scale.&lt;/p&gt;

&lt;p&gt;In a real-world enterprise suite, you aren’t dealing with 10 tests; you’re dealing with 500. When 200 of them fail right before a major release—often due to a single upstream change by another team—feeding all that data into an AI Agent is a "token bonfire." It’s expensive, slow, and often unnecessary.&lt;/p&gt;

&lt;p&gt;So, what’s the move? Do you use a Playwright MCP, a standalone LLM, or the Playwright CLI?&lt;/p&gt;

&lt;p&gt;The answer is: It depends on the scope of the fix.&lt;/p&gt;

&lt;p&gt;In this post, we’ll explore how to leverage the Playwright CLI to analyze and resolve mass failures efficiently, keeping your token costs low and your velocity high.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly is Playwright and where do you use it
&lt;/h2&gt;

&lt;p&gt;Before we talk about tokens, let's clear up the confusion that tripped me up for weeks.&lt;/p&gt;

&lt;p&gt;There are three different Playwright tools that all sound similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test        →  the test runner you already know
                              runs your spec files, reports pass/fail

@playwright/cli            →  a NEW, separate package
(playwright-cli)              built specifically for coding agents
                              controls a live browser one command at a time

Playwright MCP Server      →  for persistent agent browser sessions
                              keeps browser alive across a long investigation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most QA engineers know the first one. Almost nobody has heard of the second. And the confusion is understandable — they're both "Playwright", they both involve browsers. But they're built for completely different consumers.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npx playwright test&lt;/code&gt; is built for &lt;strong&gt;humans&lt;/strong&gt; and &lt;strong&gt;CI pipelines&lt;/strong&gt;. It runs your spec files, prints results to a terminal, exits with a code. A human reads the output. A pipeline checks the exit code. Done.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;playwright-cli&lt;/code&gt; is built for &lt;strong&gt;coding agents&lt;/strong&gt;. It was created specifically because tools like Claude Code and GitHub Copilot need to interact with a live browser — not run pre-written scripts, but actually observe a page, click things, see what changed, and loop. Every command returns a compact, structured snapshot of the current page state that a machine can read and reason about efficiently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing It
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# It's a separate package from @playwright/test&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @playwright/cli@latest

&lt;span class="c"&gt;# This is important — installs structured command docs locally&lt;/span&gt;
&lt;span class="c"&gt;# so Claude Code knows all available commands without running --help&lt;/span&gt;
playwright-cli &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--skills&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;--skills&lt;/code&gt; flag is a small thing that saves tokens on every single agent session. Instead of the agent running &lt;code&gt;playwright-cli --help&lt;/code&gt; to discover commands (costing ~300 tokens), it reads the locally installed docs once at session start.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Looks Like In Use
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Open the live app in a browser&lt;/span&gt;
playwright-cli open https://habit-tracker-one-dun-53.vercel.app/

&lt;span class="c"&gt;# Get the current page state — returns all interactive elements&lt;/span&gt;
playwright-cli snapshot

&lt;span class="c"&gt;# Interact using element refs from the snapshot&lt;/span&gt;
playwright-cli click e45
playwright-cli fill e12 &lt;span class="s2"&gt;"Morning Run"&lt;/span&gt;
playwright-cli press Enter

&lt;span class="c"&gt;# Capture state visually&lt;/span&gt;
playwright-cli screenshot

&lt;span class="c"&gt;# Check what network calls are happening&lt;/span&gt;
playwright-cli network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;snapshot&lt;/code&gt; command is the key one. Here's what it actually returns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page: Habit Tracker
URL: https://habit-tracker-one-dun-53.vercel.app/

Interactive elements:
  e12  input[placeholder="Enter habit name..."]   textbox
  e13  input[type="date"]                         date picker
  e14  select.status-selector                     combobox
  e45  button.submit-habit-form                   button "Add Habit"
  e67  a[href="/trends"]                          link "Trends"
  e68  a[href="/about"]                           link "About"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. ~150 tokens. Every interactive element on the page, with a ref ID the agent can use to interact. No noise, no full HTML, no CSS, no scripts — just what an agent needs to act.&lt;/p&gt;

&lt;p&gt;This is the foundation of why playwright-cli is so much more token-efficient than alternatives. But let's make that concrete.&lt;/p&gt;




&lt;h2&gt;
  
  
  The App I'm Testing
&lt;/h2&gt;

&lt;p&gt;Throughout this post I'll use a real example: a React Habit Tracker app I built and deployed on Vercel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Live app:&lt;/strong&gt; &lt;a href="https://habit-tracker-one-dun-53.vercel.app/" rel="noopener noreferrer"&gt;https://habit-tracker-one-dun-53.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One spec file, two test categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@smoke&lt;/code&gt; — happy path: add habit, mark complete, delete&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@regression&lt;/code&gt; — edge cases, validation, error states&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing exotic. Simple enough to follow along, realistic enough to make the problem interesting.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Token Problem With &lt;code&gt;npx playwright test&lt;/code&gt; Alone
&lt;/h2&gt;

&lt;p&gt;Here's what happens when a naive agent uses only &lt;code&gt;npx playwright test&lt;/code&gt; to investigate a failure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "Why is the add-habit smoke test failing?"

Agent (without playwright-cli):
  Step 1: Read entire spec file to understand the test    ~3,200 tokens
  Step 2: Read playwright.config.js for context             ~180 tokens
  Step 3: Run npx playwright test --reporter=list           ~800 tokens
  Step 4: Parse terminal output, identify failure           ~600 tokens
  Step 5: Read source components to find selector          ~2,800 tokens
  Step 6: Guess what the DOM looks like now                ~1,400 tokens
  Step 7: Propose a fix (might be wrong)                   ~700 tokens
  ──────────────────────────────────────────────────────
  Total:                                                  ~9,680 tokens
  Certainty of diagnosis:                                       ~60%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent is reading source files and &lt;em&gt;guessing&lt;/em&gt; what the live DOM looks like. It never actually looks at the app. Its fix might be wrong and cost you another loop.&lt;/p&gt;

&lt;p&gt;Here's the same task with &lt;code&gt;playwright-cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "Why is the add-habit smoke test failing?"

Agent (with playwright-cli):
  Step 1: npx playwright test --grep "@smoke" --reporter=json  ~420 tokens
  Step 2: playwright-cli open + snapshot on failure            ~190 tokens
  Step 3: Read ONLY the failing test block                     ~280 tokens
  Step 4: Compare snapshot to test, diagnose                   ~480 tokens
  Step 5: Propose exact fix from what it actually saw          ~150 tokens
  ──────────────────────────────────────────────────────
  Total:                                                     ~1,520 tokens
  Certainty of diagnosis:                                        ~95%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;84% fewer tokens. Higher confidence. No guessing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The agent saw the actual live DOM. It didn't read five files hoping to reconstruct what the UI looks like — it just looked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why playwright-cli Is So Token-Efficient
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Snapshot output is compact by design
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;playwright-cli snapshot&lt;/code&gt; command returns a structured, minimal representation of the current page — only interactive elements, only what an agent needs to act:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;playwright-cli open https://habit-tracker-one-dun-53.vercel.app/
playwright-cli snapshot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Page: Habit Tracker
URL: https://habit-tracker-one-dun-53.vercel.app/

Interactive elements:
  e12  input[placeholder="Enter habit name..."]   textbox
  e13  input[type="date"]                         date picker
  e14  select.status-selector                     combobox
  e45  button.submit-habit-form                   button "Add Habit"
  e67  a[href="/trends"]                          link "Trends"
  e68  a[href="/about"]                           link "About"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's ~150 tokens. It tells the agent everything it needs: what elements exist, what their refs are, what their current classes and labels are.&lt;/p&gt;

&lt;p&gt;Compare that to fetching the full page HTML — which would be 2,000–8,000 tokens of noise the agent has to parse to extract the same 6 elements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Ref-based interaction means no selector archaeology
&lt;/h3&gt;

&lt;p&gt;Once the agent has a snapshot, it uses refs — not selectors it has to guess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;playwright-cli click e45          &lt;span class="c"&gt;# click "Add Habit" button&lt;/span&gt;
playwright-cli fill e12 &lt;span class="s2"&gt;"Morning Run"&lt;/span&gt;
playwright-cli press Enter
playwright-cli snapshot           &lt;span class="c"&gt;# see what changed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No reading source code to figure out what class name the button has. No risk of using a stale selector from a file that hasn't been updated since last deploy. The snapshot tells you the current truth.&lt;/p&gt;

&lt;p&gt;This is the core insight: &lt;strong&gt;playwright-cli trades file reading for direct observation.&lt;/strong&gt; File reading costs hundreds to thousands of tokens per file. Observation costs ~150 tokens for the entire page state.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The &lt;code&gt;--skills&lt;/code&gt; flag prevents repeated &lt;code&gt;--help&lt;/code&gt; calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;playwright-cli &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--skills&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This installs structured command documentation locally. Claude Code reads it once at the start of a session — it never needs to run &lt;code&gt;playwright-cli --help&lt;/code&gt; to discover available commands. That's another ~300 tokens saved per session, and it means the agent's responses about playwright-cli are accurate and consistent.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Scoped execution with &lt;code&gt;--grep&lt;/code&gt; on the test runner side
&lt;/h3&gt;

&lt;p&gt;The other half of token efficiency is on the &lt;code&gt;npx playwright test&lt;/code&gt; side. Never let an agent run your full suite when it only needs a subset:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Full suite — 200 tests, large JSON output, expensive to parse&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--reporter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json

&lt;span class="c"&gt;# Scoped to smoke — 6 tests, tiny JSON output, cheap to parse&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"@smoke"&lt;/span&gt; &lt;span class="nt"&gt;--reporter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json

&lt;span class="c"&gt;# Single failing test — 1 result, minimal output&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"should add a new habit"&lt;/span&gt; &lt;span class="nt"&gt;--reporter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON output for 1 test is ~80 tokens. For 200 tests it's ~3,000 tokens. The agent gets the same actionable information either way. Always scope.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Token Breakdown Across a Real QA Cycle
&lt;/h2&gt;

&lt;p&gt;Here's a full pre-merge QA run on my habit tracker, with actual token estimates at each step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Task: "Is the habit tracker ready to merge?"

─── Phase 1: Discovery ──────────────────────────────────────────
npx playwright test --list --grep "@smoke"
  → Returns 6 test names
  → Cost: ~80 tokens

─── Phase 2: Smoke Run ──────────────────────────────────────────
npx playwright test --grep "@smoke" --reporter=json
  → 5 passed, 1 failed
  → JSON output parsed by agent
  → Cost: ~420 tokens

─── Phase 3: Triage (playwright-cli) ───────────────────────────
playwright-cli open https://habit-tracker-one-dun-53.vercel.app/
  → Cost: ~30 tokens

playwright-cli snapshot
  → Returns current DOM state
  → Cost: ~150 tokens
  → Agent sees: button class is "submit-habit-form" not "habit-submit-btn"
  → Diagnosis: selector-broken. Immediate. No guessing.

playwright-cli screenshot
  → Visual confirmation
  → Cost: ~40 tokens

─── Phase 4: Read only the failing test block ───────────────────
  → ~280 tokens (one test block, not the whole file)

─── Phase 5: Fix proposal ───────────────────────────────────────
  → Agent writes the corrected selector
  → Cost: ~150 tokens

─── Phase 6: Verify fix ────────────────────────────────────────
npx playwright test --grep "should add a new habit" --reporter=json
  → 1 passed ✅
  → Cost: ~80 tokens

─── Phase 7: Regression (smoke now clean) ──────────────────────
npx playwright test --grep "@regression" --reporter=json
  → 14 passed ✅
  → Cost: ~900 tokens

─────────────────────────────────────────────────────────────────
TOTAL: ~2,130 tokens
Full investigation. Real diagnosis. Verified fix. Regression confirmed.

Naive approach (same outcome, no playwright-cli):  ~18,000–22,000 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Debugging Difference
&lt;/h2&gt;

&lt;p&gt;Most Playwright engineers know &lt;code&gt;--debug&lt;/code&gt; mode. Here's why it doesn't work for agents and why playwright-cli exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test --debug
  → Interactive REPL
  → Pauses at each step, waits for human input
  → Requires a human sitting at the keyboard
  → Useless in CI, useless for autonomous agents

playwright-cli
  → Atomic commands, each returns structured output
  → No human needed between steps
  → Agent calls command → reads output → decides next command → loops
  → Works headlessly in CI, works autonomously in Claude Code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mental model shift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Human debugging:
  open browser → look → click → look → understand

Agent with playwright-cli:
  snapshot → reason → click → snapshot → reason → fix

Agent without playwright-cli:
  read file → read file → read file → guess → run test → read output → guess again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent without playwright-cli is flying blind. It's doing archaeology — piecing together what the live UI looks like from source files that may be outdated. playwright-cli gives the agent real-time ground truth.&lt;/p&gt;




&lt;h2&gt;
  
  
  What playwright-cli Commands Actually Look Like In Practice
&lt;/h2&gt;

&lt;p&gt;Here's a real triage session my agent ran on a broken selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Test failure: "locator('.habit-submit-btn') timed out"&lt;/span&gt;

playwright-cli open https://habit-tracker-one-dun-53.vercel.app/
&lt;span class="c"&gt;# Agent: app loaded, ready to investigate&lt;/span&gt;

playwright-cli snapshot
&lt;span class="c"&gt;# Agent reads:&lt;/span&gt;
&lt;span class="c"&gt;#   e45  button.submit-habit-form  (button, "Add Habit")&lt;/span&gt;
&lt;span class="c"&gt;# Agent: class is "submit-habit-form" not "habit-submit-btn"&lt;/span&gt;
&lt;span class="c"&gt;# Diagnosis: selector-broken, CSS class renamed in a recent deploy&lt;/span&gt;

playwright-cli fill e12 &lt;span class="s2"&gt;"Morning Run"&lt;/span&gt;
playwright-cli snapshot
&lt;span class="c"&gt;# Agent: input filled correctly, no issues with the input selector&lt;/span&gt;

playwright-cli click e45
playwright-cli snapshot
&lt;span class="c"&gt;# Agent: habit appeared in the list, the button works&lt;/span&gt;
&lt;span class="c"&gt;# Confirmation: it's purely a selector name issue, app logic is fine&lt;/span&gt;

playwright-cli screenshot
&lt;span class="c"&gt;# Agent: captured visual proof of the working flow with correct selector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total playwright-cli token cost for this investigation: ~420 tokens.&lt;br&gt;
Time: under 10 seconds.&lt;br&gt;
Outcome: exact diagnosis, zero guessing, confirmed fix path.&lt;/p&gt;


&lt;h2&gt;
  
  
  The Agent Files That Use playwright-cli
&lt;/h2&gt;

&lt;p&gt;I run all of this through Claude Code sub-agents defined in &lt;code&gt;.claude/agents/&lt;/code&gt;. Here are the two most important ones for the playwright-cli story.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;.claude/agents/qa-smoke-runner.md&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qa-smoke-runner&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Runs @smoke Playwright tests against the Habit Tracker and&lt;/span&gt;
  &lt;span class="s"&gt;triages failures using playwright-cli. Use proactively when code changes&lt;/span&gt;
  &lt;span class="s"&gt;are made or when asked to verify the app works.&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bash, Read, Glob&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are a QA smoke testing agent. Follow these steps exactly.

&lt;span class="gu"&gt;## Step 1 — Run smoke tests (scoped, JSON reporter only)&lt;/span&gt;
npx playwright test --grep "@smoke" --reporter=json 2&amp;gt;/dev/null

Parse the JSON. Extract: passed/failed counts, error messages per failure.

&lt;span class="gu"&gt;## Step 2 — If all pass&lt;/span&gt;
Output summary and stop. Never run regression from this agent.

&lt;span class="gu"&gt;## Step 3 — For each failure, triage with playwright-cli&lt;/span&gt;

DO NOT read source files first. Look at the live app first.
&lt;span class="p"&gt;
1.&lt;/span&gt; playwright-cli open https://habit-tracker-one-dun-53.vercel.app/
&lt;span class="p"&gt;2.&lt;/span&gt; playwright-cli snapshot
   → Read the element refs. Compare against the error message.
   → Can you already see the problem? (renamed class, missing element?)
&lt;span class="p"&gt;
3.&lt;/span&gt; Reproduce the failing steps:
   playwright-cli fill &lt;span class="nt"&gt;&amp;lt;ref&amp;gt;&lt;/span&gt; "test value"
   playwright-cli click &lt;span class="nt"&gt;&amp;lt;ref&amp;gt;&lt;/span&gt;
   playwright-cli snapshot    ← after every interaction
   playwright-cli screenshot  ← at the failure point
&lt;span class="p"&gt;
4.&lt;/span&gt; Only THEN read the failing test block if needed for assertion details.
   Read ONLY that test block. Not the whole file.
&lt;span class="p"&gt;
5.&lt;/span&gt; Categorise:
&lt;span class="p"&gt;   -&lt;/span&gt; selector-broken  → element exists, selector is wrong
&lt;span class="p"&gt;   -&lt;/span&gt; timing           → element not ready when test interacts
&lt;span class="p"&gt;   -&lt;/span&gt; real-bug         → app behaviour is actually wrong
&lt;span class="p"&gt;   -&lt;/span&gt; env-issue        → Vercel/network problem
&lt;span class="p"&gt;   -&lt;/span&gt; test-data        → hardcoded data assumption wrong

&lt;span class="gu"&gt;## Token rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; --reporter=json always. Never --reporter=html.
&lt;span class="p"&gt;-&lt;/span&gt; playwright-cli snapshot before reading any source file.
&lt;span class="p"&gt;-&lt;/span&gt; --grep "@smoke" always. Never run full suite.
&lt;span class="p"&gt;-&lt;/span&gt; Read only failing test block, not full spec.

&lt;span class="gu"&gt;## Output&lt;/span&gt;
Result: X passed / Y failed
[Per failure: category, root cause, playwright-cli commands used, fix]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.claude/agents/qa-failure-triager.md&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;qa-failure-triager&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deep-dives into a specific Playwright test failure using&lt;/span&gt;
  &lt;span class="s"&gt;playwright-cli to reproduce and diagnose the exact root cause. Use&lt;/span&gt;
  &lt;span class="s"&gt;when you need more than a summary — you need the exact fix.&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Bash, Read, Glob&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are a QA triage specialist. Your primary tool is playwright-cli.
Read source files only as a last resort.

&lt;span class="gu"&gt;## Investigation order (follow exactly)&lt;/span&gt;

&lt;span class="gu"&gt;### 1. Look at the live app first&lt;/span&gt;
playwright-cli open https://habit-tracker-one-dun-53.vercel.app/
playwright-cli snapshot

Before reading any code, answer from the snapshot:
&lt;span class="p"&gt;-&lt;/span&gt; Does the element the test expects actually exist?
&lt;span class="p"&gt;-&lt;/span&gt; Is its selector/class what the test expects?
&lt;span class="p"&gt;-&lt;/span&gt; Is the page in the right state?

&lt;span class="gu"&gt;### 2. Reproduce the failure step by step&lt;/span&gt;
For each action in the failing test, execute it with playwright-cli:

playwright-cli snapshot                    # document initial state
playwright-cli fill &lt;span class="nt"&gt;&amp;lt;ref&amp;gt;&lt;/span&gt; "test value"     # replicate test actions
playwright-cli snapshot                    # observe state change
playwright-cli click &lt;span class="nt"&gt;&amp;lt;ref&amp;gt;&lt;/span&gt;
playwright-cli snapshot                    # observe result
playwright-cli screenshot                  # capture at failure point

&lt;span class="gu"&gt;### 3. Check deeper only if needed&lt;/span&gt;
playwright-cli network   # if failure could be API/data related
playwright-cli console   # if failure could be a JS runtime error

&lt;span class="gu"&gt;### 4. Read failing test code last&lt;/span&gt;
Only after playwright-cli investigation. Read only the failing test
block — find it by line number from the JSON reporter output.

&lt;span class="gu"&gt;### 5. Write the exact fix&lt;/span&gt;
// Before (broken)
await page.locator('.old-class').click()

// After (fixed — prefer role-based selectors)
await page.getByRole('button', { name: 'Add Habit' }).click()

&lt;span class="gu"&gt;### 6. Verify&lt;/span&gt;
npx playwright test --grep "[exact test name]" --reporter=json

&lt;span class="gu"&gt;## Output&lt;/span&gt;
Category: [category]
Divergence point: [step where expectation broke]
playwright-cli commands used: [list]
Root cause: [2-3 sentences]
Fix: [exact before/after code]
Verified: ✅ / ❌
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The &lt;code&gt;--reporter=json&lt;/code&gt; Detail Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;One more token efficiency lever that pairs with playwright-cli: always use &lt;code&gt;--reporter=json&lt;/code&gt; when running tests for an agent to consume.&lt;/p&gt;

&lt;p&gt;Here's the difference in what an agent has to process:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List reporter (default):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;Running 6 tests using 1 worker

  ✓  1 [chromium] › add-habit-form.spec.js:12:5 › @smoke should add a new habit (1.2s)
  ✓  2 [chromium] › add-habit-form.spec.js:28:5 › @smoke should mark habit complete (0.8s)
  ✗  3 [chromium] › add-habit-form.spec.js:45:5 › @smoke should delete a habit (5.0s)

  Error: locator('.delete-habit-btn') - waiting for locator
    at add-habit-form.spec.js:52
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks clean to a human. For an agent, it's unstructured text it has to parse with pattern matching — brittle and expensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON reporter:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"expected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"unexpected"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"duration"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7243&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"suites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add-habit-form.spec.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"specs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@smoke should delete a habit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ok"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"results"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"locator('.delete-habit-btn') exceeded timeout"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"add-habit-form.spec.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"line"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Structured. Parseable. The agent extracts exactly what it needs in one pass: which test, what error, which file, which line. No pattern matching, no ambiguity, fewer tokens wasted on parsing overhead.&lt;/p&gt;

&lt;p&gt;Combined with &lt;code&gt;--grep&lt;/code&gt; to scope the run, you get the smallest possible output surface for an agent to work with.&lt;/p&gt;




&lt;h2&gt;
  
  
  When playwright-cli Isn't Enough: The MCP Server
&lt;/h2&gt;

&lt;p&gt;playwright-cli is stateless — each command opens the browser, acts, closes. For most triage tasks that's fine. But some investigations need state across multiple steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify a habit persists after page refresh&lt;/li&gt;
&lt;li&gt;Check that streak counts update correctly after adding habits on consecutive days&lt;/li&gt;
&lt;li&gt;Explore an entire multi-page flow without re-navigating from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For those cases, the Playwright MCP server keeps a browser alive across the entire agent session. Think of it as a playwright-cli session that doesn't close between commands.&lt;/p&gt;

&lt;p&gt;For my habit tracker I use it in the &lt;code&gt;qa-test-generator&lt;/code&gt; agent — the one that explores the live app and generates new test coverage. It needs to navigate multiple routes, build up app state, and compare across page transitions. playwright-cli per-command statelessness would make that awkward. The MCP server handles it naturally.&lt;/p&gt;

&lt;p&gt;But — and this is important — don't reach for the MCP server by default. playwright-cli covers 90% of QA agent use cases with lower overhead. Use the MCP server specifically when your investigation needs persistent browser state.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model, Simplified
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test    =  your test runner
                          answers: WHAT broke?
                          cost: low (with --grep + --reporter=json)

playwright-cli         =  your agent's eyes on the live app  
                          answers: WHY did it break?
                          cost: very low (snapshot is ~150 tokens)

playwright MCP server  =  your agent's stateful browser session
                          answers: WHAT ELSE might be broken?
                          cost: low, but only use when you need state
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shift is this: you stop reading files to reconstruct what the UI looks like. You just look at it. That's what playwright-cli makes possible for agents — and it's why the token difference is so dramatic.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference: Token Cost By Command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npx playwright test --list                          ~80 tokens
npx playwright test --grep "@smoke" --reporter=json ~400 tokens
npx playwright test --reporter=json (full suite)    ~3,000 tokens

playwright-cli open &amp;lt;url&amp;gt;                           ~30 tokens
playwright-cli snapshot                             ~150 tokens
playwright-cli click &amp;lt;ref&amp;gt;                          ~20 tokens
playwright-cli screenshot                           ~40 tokens
playwright-cli network                              ~200 tokens

Reading entire spec file                            ~3,200 tokens
Reading one test block                              ~280 tokens
Reading playwright.config.js                        ~180 tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern is clear. Every playwright-cli command costs less than reading a single source file. And it gives you ground truth about the live app rather than a static snapshot of code that may not reflect the current deployed state.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;--grep&lt;/code&gt; from day one.&lt;/strong&gt; My agents initially ran the full suite every time. Scoping to &lt;code&gt;@smoke&lt;/code&gt; for quick checks cut token usage by 6x immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;playwright-cli snapshot&lt;/code&gt; before any file read.&lt;/strong&gt; The answer is usually in the snapshot. Only go to source files when you need to understand assertion logic, not when you need to understand the DOM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role-based selectors over CSS classes.&lt;/strong&gt; CSS class names change with deploys — &lt;code&gt;getByRole('button', { name: 'Add Habit' })&lt;/code&gt; stays stable. It also makes playwright-cli triage easier because role and name are visible in the snapshot output.&lt;/p&gt;




&lt;h2&gt;
  
  
  The GitHub Repo
&lt;/h2&gt;

&lt;p&gt;All the agent files, CLAUDE.md, and the full test suite are here:&lt;/p&gt;

&lt;p&gt;👉 *&lt;em&gt;&lt;a href="https://github.com/anirseven/habit-tracker/tree/main/.claude/agents" rel="noopener noreferrer"&gt;https://github.com/anirseven/habit-tracker/tree/main/.claude/agents&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;If you're building something similar or have questions, drop a comment. Happy to go deeper on any part of this.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>claudecode</category>
      <category>playwrightcli</category>
    </item>
    <item>
      <title>Building a Full-Stack Habit Tracker with Claude Code - Part 2: Polish, Testing &amp; Deployment</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Mon, 04 May 2026 06:41:45 +0000</pubDate>
      <link>https://forem.com/anirseven/building-a-full-stack-habit-tracker-with-claude-code-part-2-polish-testing-deployment-e7b</link>
      <guid>https://forem.com/anirseven/building-a-full-stack-habit-tracker-with-claude-code-part-2-polish-testing-deployment-e7b</guid>
      <description>&lt;h2&gt;
  
  
  Building a Full-Stack Habit Tracker with Claude Code - Part 2: Polish, Testing &amp;amp; Deployment
&lt;/h2&gt;

&lt;p&gt;Taking the habit tracker from MVP to production-ready with categories, analytics, comprehensive testing, and Vercel deployment&lt;/p&gt;

&lt;h2&gt;
  
  
  Welcome Back!
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/anirseven/building-a-react-habit-tracker-with-claude-code-part-1-from-idea-to-mvp-1cgg"&gt;[Part 1]&lt;/a&gt;, we built a fully functional habit tracker MVP in about 6-8 hours using Claude Code as our AI pair programmer. We had:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Basic CRUD operations for habits&lt;/li&gt;
&lt;li&gt;✅ Date-based tracking&lt;/li&gt;
&lt;li&gt;✅ Dark/light theme toggle&lt;/li&gt;
&lt;li&gt;✅ localStorage persistence&lt;/li&gt;
&lt;li&gt;✅ Simple analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So continuing my learning journey with Claude as I will be covering below topics and how I used claude to plan and work on them&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improving the Site&lt;/strong&gt; An 8-category organization system&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving the Site&lt;/strong&gt; Real-time summary cards with progress bars&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving the Site&lt;/strong&gt; Advanced analytics with streak tracking&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improving the Site&lt;/strong&gt; Smart category filtering&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing of the App using&lt;/strong&gt; Playwright 170+ automated test cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment of the app&lt;/strong&gt;  Production deployment to Vercel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's ship this! 🚢&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 9: The Category System (Organization FTW)
&lt;/h2&gt;

&lt;p&gt;After using the app for a few days, I realized tracking "Morning workout" and "Finish quarterly report" in the same list felt... wrong. They're completely different types of habits.&lt;/p&gt;

&lt;p&gt;I asked Claude: &lt;strong&gt;"Help me add a category system to organize habits better."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Define the Categories
&lt;/h3&gt;

&lt;p&gt;Claude suggested creating a constants file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/constants/categories.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Health &amp;amp; Fitness&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;💪&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;work&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Work &amp;amp; Productivity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;💼&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;learning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Learning &amp;amp; Growth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;📚&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;personal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Personal Care&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🧘&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;social&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Social &amp;amp; Family&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👥&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;finance&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Finance &amp;amp; Money&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;💰&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hobbies&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hobbies &amp;amp; Fun&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;🎨&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;other&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Other&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;📌&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this approach?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralized: One source of truth&lt;/li&gt;
&lt;li&gt;Extensible: Easy to add more categories later&lt;/li&gt;
&lt;li&gt;Type-safe: Could convert to TypeScript easily&lt;/li&gt;
&lt;li&gt;Visual: Emojis make categories instantly recognizable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Category Selector Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/CategorySelector.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../constants/categories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CategorySelector&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="nx"&gt;htmlFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;
        &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;category&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-select&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;option&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/option&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;))}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/select&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;CategorySelector&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Update the Data Model
&lt;/h3&gt;

&lt;p&gt;Our habit objects now include category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1712345678901&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Morning workout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-04-08&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Health &amp;amp; Fitness&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// NEW!&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Category Filtering on Home Page
&lt;/h3&gt;

&lt;p&gt;Here's where it got fun. I wanted users to filter habits by category:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/pages/Home.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CATEGORIES&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../constants/categories&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Home&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onAddHabit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onUpdateHabit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onDeleteHabit&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedCategory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSelectedCategory&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Filter habits based on selected category&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;selectedCategory&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;selectedCategory&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;home-page&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Add Habit Form */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HabitForm&lt;/span&gt; &lt;span class="nx"&gt;onAddHabit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onAddHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Category Filter Buttons */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;category-filter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Filter&lt;/span&gt; &lt;span class="nx"&gt;by&lt;/span&gt; &lt;span class="nx"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;filter-buttons&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
            &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;selectedCategory&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setSelectedCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;All&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="err"&gt;🌐&lt;/span&gt; &lt;span class="nx"&gt;All&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;CATEGORIES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;
              &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;selectedCategory&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setSelectedCategory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;emoji&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="p"&gt;))}&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Habit List (now filtered) */&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HabitList&lt;/span&gt;
        &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;filteredHabits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onEdit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onUpdateHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onDelete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onDeleteHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The UX touches that made it feel polished:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Active button gets a different background color&lt;/li&gt;
&lt;li&gt;Smooth transitions between filter states&lt;/li&gt;
&lt;li&gt;Count updates automatically when filtering&lt;/li&gt;
&lt;li&gt;Empty state shows helpful message&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #6:&lt;/strong&gt; Small UX touches (active states, smooth transitions) make a huge difference in perceived quality.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 10: Summary Cards (At-a-Glance Progress)
&lt;/h2&gt;

&lt;p&gt;I wanted users to see their progress &lt;strong&gt;immediately&lt;/strong&gt; when opening the app. Claude helped me create a beautiful summary system.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Vision
&lt;/h3&gt;

&lt;p&gt;Four cards showing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;📅 Today's Progress (completed/total)&lt;/li&gt;
&lt;li&gt;📊 This Week's completion rate&lt;/li&gt;
&lt;li&gt;🔥 Active streaks count&lt;/li&gt;
&lt;li&gt;🎯 Total unique habits&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 1: Analytics Utility
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/homeSummaryAnalytics.js&lt;/span&gt;

&lt;span class="c1"&gt;// Get today's habits and completion&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTodayProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todayHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;todayHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;todayHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;todayHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;todayHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Get this week's completion rate&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getWeekProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weekAgo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weekHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;habitDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;habitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="nx"&gt;weekAgo&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;habitDate&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;weekHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;weekHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;weekHabits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Count habits with active streaks&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getActiveStreaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Group habits by name&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grouped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;groupHabitsByName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Count how many have current streaks &amp;gt; 0&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;activeStreaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;habitName&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;grouped&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateCurrentStreak&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grouped&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;habitName&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streak&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;activeStreaks&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;activeStreaks&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Count unique habit names&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getTotalUniqueHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uniqueNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;uniqueNames&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: StatCard Component
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/StatCard.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;StatCard&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat-card&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat-icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat-content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat-value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stat-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Progress Bar Component
&lt;/h3&gt;

&lt;p&gt;For today's progress, I wanted a visual bar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/ProgressBar.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ProgressBar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;progress-bar-container&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;
        &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;progress-bar-fill&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;progress-percentage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;%&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;ProgressBar&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With some beautiful CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/styles/progress-bar.css */&lt;/span&gt;
&lt;span class="nc"&gt;.progress-bar-container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;24px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bg-secondary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;hidden&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.progress-bar-fill&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;linear-gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;90deg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#4a90e2&lt;/span&gt; &lt;span class="m"&gt;0%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;#63b3ed&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;align-items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;justify-content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;center&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="m"&gt;0.5s&lt;/span&gt; &lt;span class="n"&gt;ease&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.progress-percentage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Putting It All Together
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/SummaryCards.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;StatCard&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./StatCard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ProgressBar&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ProgressBar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;getTodayProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getWeekProgress&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getActiveStreaks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;getTotalUniqueHabits&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;../utils/homeSummaryAnalytics&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;SummaryCards&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;todayProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTodayProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;weekProgress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWeekProgress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeStreaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getActiveStreaks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTotalUniqueHabits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;summary-cards&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatCard&lt;/span&gt;
        &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;📅&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todayProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;todayProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Today's Progress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProgressBar&lt;/span&gt; &lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;todayProgress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;percentage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/StatCard&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatCard&lt;/span&gt;
        &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;📊&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;weekProgress&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This Week&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatCard&lt;/span&gt;
        &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🔥&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;activeStreaks&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Active Streaks&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatCard&lt;/span&gt;
        &lt;span class="nx"&gt;icon&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🎯&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;totalHabits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Total Habits&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Result:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Users now see their progress the instant they open the app. No scrolling, no clicking - just &lt;strong&gt;immediate feedback&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #7:&lt;/strong&gt; Summary metrics should answer the question "How am I doing?" in under 2 seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 11: Restructuring for Deployment
&lt;/h2&gt;

&lt;p&gt;After building all these features, my project structure was getting messy. Claude suggested reorganizing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Before: Everything in a subdirectory&lt;/span&gt;
habit-tracker/
  habit-tracker/
    src/
    public/
    package.json
  README.md

&lt;span class="c"&gt;# After: Clean root structure&lt;/span&gt;
habit-tracker/
  src/
  public/
  package.json
  README.md
  vercel.json    &lt;span class="c"&gt;# NEW!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Deployment platforms like Vercel expect your project at the repository root.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The migration:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Move everything up one level&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;habit-tracker/&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;habit-tracker/.gitignore &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;rmdir &lt;/span&gt;habit-tracker

&lt;span class="c"&gt;# Commit the restructure&lt;/span&gt;
git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Restructure repository for easier deployment"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 Learning #8:&lt;/strong&gt; Plan your folder structure with deployment in mind from day one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 12: Deploying to Vercel
&lt;/h2&gt;

&lt;p&gt;Time to ship! 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create vercel.json
&lt;/h3&gt;

&lt;p&gt;This crucial file tells Vercel how to handle React Router:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"rewrites"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/(.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"destination"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/index.html"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why is this needed?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Single-page apps (SPAs) need all routes to point to &lt;code&gt;index.html&lt;/code&gt; so React Router can handle them. Without this, direct navigation to &lt;code&gt;/trends&lt;/code&gt; would 404.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Deploy via Vercel CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Vercel CLI&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; vercel

&lt;span class="c"&gt;# Login to Vercel&lt;/span&gt;
vercel login

&lt;span class="c"&gt;# Deploy!&lt;/span&gt;
vercel

&lt;span class="c"&gt;# Follow the prompts:&lt;/span&gt;
&lt;span class="c"&gt;# - Set up new project? Yes&lt;/span&gt;
&lt;span class="c"&gt;# - Link to existing project? No&lt;/span&gt;
&lt;span class="c"&gt;# - Project name: habit-tracker&lt;/span&gt;
&lt;span class="c"&gt;# - Which directory? ./&lt;/span&gt;
&lt;span class="c"&gt;# - Override settings? No&lt;/span&gt;

&lt;span class="c"&gt;# Deploy to production&lt;/span&gt;
vercel &lt;span class="nt"&gt;--prod&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;30 seconds later&lt;/strong&gt;: My app was live at &lt;code&gt;habit-tracker.vercel.app&lt;/code&gt; 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure Auto-Deploy
&lt;/h3&gt;

&lt;p&gt;Connected my GitHub repo to Vercel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Went to vercel.com dashboard&lt;/li&gt;
&lt;li&gt;Connected GitHub account&lt;/li&gt;
&lt;li&gt;Imported the repository&lt;/li&gt;
&lt;li&gt;Set build settings:

&lt;ul&gt;
&lt;li&gt;Build Command: &lt;code&gt;npm run build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Output Directory: &lt;code&gt;build&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install Command: &lt;code&gt;npm install&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now every push to &lt;code&gt;main&lt;/code&gt; automatically deploys. Every PR gets a preview URL. Magic! ✨&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #9:&lt;/strong&gt; Vercel's free tier is ridiculously good for React apps. No excuses not to ship!&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 13: Comprehensive Testing with Playwright MCP
&lt;/h2&gt;

&lt;p&gt;Here's where things got really interesting. I wanted to make sure the app actually &lt;strong&gt;worked&lt;/strong&gt; before sharing it with the world.&lt;/p&gt;

&lt;p&gt;I asked Claude: &lt;strong&gt;"Help me create a comprehensive testing strategy for this app."&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Playwright MCP?
&lt;/h3&gt;

&lt;p&gt;Playwright is a browser automation tool, and MCP (Model Context Protocol) integrates it directly with Claude Code. This means Claude can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Navigate the actual running app in a browser&lt;/li&gt;
&lt;li&gt;Take screenshots and accessibility snapshots&lt;/li&gt;
&lt;li&gt;Interact with UI elements&lt;/li&gt;
&lt;li&gt;Capture console logs and network requests&lt;/li&gt;
&lt;li&gt;Generate detailed test reports&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Testing Process
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start the dev server&lt;/span&gt;
npm start
&lt;span class="c"&gt;# App running at http://localhost:3001&lt;/span&gt;

&lt;span class="c"&gt;# Claude Code connects via Playwright MCP&lt;/span&gt;
&lt;span class="c"&gt;# Opens browser, starts analyzing...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude methodically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;✅ Navigated to every page&lt;/li&gt;
&lt;li&gt;✅ Tested every form input&lt;/li&gt;
&lt;li&gt;✅ Checked all filters&lt;/li&gt;
&lt;li&gt;✅ Verified localStorage persistence&lt;/li&gt;
&lt;li&gt;✅ Tested theme switching&lt;/li&gt;
&lt;li&gt;✅ Validated responsive design&lt;/li&gt;
&lt;li&gt;✅ Checked accessibility&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Generated Test Report
&lt;/h3&gt;

&lt;p&gt;Claude produced &lt;code&gt;UI_TEST_REPORT.md&lt;/code&gt; - a &lt;strong&gt;2,270-line&lt;/strong&gt; comprehensive test plan documenting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;170+ test cases&lt;/strong&gt; across 14 functional areas&lt;/li&gt;
&lt;li&gt;Priority levels (High/Medium/Low)&lt;/li&gt;
&lt;li&gt;Detailed test steps and expected results&lt;/li&gt;
&lt;li&gt;Recommended testing strategies&lt;/li&gt;
&lt;li&gt;Automation guidelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's a sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gs"&gt;**TC-FORM-013:**&lt;/span&gt; Verify habit is added when form is submitted with valid data
&lt;span class="gs"&gt;**Priority:**&lt;/span&gt; High | &lt;span class="gs"&gt;**Type:**&lt;/span&gt; Functional | &lt;span class="gs"&gt;**Automation:**&lt;/span&gt; Yes

&lt;span class="gs"&gt;**Steps:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Enter "Morning workout" in habit field
&lt;span class="p"&gt;2.&lt;/span&gt; Select category "Health &amp;amp; Fitness"
&lt;span class="p"&gt;3.&lt;/span&gt; Select status "Completed"
&lt;span class="p"&gt;4.&lt;/span&gt; Click "Add Habit"

&lt;span class="gs"&gt;**Expected Result:**&lt;/span&gt; Habit is created and appears in habit list
&lt;span class="p"&gt;
---
&lt;/span&gt;
&lt;span class="gs"&gt;**TC-STORAGE-003:**&lt;/span&gt; Verify habits persist after page refresh
&lt;span class="gs"&gt;**Priority:**&lt;/span&gt; High | &lt;span class="gs"&gt;**Type:**&lt;/span&gt; Integration | &lt;span class="gs"&gt;**Automation:**&lt;/span&gt; Yes

&lt;span class="gs"&gt;**Steps:**&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; Add multiple habits
&lt;span class="p"&gt;2.&lt;/span&gt; Press F5 to refresh
&lt;span class="p"&gt;3.&lt;/span&gt; Verify all habits still appear

&lt;span class="gs"&gt;**Expected Result:**&lt;/span&gt; Habits remain after refresh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test Coverage Summary
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature Area&lt;/th&gt;
&lt;th&gt;Test Cases&lt;/th&gt;
&lt;th&gt;Priority&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Navigation&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Theme Toggle&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add Habit Form&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Habit List Display&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Summary Cards&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Category Filter&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edit Habit&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete Habit&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LocalStorage&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trends Page&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accessibility&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Integration/E2E&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Total: 170+ test cases&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Generated Artifacts
&lt;/h3&gt;

&lt;p&gt;Claude created a &lt;code&gt;.playwright-mcp/&lt;/code&gt; directory with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Page snapshots (accessibility trees)&lt;/li&gt;
&lt;li&gt;Console log captures&lt;/li&gt;
&lt;li&gt;Network request logs&lt;/li&gt;
&lt;li&gt;Screenshots at various breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #10:&lt;/strong&gt; AI-powered testing can generate comprehensive test plans faster than any human.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 14: Documentation
&lt;/h2&gt;

&lt;p&gt;The final piece: making the project maintainable and shareable.&lt;/p&gt;

&lt;h3&gt;
  
  
  README.md
&lt;/h3&gt;

&lt;p&gt;Claude helped me create a comprehensive README covering:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Features overview&lt;/strong&gt; - What the app does&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tech stack&lt;/strong&gt; - What it's built with&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Development journey&lt;/strong&gt; - How we built it (perfect for blog posts!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting started&lt;/strong&gt; - Installation and running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment guide&lt;/strong&gt; - How to ship it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing strategy&lt;/strong&gt; - How to validate it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project structure&lt;/strong&gt; - Where everything lives&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Future enhancements&lt;/strong&gt; - What's next&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;I also created project-specific instructions for future Claude Code sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# CLAUDE.md&lt;/span&gt;

This file provides guidance to Claude Code when working with this repository.

&lt;span class="gu"&gt;## Project Structure&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; React 19 with Create React App
&lt;span class="p"&gt;-&lt;/span&gt; Client-side routing with React Router DOM
&lt;span class="p"&gt;-&lt;/span&gt; localStorage for persistence (no backend)
&lt;span class="p"&gt;-&lt;/span&gt; CSS custom properties for theming

&lt;span class="gu"&gt;## Key Conventions&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Components use functional components with hooks
&lt;span class="p"&gt;-&lt;/span&gt; Styles use CSS modules, NOT Tailwind or styled-components
&lt;span class="p"&gt;-&lt;/span&gt; Date format: ISO (YYYY-MM-DD)
&lt;span class="p"&gt;-&lt;/span&gt; Always update Navigation.js when adding new pages

&lt;span class="gu"&gt;## Testing&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; 170+ test cases documented in UI_TEST_REPORT.md
&lt;span class="p"&gt;-&lt;/span&gt; Use React Testing Library for unit tests
&lt;span class="p"&gt;-&lt;/span&gt; Playwright for E2E tests

&lt;span class="gu"&gt;## Deployment&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Vercel auto-deploys from main branch
&lt;span class="p"&gt;-&lt;/span&gt; vercel.json is required for React Router support
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; Future Claude sessions (or human developers!) can understand the project instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #11:&lt;/strong&gt; Good documentation is a gift to your future self.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Final Stats
&lt;/h2&gt;

&lt;p&gt;After &lt;strong&gt;two days&lt;/strong&gt; of development with Claude Code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What We Built:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Full-featured habit tracker&lt;/li&gt;
&lt;li&gt;✅ 8-category organization system&lt;/li&gt;
&lt;li&gt;✅ Real-time analytics and trends&lt;/li&gt;
&lt;li&gt;✅ Summary cards with progress bars&lt;/li&gt;
&lt;li&gt;✅ Category filtering&lt;/li&gt;
&lt;li&gt;✅ Dark/light themes&lt;/li&gt;
&lt;li&gt;✅ 100% responsive design&lt;/li&gt;
&lt;li&gt;✅ localStorage persistence&lt;/li&gt;
&lt;li&gt;✅ Production deployment&lt;/li&gt;
&lt;li&gt;✅ 170+ documented test cases&lt;/li&gt;
&lt;li&gt;✅ Comprehensive documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Numbers:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lines of Code:&lt;/strong&gt; ~3,500&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Components:&lt;/strong&gt; 15&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pages:&lt;/strong&gt; 3&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Cases Documented:&lt;/strong&gt; 170+&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Files Created:&lt;/strong&gt; 47&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Spent:&lt;/strong&gt; ~12-15 hours total&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Saved vs Solo:&lt;/strong&gt; Estimated 70-80%&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Repository Structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;habitTracker/
├── .playwright-mcp/          # Test artifacts
├── public/                   # Static assets
├── src/
│   ├── components/          # 15 reusable components
│   ├── constants/           # Category definitions
│   ├── hooks/               # Custom hooks (theme)
│   ├── pages/               # 3 route pages
│   ├── styles/              # 4 CSS files
│   └── utils/               # Analytics functions
├── CLAUDE.md                # AI instructions
├── README.md                # Comprehensive docs
├── UI_TEST_REPORT.md        # 170+ test cases
├── blog-part-1.md           # This blog post!
├── blog-part-2.md           # This blog post!
└── vercel.json              # Deployment config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Takeaways: Working with Claude Code
&lt;/h2&gt;

&lt;p&gt;After building this entire app with AI pair programming, here are my insights:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ What Claude Code Excelled At
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Boilerplate Elimination&lt;/strong&gt;: No more typing &lt;code&gt;import React from 'react'&lt;/code&gt; a hundred times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture Suggestions&lt;/strong&gt;: Claude's patterns were consistently better than my first instincts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehensive Testing&lt;/strong&gt;: Generated 170+ test cases I wouldn't have thought of&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt;: Created better docs than I would have written alone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning Tool&lt;/strong&gt;: Every suggestion came with explanations&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  ⚠️ Where I Still Had to Think
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Product Decisions&lt;/strong&gt;: What features to build and when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UX Design&lt;/strong&gt;: The "feel" of the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Review&lt;/strong&gt;: Reading and validating everything&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture Strategy&lt;/strong&gt;: High-level decisions about structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debugging Edge Cases&lt;/strong&gt;: Some bugs required human intuition&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  🎯 The Optimal Workflow
&lt;/h3&gt;

&lt;p&gt;I found this rhythm worked best:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Describe what I want ("Add a progress bar to today's stats")
2. Let Claude write initial implementation
3. Review and test the code
4. Ask questions about anything unclear
5. Request refinements ("Make the animation smoother")
6. Validate the final result
7. Move to next feature
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time ratio:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30% describing what I want&lt;/li&gt;
&lt;li&gt;10% waiting for Claude to write code&lt;/li&gt;
&lt;li&gt;40% reviewing and testing&lt;/li&gt;
&lt;li&gt;20% refining and polishing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real Talk: Is AI Replacing Developers?
&lt;/h2&gt;

&lt;p&gt;What used to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;80% writing code&lt;/li&gt;
&lt;li&gt;20% thinking about architecture&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is becoming:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30% writing code&lt;/li&gt;
&lt;li&gt;70% thinking about architecture, UX, and product decisions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude Code didn't replace my job as a developer. It &lt;strong&gt;elevated&lt;/strong&gt; it. I spent more time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User experience decisions&lt;/li&gt;
&lt;li&gt;Feature prioritization&lt;/li&gt;
&lt;li&gt;Design polish&lt;/li&gt;
&lt;li&gt;Testing strategy&lt;/li&gt;
&lt;li&gt;Documentation quality&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And less time on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Typing boilerplate&lt;/li&gt;
&lt;li&gt;Looking up syntax&lt;/li&gt;
&lt;li&gt;Fighting with CSS&lt;/li&gt;
&lt;li&gt;Writing repetitive tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The result?&lt;/strong&gt; A better app, built faster, with more thorough testing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources &amp;amp; Links
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tools Used:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; - AI pair programming&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React&lt;/a&gt; - UI framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://reactrouter.com" rel="noopener noreferrer"&gt;React Router&lt;/a&gt; - Client-side routing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://vercel.com" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; - Deployment platform&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playwright.dev" rel="noopener noreferrer"&gt;Playwright&lt;/a&gt; - Browser automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Claude Code, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Built a production app in 2 days instead of 2 weeks&lt;/li&gt;
&lt;li&gt;✅ Learned new React patterns and best practices&lt;/li&gt;
&lt;li&gt;✅ Generated comprehensive testing documentation&lt;/li&gt;
&lt;li&gt;✅ Created better docs than I would have alone&lt;/li&gt;
&lt;li&gt;✅ Shipped a polished product I'm proud of&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But I also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Made every product decision&lt;/li&gt;
&lt;li&gt;✅ Designed the user experience&lt;/li&gt;
&lt;li&gt;✅ Reviewed every line of code&lt;/li&gt;
&lt;li&gt;✅ Tested everything thoroughly&lt;/li&gt;
&lt;li&gt;✅ Brought the creative vision to life&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;What did you think of this series?&lt;/strong&gt; Drop a comment below!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want to see more builds like this?&lt;/strong&gt; Follow me for future AI-powered projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions about the code or process?&lt;/strong&gt; Ask away - I'll respond to everyone!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Thank you for reading!&lt;/strong&gt; 🙏&lt;/p&gt;

&lt;p&gt;If you found this valuable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❤️ Leave a reaction&lt;/li&gt;
&lt;li&gt;💬 Share your thoughts in the comments&lt;/li&gt;
&lt;li&gt;🔖 Bookmark for later&lt;/li&gt;
&lt;li&gt;📱 Share with your dev friends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy coding! 💻&lt;/p&gt;

&lt;h1&gt;
  
  
  AI #React #WebDev #Testing #Deployment #ClaudeCode #ProductivityTools #Tutorial
&lt;/h1&gt;




&lt;p&gt;&lt;em&gt;Built with Claude Code, deployed on Vercel, documented for the community. This is the way.&lt;/em&gt; ✨&lt;/p&gt;

</description>
      <category>react</category>
      <category>ai</category>
      <category>testing</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Building a React Habit Tracker with Claude Code - Part 1: From Idea to MVP</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Sat, 25 Apr 2026 04:59:44 +0000</pubDate>
      <link>https://forem.com/anirseven/building-a-react-habit-tracker-with-claude-code-part-1-from-idea-to-mvp-1cgg</link>
      <guid>https://forem.com/anirseven/building-a-react-habit-tracker-with-claude-code-part-1-from-idea-to-mvp-1cgg</guid>
      <description>&lt;h1&gt;
  
  
  Building a Full-Stack Habit Tracker with Claude Code - Part 1: From Idea to MVP
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction: Why Another Habit Tracker?
&lt;/h2&gt;

&lt;p&gt;I started this a demo project to learn how can claude code help as an AI dev partner , I used this demo project to understand and learn different ways to effectively use Claude code and its feature&lt;/p&gt;

&lt;p&gt;Here are some important topics that I learned while making this project&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Claude.md file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;context window&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tools and permission&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Planning and Thinking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Slash Command &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Custom Slash command&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MCP Server&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sub Agents&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Follow along to see how I used this learning while creating this Habit Tracker app in React.&lt;/p&gt;

&lt;p&gt;Here is what the app does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🚀 &lt;strong&gt;Fast&lt;/strong&gt; - No login, no setup, just start tracking&lt;/li&gt;
&lt;li&gt;🎨 &lt;strong&gt;Beautiful&lt;/strong&gt; - A UI I'd actually want to use daily&lt;/li&gt;
&lt;li&gt;📊 &lt;strong&gt;Insightful&lt;/strong&gt; - Show me trends, not just checkboxes&lt;/li&gt;
&lt;li&gt;🔒 &lt;strong&gt;Private&lt;/strong&gt; - My data stays in my browser&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built this entire app &lt;strong&gt;with Claude Code as my AI pair programmer&lt;/strong&gt;. And I'm going to show you exactly how.&lt;/p&gt;

&lt;h2&gt;
  
  
  But first lets start with basics
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What is Claude Code?
&lt;/h2&gt;

&lt;p&gt;Before we dive in, let me explain what made this project possible. &lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; is Anthropic's official CLI tool that brings Claude AI directly into your development workflow. Think of it as an AI pair programmer that can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read and understand your entire codebase&lt;/li&gt;
&lt;li&gt;Write, edit, and refactor code&lt;/li&gt;
&lt;li&gt;Run terminal commands&lt;/li&gt;
&lt;li&gt;Debug issues&lt;/li&gt;
&lt;li&gt;Suggest architecture improvements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not just a chatbot - it's a full development partner.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game Plan
&lt;/h2&gt;

&lt;p&gt;Here's what I wanted to build:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Core Features (MVP):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Add, edit, and delete habits&lt;/li&gt;
&lt;li&gt;✅ Track habits by date&lt;/li&gt;
&lt;li&gt;✅ Four status types: Completed, To Do, Not Completed, Skipped&lt;/li&gt;
&lt;li&gt;✅ Data persistence with localStorage&lt;/li&gt;
&lt;li&gt;✅ Dark/light theme toggle&lt;/li&gt;
&lt;li&gt;✅ Basic analytics and trends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React (latest version)&lt;/li&gt;
&lt;li&gt;React Router for navigation&lt;/li&gt;
&lt;li&gt;Pure CSS (no frameworks - keeping it simple)&lt;/li&gt;
&lt;li&gt;localStorage for data persistence (no backend needed!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's build it! 🛠️&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 1: Project Setup (The Boring-But-Essential Part)
&lt;/h2&gt;

&lt;p&gt;I started by asking Claude Code to initialize a React project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app habit-tracker
&lt;span class="nb"&gt;cd &lt;/span&gt;habit-tracker
npm &lt;span class="nb"&gt;install &lt;/span&gt;react-router-dom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude suggested this project structure, which turned out to be perfect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/
├── components/       # Reusable UI components
├── pages/           # Route components (Home, Trends, About)
├── hooks/           # Custom React hooks
├── styles/          # CSS modules
└── utils/           # Helper functions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 Learning #1:&lt;/strong&gt; Having a clear structure from day one saves hours of refactoring later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 2: Building the Data Model
&lt;/h2&gt;

&lt;p&gt;The first real code we wrote together was the data model. I described what I wanted, and Claude helped me design a simple but extensible structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Each habit entry looks like this:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1712345678901&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Timestamp-based unique ID&lt;/span&gt;
  &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Morning workout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// What you're tracking&lt;/span&gt;
  &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-04-08&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;// ISO date format&lt;/span&gt;
  &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;            &lt;span class="c1"&gt;// Completed | To Do | Not Completed | Skipped&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clean, simple, and stored as a JSON array in localStorage. No over-engineering!&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 3: State Management Strategy
&lt;/h2&gt;

&lt;p&gt;Here's where Claude Code really shined. Instead of reaching for Redux or Zustand, it suggested a simple but powerful approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// App.js - The main component&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Load habits from localStorage on mount&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHabits&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habit-tracker-habits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Auto-save to localStorage whenever habits change&lt;/span&gt;
  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habit-tracker-habits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="c1"&gt;// ... rest of the app&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 Learning #2:&lt;/strong&gt; You don't always need a state management library. React hooks + localStorage can handle a lot!&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 4: The HabitForm Component
&lt;/h2&gt;

&lt;p&gt;This was our first real UI component. I asked Claude to create a form that felt intuitive, and here's what we built together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/HabitForm.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;TextInput&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./TextInput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;DateSelector&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./DateSelector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;StatusSelector&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./StatusSelector&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HabitForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onAddHabit&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setHabit&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;To Do&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleSubmit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Basic validation&lt;/span&gt;

    &lt;span class="nf"&gt;onAddHabit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;status&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// Clear form&lt;/span&gt;
    &lt;span class="nf"&gt;setHabit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;To Do&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;habit-form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;➕&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;New&lt;/span&gt; &lt;span class="nx"&gt;Habit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextInput&lt;/span&gt;
        &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Habit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setHabit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="nx"&gt;placeholder&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter habit (e.g., Morning workout, Read 30 mins)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;

      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form-row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;DateSelector&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StatusSelector&lt;/span&gt;
          &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
        &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;btn-primary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="err"&gt;➕&lt;/span&gt; &lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;Habit&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Design Decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Composition over monolith&lt;/strong&gt;: Breaking the form into smaller components (TextInput, DateSelector, StatusSelector) made it easier to maintain and reuse.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Emojis as icons&lt;/strong&gt;: Instead of icon libraries, we used emojis. Lightweight and universally supported!&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Immediate feedback&lt;/strong&gt;: The form clears immediately after submission, giving users a sense of accomplishment.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;💡 Learning #3:&lt;/strong&gt; Small, focused components are easier for AI (and humans!) to understand and modify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 5: Displaying Habits - The HabitList Component
&lt;/h2&gt;

&lt;p&gt;Now came the fun part - displaying habits in a beautiful, organized way. I wanted habits grouped by date, with special labels for "Today" and "Yesterday".&lt;/p&gt;

&lt;p&gt;Claude Code helped me build this smart grouping logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/HabitList.js&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HabitList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onEdit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onDelete&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Group habits by date&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupedHabits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;

  &lt;span class="c1"&gt;// Sort dates in descending order (newest first)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sortedDates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groupedHabits&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Format date labels&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formatDateLabel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;yesterday&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Today&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dateString&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;yesterday&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Yesterday&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;weekday&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty-state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;empty-icon&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;📝&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;No&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt; &lt;span class="nx"&gt;tracked&lt;/span&gt; &lt;span class="nx"&gt;yet&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Add&lt;/span&gt; &lt;span class="nx"&gt;your&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;started&lt;/span&gt;&lt;span class="o"&gt;!&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/p&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;habit-list&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Your&lt;/span&gt; &lt;span class="nx"&gt;Habits&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;sortedDates&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date-group&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;date-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h3&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;formatDateLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h3&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;habit-count&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;groupedHabits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;groupedHabits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habits&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;habits-grid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;groupedHabits&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;HabitCard&lt;/span&gt;
                &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onEdit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onEdit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onDelete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;onDelete&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;))}&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="p"&gt;))}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Smart date grouping&lt;/strong&gt;: Uses &lt;code&gt;reduce()&lt;/code&gt; to organize habits efficiently&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-friendly labels&lt;/strong&gt;: "Today" and "Yesterday" are more intuitive than dates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty state&lt;/strong&gt;: Always handle the zero-state gracefully&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive grid&lt;/strong&gt;: CSS Grid makes the layout adapt to any screen size&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Phase 6: The Theme System (Making It Pretty)
&lt;/h2&gt;

&lt;p&gt;I'm a sucker for dark mode. I asked Claude to implement a theme system that would:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Toggle between light and dark&lt;/li&gt;
&lt;li&gt;Remember user preference&lt;/li&gt;
&lt;li&gt;Apply smoothly without flashing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the elegant solution we built:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/hooks/useTheme.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useEffect&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habit-tracker-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Apply theme to root element&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Save to localStorage&lt;/span&gt;
    &lt;span class="nx"&gt;localStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;habit-tracker-theme&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;setTheme&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dark&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;light&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;theme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toggleTheme&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;useTheme&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the CSS magic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* src/index.css */&lt;/span&gt;
&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"light"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bg-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#ffffff&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--bg-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f5f5f5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#333333&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#666666&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--button-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4a90e2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--button-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#357abd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:root&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;data-theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"dark"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--bg-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#1a1a1a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--bg-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#2d2d2d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#e0e0e0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--text-secondary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#a0a0a0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--border-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#404040&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="py"&gt;--button-bg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#4a90e2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--button-hover&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#357abd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Now all components just use CSS variables */&lt;/span&gt;
&lt;span class="nc"&gt;.habit-card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--bg-primary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--text-primary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--border-color&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="n"&gt;var&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;--shadow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 Learning #4:&lt;/strong&gt; CSS custom properties + the &lt;code&gt;data-theme&lt;/code&gt; attribute = the cleanest theme system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 7: Analytics &amp;amp; Trends
&lt;/h2&gt;

&lt;p&gt;This is where the app went from "useful" to "actually cool". I wanted to show users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current streaks&lt;/li&gt;
&lt;li&gt;Completion rates&lt;/li&gt;
&lt;li&gt;Trends over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Claude helped me build a utility file for all the calculations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/utils/habitAnalytics.js&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate current streak for a habit&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calculateCurrentStreak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Sort by date descending&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;checkDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;today&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entryDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;checkDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;T&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entryDate&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;expectedDate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Streak broken&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;streak&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;checkDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;checkDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDate&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Not completed = streak broken&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;streak&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate completion rate for a habit&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;calculateCompletionRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;habitEntries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Group habits by name (case-insensitive)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupHabitsByName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;habit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These pure functions made testing easy and the code maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Phase 8: Routing with React Router
&lt;/h2&gt;

&lt;p&gt;Finally, I wanted to organize the app into multiple pages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Home&lt;/strong&gt;: Main tracker interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trends&lt;/strong&gt;: Analytics dashboard&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;About&lt;/strong&gt;: Info about the app
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/App.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BrowserRouter&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-router-dom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Navigation&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./components/Navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Home&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Trends&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/Trends&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;About&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./pages/About&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ... state management code ...&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;App&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;app-header&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Habit&lt;/span&gt; &lt;span class="nx"&gt;Tracker&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Navigation&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ThemeToggle&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/header&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Routes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;
                &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onAddHabit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleAddHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onUpdateHabit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleUpdateHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="nx"&gt;onDeleteHabit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleDeleteHabit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/trends&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Trends&lt;/span&gt; &lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;habits&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;} /&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Route&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/about&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;About&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Routes&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/main&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Router&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;💡 Learning #5:&lt;/strong&gt; Keep routing simple. Pass data as props instead of reaching for context too early.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Claude Code: What I Learned
&lt;/h2&gt;

&lt;p&gt;After spending hours pair programming with Claude Code, here are my biggest takeaways:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;Be Specific About What You Want&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;❌ Bad: "Add a form"&lt;br&gt;
✅ Good: "Create a form component that accepts habit name, date, and status, with validation and clear button"&lt;/p&gt;
&lt;h3&gt;
  
  
  2. &lt;strong&gt;Let Claude Suggest Architecture&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I often asked: "What's the best way to structure this?" Claude's suggestions were consistently better than my first instincts.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. &lt;strong&gt;Iterate in Small Steps&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of "build the entire app", I worked feature by feature. This made reviewing code easier and kept momentum high.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. &lt;strong&gt;Ask for Explanations&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;When Claude wrote code I didn't understand, I asked "Why did you do it this way?" The explanations taught me new patterns.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. &lt;strong&gt;Review Everything&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AI is powerful, but not infallible. I read every line of code Claude wrote and tested thoroughly.&lt;/p&gt;
&lt;h2&gt;
  
  
  The MVP After One Day
&lt;/h2&gt;

&lt;p&gt;After about 6-8 hours of focused development with Claude Code, I had:&lt;/p&gt;

&lt;p&gt;✅ A fully functional habit tracker&lt;br&gt;
✅ Clean, maintainable code&lt;br&gt;
✅ Dark/light theme support&lt;br&gt;
✅ Data persistence with localStorage&lt;br&gt;
✅ Responsive design&lt;br&gt;
✅ Basic analytics&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lines of code written:&lt;/strong&gt; ~1,200&lt;br&gt;
&lt;strong&gt;Number of manual file operations:&lt;/strong&gt; Almost zero&lt;br&gt;
&lt;strong&gt;Time saved vs coding alone:&lt;/strong&gt; Estimated 60-70%&lt;/p&gt;
&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎨 Adding an 8-category system with emoji icons&lt;/li&gt;
&lt;li&gt;📊 Building comprehensive analytics with streak tracking&lt;/li&gt;
&lt;li&gt;📈 Creating beautiful summary cards with progress bars&lt;/li&gt;
&lt;li&gt;🚀 Deploying to Vercel with proper configuration&lt;/li&gt;
&lt;li&gt;🧪 Testing with Playwright MCP (170+ test cases!)&lt;/li&gt;
&lt;li&gt;📝 Generating comprehensive documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to build along? Here's the starter template we used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-react-app habit-tracker
&lt;span class="nb"&gt;cd &lt;/span&gt;habit-tracker
npm &lt;span class="nb"&gt;install &lt;/span&gt;react-router-dom
npm start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then open Claude Code and start with: "Help me build a habit tracking app with React. Let's start with the data model."&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Building with Claude Code felt like having a senior developer sitting next to me, ready to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write boilerplate instantly&lt;/li&gt;
&lt;li&gt;Suggest better patterns&lt;/li&gt;
&lt;li&gt;Explain complex concepts&lt;/li&gt;
&lt;li&gt;Catch bugs before I ran the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Is it perfect?&lt;/strong&gt; No. I still reviewed every line and made adjustments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it faster?&lt;/strong&gt; Absolutely. What would have taken me a weekend took an evening.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Did I learn?&lt;/strong&gt; More than I expected. Claude's explanations taught me new React patterns and best practices.&lt;/p&gt;

&lt;p&gt;In Part 2, we'll take this MVP and turn it into a production-ready app with advanced features, comprehensive testing, and real deployment. Stay tuned!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Resources:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev" rel="noopener noreferrer"&gt;React Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactrouter.com" rel="noopener noreferrer"&gt;React Router&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop them in the comments below! I'll be responding to all of them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found this helpful?&lt;/strong&gt; Give it a ❤️ and follow me for Part 2!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Part 2 coming next week: "Advanced Features, Testing, and Deployment"&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  AI #React #WebDev #Tutorial #ClaudeCode #ProductivityTools
&lt;/h1&gt;

</description>
      <category>react</category>
      <category>claudecode</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Setting Up a Java Project in VS Code - TestNG</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Sat, 14 Mar 2026 15:55:21 +0000</pubDate>
      <link>https://forem.com/anirseven/setting-up-a-java-project-in-vs-code-testng-5d6f</link>
      <guid>https://forem.com/anirseven/setting-up-a-java-project-in-vs-code-testng-5d6f</guid>
      <description>&lt;h1&gt;
  
  
  Setting Up a Java Project in VS Code: A Step-by-Step Guide
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this guide, I’ll walk you through setting up a modern Java project using Maven and TestNG in Visual Studio Code (VS Code). We’ll cover everything from project creation to running your first test, leveraging the VS Code Java Extension Pack for a seamless experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Installing the VS Code Java Extension Pack
&lt;/h2&gt;

&lt;p&gt;To get started, install the &lt;a href="https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack" rel="noopener noreferrer"&gt;Java Extension Pack by Microsoft&lt;/a&gt; in VS Code. This pack includes essential tools for Java development, such as language support, debugging, and Maven integration.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Creating a Maven Project
&lt;/h2&gt;

&lt;p&gt;Open VS Code and launch the integrated terminal. Run the following command to generate a Maven project using the quickstart archetype:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn org.apache.maven.plugins:maven-archetype-plugin:3.1.2:generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DarchetypeArtifactId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"maven-archetype-quickstart"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DarchetypeGroupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"org.apache.maven.archetypes"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DarchetypeVersion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"1.4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DgroupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"com.example"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-DartifactId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tutorial"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a project structure with &lt;code&gt;src/main/java&lt;/code&gt; and &lt;code&gt;src/test/java&lt;/code&gt; folders, and a &lt;code&gt;pom.xml&lt;/code&gt; for dependency management.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Setting Up TestNG
&lt;/h2&gt;

&lt;p&gt;To use TestNG for testing, add the TestNG dependency to your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.testng&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;testng&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;7.8.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class="nt"&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4. Creating a SimpleTest Class
&lt;/h2&gt;

&lt;p&gt;Inside &lt;code&gt;src/test/java/com/example/&lt;/code&gt;, create a file named &lt;code&gt;simpleTest.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.testng.annotations.Test&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;simpleTest&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Test&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testMethod&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"TestNG is running!"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Configuring testng.xml
&lt;/h2&gt;

&lt;p&gt;Create a &lt;code&gt;testng.xml&lt;/code&gt; file in your project root to specify which tests to run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;suite&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Tutorial Suite"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;test&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"SimpleTest"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;classes&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;class&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"com.example.simpleTest"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/classes&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/test&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/suite&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  6. Updating pom.xml for TestNG
&lt;/h2&gt;

&lt;p&gt;Ensure your &lt;code&gt;pom.xml&lt;/code&gt; includes the TestNG dependency and is set up for Java 21 (LTS):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;maven.compiler.source&amp;gt;&lt;/span&gt;21&lt;span class="nt"&gt;&amp;lt;/maven.compiler.source&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;maven.compiler.target&amp;gt;&lt;/span&gt;21&lt;span class="nt"&gt;&amp;lt;/maven.compiler.target&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Running Your Tests
&lt;/h2&gt;

&lt;p&gt;Build and run your tests using Maven:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see output from your &lt;code&gt;simpleTest&lt;/code&gt; class, confirming TestNG is working.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With VS Code, Maven, and TestNG, you have a powerful setup for Java development. The Java Extension Pack streamlines your workflow, and TestNG makes testing easy. Happy coding!&lt;/p&gt;

</description>
      <category>vscode</category>
      <category>vscodesetup</category>
      <category>javatestng</category>
      <category>testing</category>
    </item>
    <item>
      <title>Demystifying Promises, Async, and Await in JavaScript/TypeScript with Playwright and Cypress</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Fri, 29 Aug 2025 09:54:41 +0000</pubDate>
      <link>https://forem.com/anirseven/demystifying-promises-async-and-await-in-javascripttypescript-with-playwright-and-cypress-2g3i</link>
      <guid>https://forem.com/anirseven/demystifying-promises-async-and-await-in-javascripttypescript-with-playwright-and-cypress-2g3i</guid>
      <description>&lt;p&gt;One of the most common challenges for automation engineers using JavaScript/TypeScript is handling asynchronous code. Whether you’re writing tests in Playwright or Cypress, you’ll often deal with actions that don’t resolve instantly — such as page navigation, element interactions, or API calls.&lt;/p&gt;

&lt;p&gt;That’s where Promises, async, and await come in. Let’s break them down with automation-specific examples.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Promise?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A Promise represents the eventual completion (or failure) of an asynchronous operation. Think of it like a “contract” — “I’ll give you the data when it’s ready.”&lt;/p&gt;

&lt;p&gt;Example: Fetching Data&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const dataPromise = fetch('https://api.github.com/users');
dataPromise.then(response =&amp;gt; response.json())
           .then(data =&amp;gt; console.log(data));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In test automation, every browser action is often a Promise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Async &amp;amp; Await Simplified&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;async marks a function as asynchronous.&lt;/p&gt;

&lt;p&gt;await pauses the function until the Promise is resolved.&lt;/p&gt;

&lt;p&gt;This makes code look synchronous and much easier to read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Async/Await in Playwright&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Playwright heavily relies on async/await. Almost every browser interaction returns a Promise.&lt;/p&gt;

&lt;p&gt;Example: Playwright Test with Async/Await&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { test, expect } from '@playwright/test';

test('Search on Amazon', async ({ page }) =&amp;gt; {
  await page.goto('https://www.amazon.com');
  await page.fill('#twotabsearchtextbox', 'Laptop');
  await page.click('input[type="submit"]');

  const results = page.locator('span.a-size-medium');
  await expect(results.first()).toBeVisible();
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how await makes each step sequential and readable.&lt;br&gt;
Without it, you’d be chaining .then() everywhere, making tests messy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using Async/Await in Cypress&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cypress is slightly different — it uses built-in command chaining instead of raw async/await. Cypress commands like &lt;code&gt;cy.get()&lt;/code&gt; and &lt;code&gt;cy.click()&lt;/code&gt; are asynchronous but auto-managed.&lt;/p&gt;

&lt;p&gt;Example: Cypress Test with Implicit Async Handling&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;describe('Search on Amazon', () =&amp;gt; {
  it('should search for Laptop', () =&amp;gt; {
    cy.visit('https://www.amazon.com');
    cy.get('#twotabsearchtextbox').type('Laptop');
    cy.get('input[type="submit"]').click();
    cy.get('span.a-size-medium').first().should('be.visible');
  });
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, Cypress internally queues commands and resolves them without you writing await.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mixing Promises with Playwright&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes you need to handle multiple promises, e.g., waiting for API + UI events together.&lt;/p&gt;

&lt;p&gt;Example: Waiting for Navigation and Click (Playwright)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await Promise.all([
  page.waitForNavigation(),
  page.click('text=Login')
]);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the test doesn’t miss the navigation event.&lt;/p&gt;

&lt;p&gt;Mixing Promises with Cypress&lt;/p&gt;

&lt;p&gt;Cypress doesn’t expose Promises directly, but you can wrap them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cy.wrap(Promise.resolve(42)).then(value =&amp;gt; {
  cy.log('Resolved value:', value);  // 42
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common Pitfalls &amp;amp; Tips&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Forgetting await in Playwright&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page.click('button#submit'); // ❌ Won’t wait  
await page.click('button#submit'); // ✅ Correct  

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using async/await incorrectly in Cypress&lt;br&gt;
Cypress commands don’t return raw Promises, so don’t do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const text = await cy.get('h1').text(); // ❌ Not supported  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead, use&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.then():

cy.get('h1').then($el =&amp;gt; {
  cy.log($el.text());
});


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Parallelization with Promises&lt;/strong&gt;&lt;br&gt;
For Playwright, prefer Promise.all when waiting for multiple things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Understanding Promises, async, and await is essential for writing clean, reliable automation in JavaScript/TypeScript.&lt;/p&gt;

&lt;p&gt;In Playwright, always use async/await to control flow.&lt;/p&gt;

&lt;p&gt;In Cypress, commands are asynchronous but automatically queued.&lt;/p&gt;

&lt;p&gt;For advanced scenarios, leverage Promise.all (Playwright) or cy.wrap (Cypress).&lt;/p&gt;

&lt;p&gt;Mastering these concepts makes your automation more readable, stable, and maintainable.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>playwright</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Using MCP Server to Supercharge Playwright Automation</title>
      <dc:creator>Anirban Majumdar</dc:creator>
      <pubDate>Fri, 29 Aug 2025 09:32:45 +0000</pubDate>
      <link>https://forem.com/anirseven/using-mcp-server-to-supercharge-playwright-automation-5hk3</link>
      <guid>https://forem.com/anirseven/using-mcp-server-to-supercharge-playwright-automation-5hk3</guid>
      <description>&lt;p&gt;Automation at scale often struggles with two pain points:&lt;/p&gt;

&lt;p&gt;Slow test development (boilerplate code, selectors, retries).&lt;/p&gt;

&lt;p&gt;Flaky tests due to fragile locators.&lt;/p&gt;

&lt;p&gt;With Model Context Protocol (MCP), we can connect Playwright to an AI server that assists in writing, fixing, and maintaining automation tests. In this blog, we’ll set up MCP with Playwright to automate real-world scenarios on Amazon.com.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔹 1. Prerequisites&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Make sure you have the following installed:&lt;/p&gt;

&lt;p&gt;Node.js (&amp;gt;=18)&lt;/p&gt;

&lt;p&gt;Playwright&lt;/p&gt;

&lt;p&gt;MCP server implementation (e.g., mcp-server&lt;br&gt;
 or custom server)&lt;/p&gt;

&lt;p&gt;OpenAI/GitHub Copilot or another LLM provider&lt;/p&gt;

&lt;p&gt;Install Playwright&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add TypeScript config if needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install -D typescript ts-node @types/node
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔹 2. Folder Structure&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;playwright-amazon/
 ┣ tests/
 ┃ ┣ amazon-search.spec.ts
 ┃ ┣ amazon-cart.spec.ts
 ┣ mcp/
 ┃ ┣ server.ts
 ┃ ┣ config.json
 ┣ playwright.config.ts
 ┗ package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔹 3. MCP Server Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your MCP server is the middleware between Playwright and the LLM. It provides structured prompts like “Suggest resilient Playwright locator for Add to Cart button”.&lt;/p&gt;

&lt;p&gt;Example MCP Config (mcp/config.json)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "playwright-mcp",
  "description": "MCP server for Playwright automation",
  "capabilities": {
    "selectors": true,
    "code-gen": true
  },
  "llmProvider": "openai",
  "llmModel": "gpt-4o-mini"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example MCP Server (mcp/server.ts)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import express from 'express';
import bodyParser from 'body-parser';
import { generateLocator } from './utils/locator-generator';

const app = express();
app.use(bodyParser.json());

app.post('/locator', async (req, res) =&amp;gt; {
  const { request, context } = req.body;
  const locator = await generateLocator(request, context);
  res.json({ locator });
});

app.listen(4000, () =&amp;gt; console.log('MCP server running on port 4000'));

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔹 4. Playwright + MCP Integration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll call MCP server from within our Playwright tests whenever we need AI-assisted selectors or snippets.&lt;/p&gt;

&lt;p&gt;Utility to Call MCP (tests/utils/mcp-client.ts)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import axios from 'axios';

export async function getLocator(request: string, context: string): Promise&amp;lt;string&amp;gt; {
  const response = await axios.post('http://localhost:4000/locator', { request, context });
  return response.data.locator;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔹 5. Example Test – Amazon Search&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;tests/amazon-search.spec.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { test, expect } from '@playwright/test';
import { getLocator } from './utils/mcp-client';

test('Amazon Search for Laptop', async ({ page }) =&amp;gt; {
  await page.goto('https://www.amazon.com');

  // Get locator dynamically from MCP
  const searchBox = await getLocator("Search input field", "amazon-homepage");
  await page.fill(searchBox, 'Laptop');

  const searchButton = await getLocator("Search button", "amazon-homepage");
  await page.click(searchButton);

  // Validate results
  const results = page.locator('span.a-size-medium');
  await expect(results.first()).toBeVisible();
});



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here MCP generates resilient locators, so even if Amazon updates their DOM slightly, the AI can adapt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔹 6. Example Test – Add to Cart&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;tests/amazon-cart.spec.ts&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
import { test, expect } from '@playwright/test';
import { getLocator } from './utils/mcp-client';

test('Add product to Amazon Cart', async ({ page }) =&amp;gt; {
  await page.goto('https://www.amazon.com');

  // Search product
  const searchBox = await getLocator("Search input field", "amazon-homepage");
  await page.fill(searchBox, 'iPhone 15');
  const searchButton = await getLocator("Search button", "amazon-homepage");
  await page.click(searchButton);

  // Click first result
  const firstResult = await getLocator("First product link", "amazon-search-results");
  await page.click(firstResult);

  // Add to cart
  const addToCart = await getLocator("Add to Cart button", "amazon-product-page");
  await page.click(addToCart);

  // Validate cart
  const cartCount = page.locator('#nav-cart-count');
  await expect(cartCount).toHaveText('1');
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;🔹 7. Benefits&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ Resilient selectors – MCP auto-generates robust locators.&lt;br&gt;
✅ Reduced flaky tests – Fewer selector breakages on UI changes.&lt;br&gt;
✅ Faster automation – Engineers describe tests; AI scaffolds code.&lt;br&gt;
✅ Scalable – Can plug into CI/CD for continuous improvement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔹 Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using MCP with Playwright transforms how we build automation. Instead of spending hours debugging fragile selectors, engineers can focus on test strategy, while MCP + AI handles the scaffolding.&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>mcp</category>
      <category>playwrightmcp</category>
      <category>testautomation</category>
    </item>
  </channel>
</rss>
