Build a Task Tracker with Claude Code

Use Claude Code to build a task tracker app without writing a single line of code.

Introduction

30 Second Summary

Imagine having a coding partner that writes entire applications for you based on plain English instructions. That's exactly what AI coding agents do today.

In this project, you will use Claude Code to build a complete task tracker web app without writing a single line of code yourself. You'll learn how to prompt, plan, iterate, and ship with an AI agent running in your terminal.

What You'll Build

A functional task tracker web application built entirely through natural language prompts to Claude Code, an AI coding agent that runs in your terminal.

By the end of this project, you'll have:

  • Claude Code installed and authenticated on your machine, ready to build projects from natural language.
  • A working task tracker web app with HTML, CSS, and JavaScript files that runs in your browser with add, complete, delete, and filter functionality.
  • A CLAUDE.md configuration file that guides Claude Code's behavior for your project.
  • Secret Mission: Create a custom Claude Code skill that generates a project summary and changelog from your Git history.

Are there any prerequisites?

You'll need basic familiarity with using a terminal and a paid Claude subscription. Claude Code requires a Claude Pro ($20/month), Max, Team, or Enterprise plan. Alternatively, you can use an Anthropic Console account with pre-paid API credits.

Install Claude Code

Claude Code is a command-line tool that turns your terminal into an AI coding partner. It reads your project files, writes code, runs commands, and commits to Git based on your natural language instructions.

In this step, you will install Claude Code on your Mac, authenticate with your Claude subscription, and confirm Git is ready for version control.

In this step, get ready to:
  • Install Claude Code on your Mac.
  • Authenticate with your Claude subscription.
  • Verify Git is installed.
Install Claude Code via the native installer

Claude Code lives in your terminal. You will download and install it using a single command, then verify it is ready to use.

  • Press Cmd+Space (macOS) to open Spotlight.
  • Type Terminal and press Enter to open it.
  • Install Claude Code by running this command:
curl -fsSL https://claude.ai/install.sh | bash

What does this command do?

This downloads the official Claude Code installer script from Anthropic's servers and runs it. The installer places the claude binary in ~/.local/bin and adds it to your shell's PATH.

The native installer also enables automatic background updates, so Claude Code stays current without manual intervention.

  • Verify the installation succeeded by running:
claude --version

You should see a version number printed in your terminal.

Seeing command not found?

Your terminal may not have picked up the new PATH yet. Close your Terminal window completely and open a fresh one, then try claude --version again.

If it still does not work, manually add the install directory to your PATH by running:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

Then try claude --version once more.

help me fix Claude Code command not found after installation

Authenticate with your Claude account

Claude Code needs to connect to your Anthropic account to function. The first time you launch it, it walks you through a browser-based login.

What accounts work with Claude Code?

You need a Claude Pro, Max, Team, or Enterprise subscription. Alternatively, you can use an Anthropic Console account with pre-paid API credits.

The free Claude.ai plan does not include Claude Code access.

  • Start Claude Code by running:
claude

What will I see?

Claude Code will detect this is your first run and begin the authentication flow. It will ask you to choose a login method (your Claude.ai account or Anthropic Console).

A browser window will open automatically for you to sign in. If the browser does not open, the terminal will display a URL you can copy and paste manually.

  • Select your login method when prompted (Claude.ai account for most learners).
  • Complete the sign-in in your browser.
  • Return to your terminal after signing in.

Once authenticated, you will see the Claude Code interactive prompt showing the model name and your working directory. This confirms Claude Code is connected and ready.

  • Exit the session for now by pressing Ctrl+D or typing exit.

Authentication not completing?

Make sure you are signing in with an account that has a Pro, Max, Team, or Enterprise subscription. The free Claude.ai plan does not include Claude Code access.

If the browser redirect fails, look for a URL printed in your terminal. Copy and paste it into your browser to complete the login manually.

help me fix Claude Code authentication issues

Verify Git is installed

Claude Code uses Git to track changes and create commits for your project. Let's confirm it is available on your machine.

  • Check your Git installation by running:
git --version

✔️ I see a version number

Git is ready to go. You will see something like git version 2.x.x in the output.

ⓧ Command not found

macOS will automatically prompt you to install Xcode Command Line Tools, which includes Git.

  • Click Install when the dialog appears.
  • Wait for the installation to complete (this may take a few minutes).
  • Run git --version again to confirm it is now installed.

Your environment is locked and loaded. Next, you will create your project folder and start exploring what Claude Code can do.

Set Up Your Project

With Claude Code installed and ready, it's time to create your project workspace.

In this step, you'll set up a dedicated folder for your task tracker, initialize version control, and start your first Claude Code session. You'll also create a CLAUDE.md file that teaches Claude Code your project's coding preferences.

In this step, get ready to:
  • Create a project directory with Git version control.
  • Start a Claude Code session and explore its interactive commands.
  • Create a CLAUDE.md configuration file to guide Claude Code's behavior.
Create a project directory and initialize Git

Every project needs a home folder and version control. Git tracks every change Claude Code makes, so you can always see what happened and roll back if needed.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, move into it, and initialize a Git repository by running these commands:
mkdir task-tracker
cd task-tracker
git init

What do these commands do?

  • mkdir task-tracker creates a new folder called task-tracker on your Desktop.
  • cd task-tracker moves your terminal inside that folder.
  • git init sets up Git version control so every change is tracked.
  • Confirm Git initialized successfully by running:
ls -a

You should see a .git folder in the output. This hidden folder is where Git stores your project history.

Don't see a .git folder?

  • Make sure you ran git init inside the task-tracker folder, not your Desktop.
  • Run pwd to check your current location. It should end with /task-tracker.

Help me set up my project folder and Git repository.

  • Start Claude Code inside your project by running:
claude

You'll see the Claude Code prompt with the version, current model, and working directory shown above it. This means Claude Code is active and ready for your instructions.

Claude Code won't start?

  • If you see an authentication error, your session may have expired. Type /login once Claude Code opens, or re-run the authentication from Step 1.
  • Make sure you have an active internet connection. Claude Code requires a network connection to communicate with Claude.

Help me get Claude Code running in my terminal.

Explore Claude Code's interactive commands

Before building anything, take a moment to see what Claude Code can do. The interactive session accepts both slash commands (like /help) and natural language prompts.

  • See all available commands by typing this into the Claude Code prompt:
/help

What does /help show?

The /help command lists all slash commands available inside a Claude Code session. These include shortcuts for clearing history, resuming past conversations, and managing your session.

  • Try asking Claude Code a natural language question by typing:
what can you help me build in this empty project?

Claude Code responds with suggestions based on what it knows. Since the folder is empty, it will offer general ideas. This shows how you interact with Claude Code conversationally.

  • Clear the conversation to start fresh by typing:
/clear

Why clear the conversation?

Claude Code has a limited context window. Clearing removes previous messages so Claude can focus entirely on your next request. It's good practice to clear between unrelated tasks.

Create a CLAUDE.md file to guide Claude Code

A CLAUDE.md file is a markdown file at your project root that Claude Code reads at the start of every session. It tells Claude Code your preferred tech stack, coding style, and file structure.

Why does CLAUDE.md matter?

Without a CLAUDE.md file, Claude Code has no project-specific context. It might use a framework you don't want or put files in unexpected places.

With a CLAUDE.md file, every session starts with your preferences loaded. The better your instructions, the more consistent Claude Code's output becomes.

  • Ask Claude Code to create the configuration file by typing this prompt:
Create a CLAUDE.md file for this project. This is a task tracker web app using vanilla HTML, CSS, and JavaScript. Use modern ES6+ syntax, semantic HTML, and CSS custom properties for theming. All files should go in the project root.
  • Claude Code will show you the file it plans to create. Review the content and approve the changes when prompted.
  • Confirm the file was created by typing:
can you show me what's in CLAUDE.md?

Claude Code should display the contents of your new CLAUDE.md file. It should mention vanilla HTML, CSS, and JavaScript, ES6+ syntax, semantic HTML, CSS custom properties, and a file structure with all files in the project root.

What if my CLAUDE.md looks different?

Since Claude Code generates content dynamically, your file's exact wording may differ slightly. The important thing is that it covers the same key points: vanilla tech stack, ES6+ syntax, semantic HTML, CSS custom properties, and the project root file structure.

Claude Code didn't create the file?

  • Make sure you approved the file creation when Claude Code asked for permission. Claude Code asks before writing files to disk.
  • If you accidentally denied permission, run the same prompt again and approve this time.

Help me create my CLAUDE.md file with Claude Code.

✔️ Awesome, I've got everything!

Great. Double check you see a CLAUDE.md file in your task-tracker folder. You're all set for the next step.

ⓧ I'd like to double check the full code

Here is what your CLAUDE.md file should look like. Your version may differ slightly in wording, but should cover the same key points:

# Task Tracker Project

## Project Overview
A task tracker web application using vanilla HTML, CSS, and JavaScript.

## Tech Stack
- HTML5 with semantic elements
- CSS3 with custom properties for theming
- Vanilla JavaScript (ES6+ syntax)
- localStorage for data persistence

## Code Style
- Use modern ES6+ syntax (const/let, arrow functions, template literals)
- Use semantic HTML elements (main, section, header, button)
- Use CSS custom properties for colors and spacing
- All files go in the project root directory
- No build tools or frameworks

## File Structure
- index.html - Main HTML file
- style.css - All styles
- app.js - Application logic

Your project workspace is ready and Claude Code knows exactly how you want your code written. Next up, you'll use plan mode to design the task tracker app before Claude writes a single line of code.

Plan Your App with Plan Mode

Your project is set up with a git repo and a CLAUDE.md file that tells Claude Code exactly how you want your app built.

Now it is time to actually build the app. Instead of jumping straight into code, you will use Claude Code's plan mode to design the app first, then refine the plan, and finally let Claude build everything in one go. This explore-then-plan-then-code workflow is the highest-leverage way to use Claude Code.

In this step, get ready to:
  • Use plan mode to design your task tracker before writing any code.
  • Refine the plan with feedback to add a task count display.
  • Execute the plan and test your app in the browser.
Use plan mode to design before coding

Jumping straight into code often produces code that solves the wrong problem. Plan mode tells Claude Code to think through the full design before touching any files.

  • In your Claude Code session, type the following prompt and press Enter:
Plan a task tracker web app. It should let users add tasks with a title, mark tasks as complete, delete tasks, and persist data with localStorage. Keep it simple - just index.html, style.css, and app.js.

What does this prompt do?

Starting your prompt with "Plan" signals to Claude Code that you want a design pass, not immediate code generation. Claude will outline the file structure, key functions, and overall approach without creating any files yet.

This is the same workflow Anthropic recommends in their best practices: separate exploration from execution.

Claude Code responds with a structured plan covering the three files, the data model for tasks, and the functions needed. Take a moment to read through it.

Read the plan carefully

The plan should mention things like a task array, localStorage persistence, DOM rendering, and event listeners for add, complete, and delete actions. If something major is missing, you will fix it in the next substep.

Refine the plan with feedback

An AI coding tool becomes dramatically more effective when you iterate on its output instead of accepting the first response. This prompt-then-refine cycle is the core skill of working with Claude Code.

  • Give Claude Code this follow-up feedback:
The plan looks good. Also add a task count display showing "X of Y tasks complete" at the top.

Why iterate instead of prompting everything upfront?

Giving feedback in a second message lets you review Claude's interpretation before committing to it. You can catch misunderstandings early and steer the design without rewriting a long prompt from scratch.

This iterative workflow is what makes Claude Code effective for real projects. One big prompt rarely captures everything. Short feedback loops do.

Claude Code updates the plan to include the task count feature. You should see it mentioned in the revised output.

Execute the plan

With the plan finalized, you can now tell Claude Code to build everything. Claude will create all three files based on the design you just approved.

  • Tell Claude Code to start building:
Go ahead and build it based on the plan.
  • Claude Code will ask for permission to create files. Approve each file creation when prompted by pressing y or selecting Allow.

What is Claude Code doing?

Claude Code reads your CLAUDE.md for project guidelines, then generates each file following those instructions. It uses semantic HTML, CSS custom properties, and ES6+ syntax because your CLAUDE.md told it to.

This is the power of that configuration file you created in the previous step. Without it, Claude would make its own style choices every time.

  • Once Claude finishes, open index.html in your browser. Exit the Claude Code session first by typing exit, then run:
open index.html

Your browser opens with the task tracker app. You should see a centered card with a "Task Tracker" heading, a task count reading "0 of 0 tasks complete", an input field with an Add button, and an empty task list below.

  • Try adding a task by typing a title into the input field and clicking Add.
  • Try clicking Done on a task to mark it complete.
  • Try clicking Delete to remove a task.

Don't see anything in the browser?

Make sure you are opening index.html from inside the task-tracker directory. If your terminal is in a different folder, run cd ~/Desktop/task-tracker first, then open index.html again.

If the page looks broken or is completely blank, check that Claude Code created all three files. Run ls in your terminal and confirm you see index.html, style.css, and app.js. If any are missing, start a new Claude Code session with claude and ask it to create the missing file.

Help me troubleshoot why my task tracker isn't rendering in the browser. Claude Code created the files but the page is blank or broken.

✔️ Awesome, I've got everything!

Great. Double check you have all three files in your task-tracker folder and that the app loads in your browser.

ⓧ I'd like to double check the full code

Since Claude Code generates the code, your files may differ slightly in implementation. The important thing is that the functionality works. Here is a reference version showing what the files should approximately look like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task Tracker</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <header>
            <h1>Task Tracker</h1>
            <p class="task-count" id="taskCount">0 of 0 tasks complete</p>
        </header>

        <form id="taskForm" class="task-form">
            <input type="text" id="taskInput" placeholder="Add a new task..." required>
            <button type="submit">Add</button>
        </form>

        <ul class="task-list" id="taskList"></ul>
    </main>

    <script src="app.js"></script>
</body>
</html>
:root {
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --text-primary: #1a1a1a;
    --text-secondary: #666666;
    --accent: #4a90d9;
    --accent-hover: #357abd;
    --border: #e0e0e0;
    --danger: #e74c3c;
    --danger-hover: #c0392b;
    --success: #27ae60;
    --radius: 8px;
    --spacing: 1rem;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    line-height: 1.6;
    min-height: 100vh;
    padding: var(--spacing);
}

.container {
    max-width: 600px;
    margin: 2rem auto;
    background-color: var(--bg-primary);
    border-radius: var(--radius);
    padding: 2rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

header { text-align: center; margin-bottom: 1.5rem; }
header h1 { font-size: 1.8rem; margin-bottom: 0.25rem; }
.task-count { color: var(--text-secondary); font-size: 0.9rem; }

.task-form { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.task-form input { flex: 1; padding: 0.75rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); font-size: 1rem; background-color: var(--bg-secondary); color: var(--text-primary); }
.task-form input:focus { outline: none; border-color: var(--accent); }
.task-form button { padding: 0.75rem 1.5rem; background-color: var(--accent); color: white; border: none; border-radius: var(--radius); font-size: 1rem; cursor: pointer; }
.task-form button:hover { background-color: var(--accent-hover); }

.task-list { list-style: none; }
.task-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border-bottom: 1px solid var(--border); }
.task-item:last-child { border-bottom: none; }
.task-item.completed .task-text { text-decoration: line-through; color: var(--text-secondary); }
.task-text { flex: 1; font-size: 1rem; }
.task-item button { padding: 0.4rem 0.8rem; border: none; border-radius: var(--radius); cursor: pointer; font-size: 0.8rem; }
.complete-btn { background-color: var(--success); color: white; }
.complete-btn:hover { opacity: 0.8; }
.delete-btn { background-color: var(--danger); color: white; }
.delete-btn:hover { background-color: var(--danger-hover); }
const taskForm = document.getElementById('taskForm');
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
const taskCount = document.getElementById('taskCount');

let tasks = JSON.parse(localStorage.getItem('tasks')) || [];

const saveTasks = () => { localStorage.setItem('tasks', JSON.stringify(tasks)); };

const updateTaskCount = () => {
    const completed = tasks.filter(task => task.completed).length;
    taskCount.textContent = `${completed} of ${tasks.length} tasks complete`;
};

const renderTasks = () => {
    taskList.innerHTML = '';
    tasks.forEach(task => {
        const li = document.createElement('li');
        li.className = `task-item${task.completed ? ' completed' : ''}`;
        li.innerHTML = `
            <span class="task-text">${task.title}</span>
            <button class="complete-btn">${task.completed ? 'Undo' : 'Done'}</button>
            <button class="delete-btn">Delete</button>
        `;
        li.querySelector('.complete-btn').addEventListener('click', () => { task.completed = !task.completed; saveTasks(); renderTasks(); updateTaskCount(); });
        li.querySelector('.delete-btn').addEventListener('click', () => { tasks = tasks.filter(t => t.id !== task.id); saveTasks(); renderTasks(); updateTaskCount(); });
        taskList.appendChild(li);
    });
    updateTaskCount();
};

taskForm.addEventListener('submit', (e) => {
    e.preventDefault();
    const title = taskInput.value.trim();
    if (!title) return;
    tasks.push({ id: Date.now(), title, completed: false });
    saveTasks();
    renderTasks();
    taskInput.value = '';
    taskInput.focus();
});

renderTasks();

Your task tracker is alive. Next up, you will test it more thoroughly and use Claude Code to fix issues and add new features on the fly.

Iterate and Fix with Claude Code

Your task tracker is live in the browser, built entirely through natural language prompts.

Real development is never one-and-done. You test, find issues, request changes, and commit your progress. In this step, you will practice the iterative workflow that makes Claude Code so effective for day-to-day coding.

In this step, get ready to:
  • Test your app and report issues to Claude Code.
  • Request a feature enhancement (filter bar).
  • Commit your changes using Claude Code's git integration.
Test your app and report issues

Before requesting new features, you should verify that what Claude Code already built is working correctly. This is the test-then-report pattern you will use constantly with agentic coding tools.

  • Switch to your browser where index.html is open.
  • Add a couple of tasks using the input field.
  • Click the Done button on a task to mark it complete.
  • Check that completed tasks show a strikethrough style on the task text.
  • Click Delete on a task to remove it.

If the strikethrough is not appearing (or any other feature is broken), you can describe the problem to Claude Code and let it investigate. Return to your Claude Code session in the terminal and type this prompt:

When I click the complete button, the task text should get a strikethrough style. Can you check if that's working correctly and fix it if not?

What happens when you report an issue?

Claude Code reads your existing files, identifies the relevant CSS rule and JavaScript logic, then confirms whether it works or proposes a fix. You do not need to point it at a specific file or line number. It finds the relevant code on its own.

  • If Claude Code proposes changes, approve them when prompted.
  • Refresh your browser and confirm the strikethrough now works on completed tasks.

Claude Code says everything looks fine but it still doesn't work?

Try being more specific. Mention which browser you are using and describe exactly what you see versus what you expect. The more concrete your bug report, the better Claude Code can diagnose the issue.

Help me debug why my completed tasks don't show a strikethrough style in the browser.

Request a feature enhancement

Your app works, but you want more functionality. This is where Claude Code shines. You can describe a feature in plain English and watch it plan and implement the change across multiple files.

  • In your Claude Code session, request the filter feature by typing this prompt:
Add a filter bar at the top with three buttons: All, Active, and Completed. Clicking each one should filter the visible tasks.

What does Claude Code do with this prompt?

Claude Code reads your existing index.html, style.css, and app.js files. It plans where to place the HTML, what CSS styles to add, and how to wire up the JavaScript filter logic. Then it makes all the edits in one go.

  • Review the changes Claude Code proposes. You should see edits to all three files.
  • Approve the file changes when prompted.
  • Refresh your browser.

You should see three buttons (All, Active, Completed) between the input form and the task list. Click each button to confirm it filters your tasks correctly.

Why this iterative workflow matters

This prompt-then-refine loop is the core pattern of working with Claude Code. You describe what you want, review the result, and give feedback. Each cycle teaches Claude Code more about your project through the conversation context.

Filters not working as expected?

If clicking the filter buttons does nothing, tell Claude Code exactly what you observe. For example: "The filter buttons appear but clicking Active doesn't hide completed tasks." Claude Code will read the current code and fix the logic.

Help me fix my filter buttons in Claude Code.

Commit your work with Claude Code's git integration

Your filter feature is working. In a real project, this is the moment you would commit your progress to Git. Claude Code can handle this for you, including writing a meaningful commit message based on what actually changed.

  • In your Claude Code session, ask it to commit by typing this prompt:
Commit all changes with a descriptive commit message

What does Claude Code do here?

Claude Code runs git add to stage your files, then git commit with a commit message it writes based on the actual changes. The message describes what was built, not a vague "update files."

  • Approve the git commands when Claude Code asks for permission.
  • Confirm the commit succeeded. Claude Code will show you the commit hash and message.

Check your git log anytime

You can ask Claude Code to show your git history by typing show me the git log. This is helpful for reviewing what you have built across multiple iterations.

Seeing a git error?

If Claude Code reports a git identity error, it means git does not know your name and email. Tell Claude Code: "Set my git user name to My Name and email to my@email.com, then try the commit again." Replace with your actual name and email.

Help me fix a git identity error in Claude Code.

Now take a moment to compare your files against the expected state. Since Claude Code generates code dynamically, your implementation may differ slightly in naming or structure. The functionality should match.

✔️ Awesome, I've got everything!

Great. Double check you have the filter buttons visible in your browser and that your git commit completed successfully.

ⓧ I'd like to double check the full code

Here is the expected state of each file after this step. Your code may differ slightly in formatting or variable names, but the functionality should match.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task Tracker</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <header>
            <h1>Task Tracker</h1>
            <p class="task-count" id="taskCount">0 of 0 tasks complete</p>
        </header>

        <form id="taskForm" class="task-form">
            <input type="text" id="taskInput" placeholder="Add a new task..." required>
            <button type="submit">Add</button>
        </form>

        <div class="filters" id="filters">
            <button class="filter-btn active" data-filter="all">All</button>
            <button class="filter-btn" data-filter="active">Active</button>
            <button class="filter-btn" data-filter="completed">Completed</button>
        </div>

        <ul class="task-list" id="taskList"></ul>
    </main>

    <script src="app.js"></script>
</body>
</html>
:root {
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --text-primary: #1a1a1a;
    --text-secondary: #666666;
    --accent: #4a90d9;
    --accent-hover: #357abd;
    --border: #e0e0e0;
    --danger: #e74c3c;
    --danger-hover: #c0392b;
    --success: #27ae60;
    --radius: 8px;
    --spacing: 1rem;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    line-height: 1.6;
    min-height: 100vh;
    padding: var(--spacing);
}

.container {
    max-width: 600px;
    margin: 2rem auto;
    background-color: var(--bg-primary);
    border-radius: var(--radius);
    padding: 2rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

header { text-align: center; margin-bottom: 1.5rem; }
header h1 { font-size: 1.8rem; margin-bottom: 0.25rem; }
.task-count { color: var(--text-secondary); font-size: 0.9rem; }

.task-form { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.task-form input { flex: 1; padding: 0.75rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); font-size: 1rem; background-color: var(--bg-secondary); color: var(--text-primary); }
.task-form input:focus { outline: none; border-color: var(--accent); }
.task-form button { padding: 0.75rem 1.5rem; background-color: var(--accent); color: white; border: none; border-radius: var(--radius); font-size: 1rem; cursor: pointer; }
.task-form button:hover { background-color: var(--accent-hover); }

.filters { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.filter-btn { flex: 1; padding: 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background-color: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; }
.filter-btn.active { background-color: var(--accent); color: white; border-color: var(--accent); }
.filter-btn:hover:not(.active) { border-color: var(--accent); color: var(--accent); }

.task-list { list-style: none; }
.task-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border-bottom: 1px solid var(--border); }
.task-item:last-child { border-bottom: none; }
.task-item.completed .task-text { text-decoration: line-through; color: var(--text-secondary); }
.task-text { flex: 1; font-size: 1rem; }
.task-item button { padding: 0.4rem 0.8rem; border: none; border-radius: var(--radius); cursor: pointer; font-size: 0.8rem; }
.complete-btn { background-color: var(--success); color: white; }
.complete-btn:hover { opacity: 0.8; }
.delete-btn { background-color: var(--danger); color: white; }
.delete-btn:hover { background-color: var(--danger-hover); }
const taskForm = document.getElementById('taskForm');
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
const taskCount = document.getElementById('taskCount');
const filterButtons = document.querySelectorAll('.filter-btn');

let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentFilter = 'all';

const saveTasks = () => { localStorage.setItem('tasks', JSON.stringify(tasks)); };

const updateTaskCount = () => {
    const completed = tasks.filter(task => task.completed).length;
    taskCount.textContent = `${completed} of ${tasks.length} tasks complete`;
};

const renderTasks = () => {
    taskList.innerHTML = '';
    const filteredTasks = tasks.filter(task => {
        if (currentFilter === 'active') return !task.completed;
        if (currentFilter === 'completed') return task.completed;
        return true;
    });
    filteredTasks.forEach(task => {
        const li = document.createElement('li');
        li.className = `task-item${task.completed ? ' completed' : ''}`;
        li.innerHTML = `
            <span class="task-text">${task.title}</span>
            <button class="complete-btn">${task.completed ? 'Undo' : 'Done'}</button>
            <button class="delete-btn">Delete</button>
        `;
        li.querySelector('.complete-btn').addEventListener('click', () => { task.completed = !task.completed; saveTasks(); renderTasks(); updateTaskCount(); });
        li.querySelector('.delete-btn').addEventListener('click', () => { tasks = tasks.filter(t => t.id !== task.id); saveTasks(); renderTasks(); updateTaskCount(); });
        taskList.appendChild(li);
    });
    updateTaskCount();
};

taskForm.addEventListener('submit', (e) => {
    e.preventDefault();
    const title = taskInput.value.trim();
    if (!title) return;
    tasks.push({ id: Date.now(), title, completed: false });
    saveTasks();
    renderTasks();
    taskInput.value = '';
    taskInput.focus();
});

filterButtons.forEach(btn => {
    btn.addEventListener('click', () => {
        filterButtons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        currentFilter = btn.dataset.filter;
        renderTasks();
    });
});

renderTasks();

You have just experienced the full iteration loop: test, report, enhance, and commit. Next up, you will learn advanced ways to use Claude Code including one-shot mode, conversation continuation, and piped input.

Use Claude Code Like a Pro

You have been working inside Claude Code's interactive session for the entire project so far. That is great for multi-step work, but Claude Code also supports other modes for different situations.

In this step, you will learn three power-user techniques: launching Claude Code with a task already defined, resuming a previous conversation, and piping file content directly into Claude for analysis.

In this step, get ready to:
  • Add a dark mode toggle using an initial-prompt session.
  • Resume your most recent conversation with claude -c.
  • Pipe file content into Claude Code for a code review.
Try an initial-prompt session for quick tasks

So far, you have started Claude Code and then typed your prompts inside the session. There is a faster way: you can pass your task directly as a quoted argument when you launch Claude Code.

  • Exit your current Claude Code session by typing exit or pressing Ctrl+D.
  • You will return to your normal terminal prompt inside the task-tracker directory.
  • Add a dark mode toggle to your task tracker by running this command:
claude "add a dark mode toggle button to the task tracker that saves the preference to localStorage"

What does this command do?

Passing a quoted string after claude starts an interactive session with that prompt already submitted. Claude Code immediately begins working on the task without you needing to type anything inside the session.

This is perfect for well-defined tasks where you know exactly what you want. Claude reads your CLAUDE.md file, understands the project context, and gets straight to work.

  • Claude Code will read your existing code, plan the dark mode implementation, and make changes across index.html, style.css, and app.js.
  • Approve the file changes when Claude prompts you.
  • Open index.html in your browser and look for the dark mode toggle button at the bottom of the app.
  • Click the toggle button. The entire app should switch to a dark color scheme.
  • Refresh the page. Your dark mode preference should persist (thanks to localStorage).

Dark mode not working?

  • Make sure you approved all file changes when Claude prompted you. If you declined any, Claude may not have written the full implementation.
  • Check that your browser is loading the latest version of the files. Do a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows).
  • If the toggle button is missing entirely, ask Claude in the still-open session: The dark mode toggle button is not appearing. Can you check index.html?

Help me troubleshoot my dark mode toggle in Claude Code

Since Claude Code generates code dynamically, your exact implementation may differ slightly from the reference code below. The important thing is that the feature works: a toggle button that switches between light and dark themes and remembers the preference.

✔️ Awesome, I've got everything!

Great. Make sure the dark mode toggle works and persists on refresh before moving on.

ⓧ I'd like to double check the full code

Here is the expected state of each file after the dark mode feature is added. Your code may look slightly different but should achieve the same functionality.

index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Task Tracker</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <main class="container">
        <header>
            <h1>Task Tracker</h1>
            <p class="task-count" id="taskCount">0 of 0 tasks complete</p>
        </header>

        <form id="taskForm" class="task-form">
            <input type="text" id="taskInput" placeholder="Add a new task..." required>
            <button type="submit">Add</button>
        </form>

        <div class="filters" id="filters">
            <button class="filter-btn active" data-filter="all">All</button>
            <button class="filter-btn" data-filter="active">Active</button>
            <button class="filter-btn" data-filter="completed">Completed</button>
        </div>

        <ul class="task-list" id="taskList"></ul>

        <button class="dark-mode-toggle" id="darkModeToggle">🌙 Dark Mode</button>
    </main>

    <script src="app.js"></script>
</body>
</html>

style.css:

:root {
    --bg-primary: #ffffff;
    --bg-secondary: #f5f5f5;
    --text-primary: #1a1a1a;
    --text-secondary: #666666;
    --accent: #4a90d9;
    --accent-hover: #357abd;
    --border: #e0e0e0;
    --danger: #e74c3c;
    --danger-hover: #c0392b;
    --success: #27ae60;
    --radius: 8px;
    --spacing: 1rem;
}

[data-theme="dark"] {
    --bg-primary: #1a1a2e;
    --bg-secondary: #16213e;
    --text-primary: #e0e0e0;
    --text-secondary: #a0a0a0;
    --accent: #4a90d9;
    --accent-hover: #5ba0e9;
    --border: #2a2a4a;
    --danger: #e74c3c;
    --danger-hover: #ff6b6b;
    --success: #2ecc71;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: var(--bg-secondary);
    color: var(--text-primary);
    line-height: 1.6;
    min-height: 100vh;
    padding: var(--spacing);
    transition: background-color 0.3s, color 0.3s;
}

.container {
    max-width: 600px;
    margin: 2rem auto;
    background-color: var(--bg-primary);
    border-radius: var(--radius);
    padding: 2rem;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

header { text-align: center; margin-bottom: 1.5rem; }
header h1 { font-size: 1.8rem; margin-bottom: 0.25rem; }
.task-count { color: var(--text-secondary); font-size: 0.9rem; }

.task-form { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.task-form input { flex: 1; padding: 0.75rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); font-size: 1rem; background-color: var(--bg-secondary); color: var(--text-primary); transition: border-color 0.2s; }
.task-form input:focus { outline: none; border-color: var(--accent); }
.task-form button { padding: 0.75rem 1.5rem; background-color: var(--accent); color: white; border: none; border-radius: var(--radius); font-size: 1rem; cursor: pointer; transition: background-color 0.2s; }
.task-form button:hover { background-color: var(--accent-hover); }

.filters { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.filter-btn { flex: 1; padding: 0.5rem; border: 1px solid var(--border); border-radius: var(--radius); background-color: var(--bg-secondary); color: var(--text-secondary); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; }
.filter-btn.active { background-color: var(--accent); color: white; border-color: var(--accent); }
.filter-btn:hover:not(.active) { border-color: var(--accent); color: var(--accent); }

.task-list { list-style: none; }
.task-item { display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border-bottom: 1px solid var(--border); transition: opacity 0.2s; }
.task-item:last-child { border-bottom: none; }
.task-item.completed .task-text { text-decoration: line-through; color: var(--text-secondary); }
.task-text { flex: 1; font-size: 1rem; }
.task-item button { padding: 0.4rem 0.8rem; border: none; border-radius: var(--radius); cursor: pointer; font-size: 0.8rem; transition: background-color 0.2s; }
.complete-btn { background-color: var(--success); color: white; }
.complete-btn:hover { opacity: 0.8; }
.delete-btn { background-color: var(--danger); color: white; }
.delete-btn:hover { background-color: var(--danger-hover); }

.dark-mode-toggle { display: block; margin: 1.5rem auto 0; padding: 0.5rem 1rem; border: 1px solid var(--border); border-radius: var(--radius); background-color: var(--bg-secondary); color: var(--text-primary); cursor: pointer; font-size: 0.85rem; transition: all 0.2s; }
.dark-mode-toggle:hover { border-color: var(--accent); }

app.js:

const taskForm = document.getElementById('taskForm');
const taskInput = document.getElementById('taskInput');
const taskList = document.getElementById('taskList');
const taskCount = document.getElementById('taskCount');
const darkModeToggle = document.getElementById('darkModeToggle');
const filterButtons = document.querySelectorAll('.filter-btn');

let tasks = JSON.parse(localStorage.getItem('tasks')) || [];
let currentFilter = 'all';

const saveTasks = () => { localStorage.setItem('tasks', JSON.stringify(tasks)); };

const updateTaskCount = () => {
    const completed = tasks.filter(task => task.completed).length;
    taskCount.textContent = `${completed} of ${tasks.length} tasks complete`;
};

const renderTasks = () => {
    taskList.innerHTML = '';
    const filteredTasks = tasks.filter(task => {
        if (currentFilter === 'active') return !task.completed;
        if (currentFilter === 'completed') return task.completed;
        return true;
    });
    filteredTasks.forEach(task => {
        const li = document.createElement('li');
        li.className = `task-item${task.completed ? ' completed' : ''}`;
        li.innerHTML = `
            <span class="task-text">${task.title}</span>
            <button class="complete-btn">${task.completed ? 'Undo' : 'Done'}</button>
            <button class="delete-btn">Delete</button>
        `;
        li.querySelector('.complete-btn').addEventListener('click', () => { task.completed = !task.completed; saveTasks(); renderTasks(); updateTaskCount(); });
        li.querySelector('.delete-btn').addEventListener('click', () => { tasks = tasks.filter(t => t.id !== task.id); saveTasks(); renderTasks(); updateTaskCount(); });
        taskList.appendChild(li);
    });
    updateTaskCount();
};

taskForm.addEventListener('submit', (e) => {
    e.preventDefault();
    const title = taskInput.value.trim();
    if (!title) return;
    tasks.push({ id: Date.now(), title, completed: false });
    saveTasks();
    renderTasks();
    taskInput.value = '';
    taskInput.focus();
});

filterButtons.forEach(btn => {
    btn.addEventListener('click', () => {
        filterButtons.forEach(b => b.classList.remove('active'));
        btn.classList.add('active');
        currentFilter = btn.dataset.filter;
        renderTasks();
    });
});

const initDarkMode = () => {
    const savedTheme = localStorage.getItem('theme');
    if (savedTheme === 'dark') {
        document.body.setAttribute('data-theme', 'dark');
        darkModeToggle.textContent = '☀️ Light Mode';
    }
};

darkModeToggle.addEventListener('click', () => {
    const isDark = document.body.getAttribute('data-theme') === 'dark';
    if (isDark) {
        document.body.removeAttribute('data-theme');
        darkModeToggle.textContent = '🌙 Dark Mode';
        localStorage.setItem('theme', 'light');
    } else {
        document.body.setAttribute('data-theme', 'dark');
        darkModeToggle.textContent = '☀️ Light Mode';
        localStorage.setItem('theme', 'dark');
    }
});

initDarkMode();
renderTasks();
Continue a previous conversation

Claude Code remembers your conversations per directory. If you exit a session and come back later, you can pick up exactly where you left off.

  • Exit the current session if you are still inside one (type exit or press Ctrl+D).
  • Resume your most recent conversation by running this command:
claude -c

What does this flag do?

The -c flag tells Claude Code to continue the most recent conversation in the current directory. Claude picks up with full context of what you discussed and what files were changed.

This is useful when you step away from a project and return later. You do not need to re-explain your project or what you were working on.

  • Ask Claude about the changes it just made by typing:
What changes did you just make?
  • Claude should describe the dark mode feature it implemented, including the files it modified and the approach it took.

Why is this useful?

Conversation continuation means you never lose context. If you close your laptop and come back the next day, claude -c puts you right back in the same conversation. Claude remembers the project state, what was discussed, and what was built.

Use piped input for context

Sometimes you want to send a file directly to Claude for analysis without an interactive session. The -p flag runs a query and prints the response without entering interactive mode.

  • Exit the current session (type exit or press Ctrl+D).
  • Pipe your app.js file into Claude Code for a code review by running this command:
cat app.js | claude -p "review this code and suggest improvements for error handling and edge cases"

What does this command do?

The cat app.js part reads the file content and sends it via a pipe (|) to Claude Code. The -p flag tells Claude to process the query and print the result directly to your terminal, then exit.

This pattern is great for quick analysis tasks: code reviews, documentation checks, or getting explanations of unfamiliar code. No session, no approvals, just a fast answer.

  • Read through Claude's code review suggestions. You will see recommendations for things like input validation, error boundaries, or edge case handling.
  • Notice that the terminal returns to your normal prompt after the response. No interactive session was started.

Three modes, three use cases

  • claude (interactive): Best for multi-step tasks where you iterate with Claude across multiple prompts.
  • claude "task" (initial prompt): Best for well-defined tasks where you can describe the goal in one sentence.
  • claude -p "query" (print mode): Best for quick analysis where you want an answer printed to the terminal with no session.

Getting a permission or authentication error?

If you see an error when running claude -p, make sure you are still in the task-tracker directory. Claude Code uses directory context for authentication and project settings.

Help me fix a Claude Code authentication or permission error

Secret mission

Create a Custom Skill

Claude Code's skill system lets you create reusable commands that execute with dynamic context. In this mission, you'll build a custom skill that turns your git history into a human-readable changelog.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing cloud costs. Decide whether to keep your resources for future experiments, pause by stopping any running sessions, or delete them entirely.

Resources you used:

  • The task-tracker project folder on your Desktop (HTML, CSS, JS, CLAUDE.md, git repo).
  • Claude Code CLI installed via the native installer.
  • Custom changelog skill at ~/.claude/skills/changelog/SKILL.md.
  • Claude Code configuration files (~/.claude and ~/.claude.json).

Keep everything running

No action needed. Choose this if you plan to keep using Claude Code for other projects.

  • Your task-tracker folder stays on your Desktop, ready to open and iterate on.
  • Claude Code remains installed and authenticated. You can run claude in any project directory to start a new session.
  • Your custom changelog skill stays available across all projects.
  • Your Claude Pro (or higher) subscription continues to bill monthly.

Pause - I'll come back to this later

Free up terminal resources while keeping your files intact.

  • If Claude Code is still running in a terminal, exit it by typing exit or pressing Ctrl+D.
  • Your project files, git history, custom skills, and configuration all remain on disk for when you return.
  • To resume later, open Terminal, navigate back to the project folder, and run claude to start a new session (or claude -r to resume a previous conversation).

Delete - I don't want to use this again

Remove all project resources and Claude Code from your machine.

  • Delete the task-tracker project folder by running:
rm -rf ~/Desktop/task-tracker
  • Remove the Claude Code binary and version files by running:
rm -f ~/.local/bin/claude
rm -rf ~/.local/share/claude
  • Remove Claude Code configuration, settings, session history, and custom skills (including your changelog skill) by running:
rm -rf ~/.claude
rm ~/.claude.json

What about your Claude subscription?

Uninstalling Claude Code from your machine does not cancel your Claude Pro, Max, Team, or Enterprise subscription. If you want to manage your subscription, visit your account settings at claude.ai.

Nice Work!

Nice Work!

Nice work! You just built a complete task tracker web application without writing a single line of code yourself.

You've learned how to:

  • Built a multi-file web app (HTML, CSS, and JavaScript) entirely through natural language prompts to Claude Code.
  • Configured Claude Code with a CLAUDE.md file and used plan mode to design your app before building it.
  • Used advanced workflows including one-shot mode, conversation continuation, and piped input to work with Claude Code like a pro.
  • Created a custom Claude Code skill that generates a human-readable changelog from your git history.

Ready to quiz yourself?