Repair a Focus Timer with Claude Code
Use Claude Code to repair and extend a local Focus Timer.
Introduction
30 Second Summary
A polished app can hide a tiny mistake that stops its most important button from working. Confidence comes from finding the cause before making a change.
In this project, you will use Claude Code to investigate, repair, and extend a broken Focus Timer. A repeatable verification check keeps each delegated change grounded in evidence.
What You'll Build
In Safari, your finished timer visibly counts down from 25:00 while a status message tracks the session.
By the end of this project, you'll have:
- A working Focus Timer that counts down from 25:00 when you select Start. Selecting Reset stops the countdown and restores 25:00.
- Visible status feedback that reports ready, running, reset, and complete states.
- A repeatable repair workflow that proves behavior with verify.sh. Git diffs make each file change reviewable. Rules in CLAUDE.md remain available in fresh Claude Code sessions.
- Secret Mission: Trial a high-contrast orange theme. You will inspect its diff. A Claude Code checkpoint restores the saved implementation.
Are there any prerequisites?
You need a Mac running macOS 13.0 or later with internet access.
You also need an active Claude Pro or Max subscription plus basic web coding and terminal familiarity.
Before We Start
Before the hands-on work begins, take a moment to commit to repairing a dependency-free Focus Timer with Claude Code. Naming why a plan-first, test-backed workflow matters gives your work a clear purpose.
Install and Authenticate Claude Code
Your plan-first Focus Timer workflow depends on Claude Code being ready before it touches the project. An unsupported system or an incorrect account would make every later diagnosis unreliable.
This step confirms that your macOS version is compatible. You will also install Claude Code and connect it to your intended Claude Pro or Max subscription.
In this step, get ready to:
- Confirm that your Mac runs a supported macOS version.
- Install Claude Code and review its diagnostics.
- Authenticate with your intended Claude Pro or Max subscription.
Confirm your macOS version
Claude Code requires macOS 13.0 or later. Checking your version first prevents an incompatible installation from blocking the workflow.
- Click the Apple menu in the upper-left corner of your screen.
- Choose About This Mac.
- Read the macOS version shown in the window.
✔️ I see a supported version
Your Mac meets the requirement if the version is 13.0 or later. That compatibility check is complete.
ⓧ I see an older version
Your current macOS version cannot run Claude Code. Your Mac needs a compatible upgrade before you continue.
- Follow Apple's macOS update guide to install a compatible upgrade.
- Return to About This Mac after the upgrade finishes.
- Confirm that the displayed version is 13.0 or later.
Unable to install a compatible upgrade?
Your Mac may not support a recent enough macOS release. Check Apple's compatibility information before continuing with this project.
Ask for help checking whether your Mac supports macOS 13.0 or later.
ⓧ I cannot find the version
The version is also available in your Mac's system information. This route gives you another place to check it.
- Click the Apple menu in the upper-left corner of your screen.
- Choose System Settings.
- Select General in the sidebar.
- Select About.
- Read the macOS version shown on the page.
- Confirm that the displayed version is 13.0 or later.
Install and diagnose Claude Code
The native installer puts the Claude Code command on your Mac. Native installations use the latest release channel by default.
- Press Cmd+Space to open Spotlight.
- Type Terminal into Spotlight.
- Press Return to open macOS Terminal.
- Check whether Claude Code is already installed by running this command:
claude --version
What does this check?
This command asks the installed Claude Code executable to identify itself. A working installation prints a version number followed by (Claude Code).
✔️ I see a Claude Code version
Claude Code is already available from Terminal. You can move on to the diagnostic check below.
ⓧ Command not found
Claude Code is not installed in the current Terminal environment. Anthropic's native installer adds the latest release to your Mac.
- Install Claude Code by running this command:
curl -fsSL https://claude.ai/install.sh | bash
What does the installer do?
This downloads Anthropic's native macOS installer and installs Claude Code on the latest release channel. Native installations receive background updates after setup.
- Check the completed installation by running this command:
claude --version
How does this confirm the install?
A version number followed by (Claude Code) confirms that Terminal can find the newly installed command.
You should now see the Claude Code version in Terminal. The installation is ready for its health check.
Still unable to run Claude Code?
Close the current Terminal window after the installer finishes. Start a fresh Terminal session so it can load the updated command path.
Help me troubleshoot why Claude Code is still unavailable after using the native macOS installer.
Claude Code includes a read-only diagnostic command for checking installation health. It also reports settings and update status without starting an interactive session.
- Review the Claude Code diagnostics by running this command:
claude doctor
What does the diagnostic report cover?
- It checks the Claude Code installation health.
- It reports the settings currently in use.
- It reports the installation's update status.
You should see read-only diagnostic information in Terminal. This confirms that Claude Code can inspect its setup before you start a session.
Seeing a diagnostic problem?
Read the affected diagnostic section for the installation or settings area that needs attention. Avoid changing unrelated system settings.
Help me understand my Claude Code diagnostic result.
Authenticate your subscription
Authentication connects the terminal session to your existing Claude Pro or Max subscription. Subscription usage shares the plan's usage limits.
A quick account check protects you from separate API charges. An active ANTHROPIC_API_KEY overrides subscription authentication and uses API billing.
Running Claude Code starts an interactive terminal session. If authentication is needed, Claude Code sends you through browser prompts before returning you to the session.
- Start a Claude Code session by running this command:
claude
What happens when the session starts?
The command opens Claude Code's interactive prompt in Terminal. Browser authentication appears when the session needs you to sign in.
- Complete the browser prompts with your intended Claude Pro or Max subscription if they appear.
- Return to the Claude Code session in Terminal after authentication completes.
Before you check, which account do you expect Claude Code to report?
- Inspect the active account by entering this command in the Claude Code session:
/status
What does the status check show?
- It shows the active Claude Code version.
- It shows the model and account in use.
- It shows connectivity and session information.
✔️ I see my Pro or Max account
Your intended subscription account is active. Claude Code is installed and authenticated for the Focus Timer workflow.
ⓧ I see the wrong account
The session is authenticated with a different account. Claude Code can switch accounts from inside the current session.
- Start the account switch by entering this command:
/login
What does the login command do?
This starts re-authentication for the current Claude Code session. It lets you select the intended subscription account through the browser.
- Complete the browser login with your intended Claude Pro or Max subscription.
- Check the active account again by entering this command:
/status
What should the second check confirm?
The account shown by the status check should now match your intended Claude Pro or Max subscription.
ⓧ An API key is overriding my subscription
An active ANTHROPIC_API_KEY takes priority over subscription authentication. Remove that override before continuing so this project does not create separate API charges.
- Remove the ANTHROPIC_API_KEY entry from the shell configuration where you originally added it.
- Close the current Terminal window.
- Press Cmd+Space to return to Spotlight.
- Type Terminal into Spotlight.
- Press Return to start a fresh Terminal session.
- Start Claude Code again by running this command:
claude
Why start a fresh session?
A fresh Terminal session reloads your shell configuration. Claude Code can then authenticate without the removed API key override.
- Select your intended subscription account by entering this command:
/login
What does re-authentication change?
The login flow connects the fresh session to the account you select in the browser.
- Complete the browser login with your Claude Pro or Max subscription.
- Confirm the active account by entering this command:
/status
What confirms subscription access?
The status check should show your intended Pro or Max account without an API key override.
Not sure where the API key is configured?
Avoid printing the key or sharing its value. Look for the configuration location where you previously added the environment variable.
Help me safely locate and remove an ANTHROPIC_API_KEY environment variable on macOS without displaying the secret.
- Hide your email address or other personal account details before uploading evidence.
Your supported Mac now has an authenticated Claude Code session ready to work. Next, you will create the Focus Timer and watch its polished interface fail in a reproducible way.
Create the Broken Timer Project
Your Claude Code setup is ready. It now needs a real repository where it can inspect files and run a repeatable check.
A polished interface can still hide a broken interaction. You will build that deliberate failure inside a Git-backed Focus Timer so the next step has concrete evidence to investigate.
In this step, get ready to:
- Create the Focus Timer repository.
- Build the visible timer interface.
- Stage the broken baseline with a failing verification script.
Create the Git-backed project folder
The repository gives every file a shared home. Its staged baseline also gives future changes a precise comparison point.
- Create the project folder with an initialized Git repository by running these commands:
mkdir -p ~/claude-code-focus-timer
cd ~/claude-code-focus-timer
git init
What do these commands do?
- The first command creates the claude-code-focus-timer folder inside your home folder.
- The second command moves your terminal into that folder.
- The final command creates the hidden Git data that tracks your project.
You should see confirmation that Git initialized an empty repository. Your terminal path now ends with claude-code-focus-timer.
Did the repository setup fail?
Check that your terminal prompt is available before retrying the commands. Confirm that the folder path is spelled ~/claude-code-focus-timer.
Ask for help with the setup error here: help me diagnose why I cannot create or initialize this project folder
Visual Studio Code gives you an editor for the four starter files. Opening the repository folder keeps every edit inside the Git baseline.
- Press Cmd+Space to open macOS search.
- Type Visual Studio Code into the search field.
- Press Enter to open Visual Studio Code.
- Use Visual Studio Code's folder picker to select claude-code-focus-timer from your home folder.
The Explorer sidebar should show claude-code-focus-timer as an empty folder. This confirms Visual Studio Code is editing the repository you initialized.
Build the visible timer interface
HTML defines the timer's content. CSS turns that content into the finished card you can see in the browser.
- Select the new-file icon at the top of the Visual Studio Code Explorer sidebar.
- Enter index.html as the file name.
You should see the blank index.html file in the Explorer sidebar. The file is ready for the timer markup.
- Add the timer card to index.html by pasting this markup:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus Timer</title>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<main class="timer-card">
<p class="eyebrow">Deep work</p>
<h1>Focus Timer</h1>
<p id="timerDisplay" class="timer">25:00</p>
<div class="actions">
<button id="startBtn" type="button">Start</button>
<button id="resetBtn" class="secondary" type="button">Reset</button>
</div>
</main>
</body>
</html>
What does this markup do?
- The document loads styles.css for the visual design.
- The deferred app.js script loads after the page structure is available.
- The three element IDs give JavaScript specific controls to find later.
- Save index.html.
- Control-click index.html in the Explorer sidebar.
- Use the available option to reveal the file in Finder.
- Control-click index.html in Finder.
- Choose Safari as the app that opens the file.
Safari should show the Focus Timer heading with a 25:00 display. The page has no card styling yet because its stylesheet is still missing.
Is the timer markup missing?
Confirm that Safari opened the index.html file inside claude-code-focus-timer. Save the file again if the browser shows a blank page.
Ask for help with the page here: help me find why my Focus Timer markup is missing in Safari
- Select the new-file icon at the top of the Visual Studio Code Explorer sidebar.
- Enter styles.css as the file name.
The blank styles.css file should now appear beside index.html.
- Create the page background with its centered card by pasting this first CSS section:
:root {
color-scheme: light;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f4f1ff;
color: #201a35;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background: linear-gradient(145deg, #f8f6ff, #e8e1ff);
}
.timer-card {
width: min(100%, 420px);
padding: 40px;
border: 1px solid #d8cff8;
border-radius: 24px;
background: #ffffff;
box-shadow: 0 24px 60px rgba(64, 45, 110, 0.16);
text-align: center;
}
What does this styling do?
- The root rules establish the page colors with a system-friendly font stack.
- The body rules center the timer against a purple gradient.
- The card rules create the white panel with rounded corners and a soft shadow.
- Save styles.css.
- Refresh the timer page in Safari.
You should see the timer inside a centered white card. The purple gradient now fills the page behind it.
Is the card still unstyled?
Check that styles.css sits beside index.html. Confirm that both file names use lowercase letters.
Ask for help with the stylesheet here: help me diagnose why styles.css is not changing my Focus Timer page
- Add the timer typography beneath the existing CSS by pasting this section:
.eyebrow {
margin: 0 0 8px;
color: #6c55a3;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: 2rem;
}
.timer {
margin: 28px 0;
color: #5636a5;
font-size: clamp(4rem, 18vw, 6rem);
font-weight: 800;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.actions {
display: flex;
justify-content: center;
gap: 12px;
}
How does the timer gain hierarchy?
- The eyebrow becomes a compact label above the heading.
- The timer receives the largest type on the card so the remaining time is easy to scan.
- The action container places both controls on one centered row.
- Save styles.css.
- Refresh the timer page in Safari.
You should see a large purple 25:00 display. The two controls now sit together beneath it.
Is the timer text still small?
Confirm that the CSS selector is .timer. Check that the timer paragraph in index.html uses the matching class.
Ask for help with the typography here: help me compare my timer class with the CSS selector
- Finish the controls beneath the existing CSS by pasting this section:
button {
min-width: 112px;
border: 0;
border-radius: 999px;
padding: 12px 20px;
background: #5636a5;
color: #ffffff;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button:hover {
background: #442786;
}
button.secondary {
background: #ece7fb;
color: #38256d;
}
button.secondary:hover {
background: #ddd4f6;
}
What completes the interface?
- The shared button rules create rounded controls with consistent spacing.
- The primary control uses a dark purple background.
- The secondary control uses a lighter treatment that keeps Start visually prominent.
- Save styles.css.
- Refresh the timer page in Safari.
You should now see a polished timer card with purple Start and pale Reset controls. The interface is ready for its behavior.
Are the controls still plain?
Check that the final button rules sit outside the closing brace for .actions. Save the stylesheet before refreshing Safari again.
Ask for help with the controls here: help me diagnose why my Focus Timer button styles are missing
Add the broken behavior and verifier
JavaScript stores the remaining time and connects the page controls to timer functions. This starter contains the deliberate fault that every learner investigates next.
- Select the new-file icon at the top of the Visual Studio Code Explorer sidebar.
- Enter app.js as the file name.
The blank app.js file should now appear in the Explorer sidebar.
- Add the timer state with its display helpers by pasting this first JavaScript section:
const SESSION_SECONDS = 25 * 60;
let remainingSeconds = SESSION_SECONDS;
let timerId = null;
const timerDisplay = document.getElementById("timerDisplay");
const startButton = document.getElementById("start-button");
const resetButton = document.getElementById("resetBtn");
function formatTime(seconds) {
const secondsPart = seconds % 60;
const minutes = (seconds - secondsPart) / 60;
const paddedSeconds = secondsPart < 10 ? `0${secondsPart}` : `${secondsPart}`;
return `${minutes}:${paddedSeconds}`;
}
function renderTimer() {
timerDisplay.textContent = formatTime(remainingSeconds);
}
What does this JavaScript do?
- The state variables hold the session length and the active timer reference.
- The DOM lookups connect JavaScript variables to elements on the page.
- The formatting helpers convert a number of seconds into the clock text shown by the timer.
- Save app.js.
- Switch back to the macOS Terminal window from earlier.
- Confirm that Git can see the new project files by running:
git status --short
What does this status show?
Git lists the new files as untracked because they have not been staged yet. This confirms that your edits are inside the intended repository.
- Return to app.js in Visual Studio Code.
- Add the timer interactions beneath renderTimer() by pasting this section:
function startTimer() {
if (timerId !== null) {
return;
}
timerId = window.setInterval(() => {
remainingSeconds -= 1;
renderTimer();
if (remainingSeconds === 0) {
window.clearInterval(timerId);
timerId = null;
}
}, 1000);
}
function resetTimer() {
if (timerId !== null) {
window.clearInterval(timerId);
timerId = null;
}
remainingSeconds = SESSION_SECONDS;
renderTimer();
}
startButton.addEventListener("click", startTimer);
resetButton.addEventListener("click", resetTimer);
renderTimer();
How should the timer behave?
- The start function creates a repeating browser timer when no timer is already active.
- The reset function cancels an active timer before restoring the full session length.
- The event listeners connect each page control to its corresponding function.
- Save app.js.
- Return to the Focus Timer page in Safari.
- Refresh the page.
Before you click Start, do you expect the polished interface to respond correctly?
- Click Start.
- Wait for two seconds.
The display remains at 25:00. The missing countdown is the intended obstacle.
- Click Reset.
The display shows 25:00 after Reset. You now have a visible interface with one reproducible failure.
- Select the new-file icon at the top of the Visual Studio Code Explorer sidebar.
- Enter verify.sh as the file name.
The blank verify.sh file should now appear beside the other starter files.
- Add the deterministic project checks to verify.sh by pasting this script:
#!/bin/sh
fail() {
printf 'FAIL: %s\n' "$1"
exit 1
}
require_literal() {
file=$1
text=$2
message=$3
grep -Fq "$text" "$file" || fail "$message"
}
require_literal index.html 'id="startBtn"' 'index.html must define startBtn.'
require_literal app.js 'getElementById("startBtn")' 'app.js must query startBtn.'
require_literal index.html 'id="resetBtn"' 'index.html must define resetBtn.'
require_literal app.js 'getElementById("resetBtn")' 'app.js must query resetBtn.'
require_literal index.html 'id="timerDisplay"' 'index.html must define timerDisplay.'
require_literal app.js 'getElementById("timerDisplay")' 'app.js must query timerDisplay.'
require_literal app.js 'window.setInterval' 'app.js must start a repeating timer.'
if grep -Fq 'getElementById("start-button")' app.js; then
fail 'app.js still contains the incorrect start-button selector.'
fi
printf 'PASS: Focus Timer selectors and required behavior are present.\n'
What does the verifier check?
- The failure helper prints one specific message before returning an unsuccessful exit status.
- The literal checks compare required HTML IDs with the corresponding JavaScript lookups.
- The timer check confirms that the repeating browser behavior is present.
- Save verify.sh.
Use this comparison if you want to confirm every starter file before staging the baseline.
✔️ Awesome, I've got everything!
Your four starter files are ready. Continue to the staging commands below.
ⓧ I'd like to double check the full code
Compare index.html with this complete starter file.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus Timer</title>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<main class="timer-card">
<p class="eyebrow">Deep work</p>
<h1>Focus Timer</h1>
<p id="timerDisplay" class="timer">25:00</p>
<div class="actions">
<button id="startBtn" type="button">Start</button>
<button id="resetBtn" class="secondary" type="button">Reset</button>
</div>
</main>
</body>
</html>
This file defines the visible timer card with its display and two controls.
Compare styles.css with this complete starter file.
:root {
color-scheme: light;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f4f1ff;
color: #201a35;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background: linear-gradient(145deg, #f8f6ff, #e8e1ff);
}
.timer-card {
width: min(100%, 420px);
padding: 40px;
border: 1px solid #d8cff8;
border-radius: 24px;
background: #ffffff;
box-shadow: 0 24px 60px rgba(64, 45, 110, 0.16);
text-align: center;
}
.eyebrow {
margin: 0 0 8px;
color: #6c55a3;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: 2rem;
}
.timer {
margin: 28px 0;
color: #5636a5;
font-size: clamp(4rem, 18vw, 6rem);
font-weight: 800;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.actions {
display: flex;
justify-content: center;
gap: 12px;
}
button {
min-width: 112px;
border: 0;
border-radius: 999px;
padding: 12px 20px;
background: #5636a5;
color: #ffffff;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button:hover {
background: #442786;
}
button.secondary {
background: #ece7fb;
color: #38256d;
}
button.secondary:hover {
background: #ddd4f6;
}
This file creates the complete visual design for the timer card and its controls.
Compare app.js with this complete starter file.
const SESSION_SECONDS = 25 * 60;
let remainingSeconds = SESSION_SECONDS;
let timerId = null;
const timerDisplay = document.getElementById("timerDisplay");
const startButton = document.getElementById("start-button");
const resetButton = document.getElementById("resetBtn");
function formatTime(seconds) {
const secondsPart = seconds % 60;
const minutes = (seconds - secondsPart) / 60;
const paddedSeconds = secondsPart < 10 ? `0${secondsPart}` : `${secondsPart}`;
return `${minutes}:${paddedSeconds}`;
}
function renderTimer() {
timerDisplay.textContent = formatTime(remainingSeconds);
}
function startTimer() {
if (timerId !== null) {
return;
}
timerId = window.setInterval(() => {
remainingSeconds -= 1;
renderTimer();
if (remainingSeconds === 0) {
window.clearInterval(timerId);
timerId = null;
}
}, 1000);
}
function resetTimer() {
if (timerId !== null) {
window.clearInterval(timerId);
timerId = null;
}
remainingSeconds = SESSION_SECONDS;
renderTimer();
}
startButton.addEventListener("click", startTimer);
resetButton.addEventListener("click", resetTimer);
renderTimer();
This file contains the starter state, timer functions, and page event listeners.
Compare verify.sh with this complete starter file.
#!/bin/sh
fail() {
printf 'FAIL: %s\n' "$1"
exit 1
}
require_literal() {
file=$1
text=$2
message=$3
grep -Fq "$text" "$file" || fail "$message"
}
require_literal index.html 'id="startBtn"' 'index.html must define startBtn.'
require_literal app.js 'getElementById("startBtn")' 'app.js must query startBtn.'
require_literal index.html 'id="resetBtn"' 'index.html must define resetBtn.'
require_literal app.js 'getElementById("resetBtn")' 'app.js must query resetBtn.'
require_literal index.html 'id="timerDisplay"' 'index.html must define timerDisplay.'
require_literal app.js 'getElementById("timerDisplay")' 'app.js must query timerDisplay.'
require_literal app.js 'window.setInterval' 'app.js must start a repeating timer.'
if grep -Fq 'getElementById("start-button")' app.js; then
fail 'app.js still contains the incorrect start-button selector.'
fi
printf 'PASS: Focus Timer selectors and required behavior are present.\n'
This script turns the browser failure into a repeatable pass-or-fail check.
- Switch back to the macOS Terminal window from earlier.
- Stage the four starter files by running:
git add index.html styles.css app.js verify.sh
What does staging save?
Git copies the current contents of all four files into its index. Future working-tree edits can now be compared with this trusted baseline.
- Confirm the baseline is staged by running:
git status --short
What should the status show?
Each of the four file names should appear with an added-file marker. This confirms the complete starter bundle is staged.
Before you run the verifier, what do you expect it to report about the unresponsive Start control?
- Run the deterministic verification script:
sh verify.sh
What did the verifier catch?
You should see FAIL: app.js must query startBtn. in the terminal. The verifier found a disagreement between the required page selector and the JavaScript lookup.
This failure is intentional. It gives Claude Code a specific symptom with reproducible evidence to investigate.
Did the verifier show a different result?
Confirm that your terminal is still inside ~/claude-code-focus-timer. Compare all four files with the full-code tab before running the script again.
Ask for help with the unexpected result here: help me compare my starter files with the expected verification failure
That is the designed obstacle working: your timer looks complete while its check exposes a precise failure. Next, you will let Claude Code investigate the repository in plan mode without editing any files.
Investigate in Plan Mode
Your Focus Timer is visible in Safari. The failing verifier now gives you a repeatable symptom to investigate.
Next, you'll use Claude Code in plan mode to inspect the repository before any file changes are allowed. This separates diagnosis from implementation.
Jumping straight to an edit can fix the symptom without proving the cause. A read-only investigation ties the broken Start button to evidence in the repository.
In this step, get ready to:
- Enter Claude Code plan mode inside the Focus Timer repository.
- Investigate the failed Start button with repository evidence.
- Confirm the proposed fix without changing project files.
Enter plan mode
Plan mode creates a read-only boundary for the investigation. You can let Claude inspect the failure while your staged baseline stays untouched.
- Return to the integrated terminal from earlier.
- Start a Claude Code session by running this command:
claude
What does this command do?
Running claude starts an interactive Claude Code session in the current folder. The session uses that folder as the scope for its repository work.
- Confirm the session shows ~/claude-code-focus-timer as its working directory.
- Enter plan mode by running this slash command:
/plan
What Changes in Plan Mode?
Plan mode lets Claude inspect files. It also allows read-only shell commands during exploration.
Source edits stay blocked until you leave plan mode. Your investigation remains separate from the eventual fix.
Claude Code now confirms that plan mode is active.
Ask Claude to investigate
A focused investigation prompt defines the evidence Claude must inspect. It also sets a firm boundary against premature edits.
- Send Claude this investigation prompt:
Investigate why the Start button does nothing. Read all four project files, run sh verify.sh, explain the root cause, and propose the smallest safe fix. Do not edit files yet.
How Does This Prompt Guide Claude?
- Reading index.html reveals the button ID used by the browser.
- Reading app.js reveals the selector connected to the Start behavior.
- Reading verify.sh reveals the selector required by the automated check.
- Reading styles.css completes the requested repository review.
- The no-edit boundary keeps implementation outside this investigation.
- Approve the read-only verifier request if Claude asks for permission.
Claude should reproduce FAIL: app.js must query startBtn. while investigating the repository. Its plan should connect id="startBtn" in index.html with getElementById("start-button") in app.js.
Question the evidence and inspect the diff
A strong plan explains how the evidence proves the root cause. Your follow-up asks Claude to connect the verifier's failure to the incorrect selector.
- Send Claude this follow-up question:
Why did verify.sh catch the issue before the browser exposed the root cause?
Why Did the Verifier Catch It?
The verifier searches app.js for getElementById("startBtn"). That required literal is missing from the current file.
The current code asks for start-button. The fail() function exits with the named selector message.
- Compare Claude's explanation with the selector evidence above.
- Confirm the plan changes only document.getElementById("start-button") in app.js.
- Confirm the replacement is document.getElementById("startBtn").
- Confirm the plan reruns the verifier after the edit.
- Keep plan mode active without approving implementation.
Before you inspect the diff, pause to predict whether plan mode changed any project files.
- Inspect Claude Code's uncommitted-change viewer by running:
/diff
What Should You See?
Claude Code should report no project edits from this investigation. That result proves plan mode separated diagnosis from implementation.
That's the diagnosis locked down. Next, you'll let Claude apply the one-line selector fix. The verifier and Safari will prove the result.
Fix and Verify the Timer
Your investigation found the selector mismatch. Plan mode kept the repository untouched.
Now Claude Code can apply one bounded change against a pass-or-fail command. Independent verification keeps the final judgment in your hands.
In this step, get ready to:
- Approve the focused selector-fix plan.
- Have Claude Code implement the fix with verification evidence.
- Independently prove the repaired timer works.
Move from diagnosis to implementation
The plan gives this edit a narrow scope. The verifier gives the agent a concrete stopping condition.
- Approve the proposed plan in the Claude Code session from earlier.
- Leave plan mode when Claude Code requests permission to implement the change.
Before you send the prompt, predict whether one selector correction is enough for the verifier to pass.
- Ask Claude Code to apply the approved fix by sending this prompt:
Implement the approved minimal fix. Run sh verify.sh after editing. If it fails, inspect the output and keep iterating until it passes. Show me the final verification evidence.
How Does This Close the Loop?
- The prompt limits the implementation to the approved minimal fix.
- The sh verify.sh command gives Claude Code a deterministic success condition.
- The retry instruction keeps the agent working from test evidence.
Claude Code should finish with PASS: Focus Timer selectors and required behavior are present. in its verification evidence.
That closes your first agentic repair loop. The change now has executable evidence behind it.
Did Claude Code Stay in Plan Mode?
- Confirm that the current session has left plan mode.
- Send the implementation prompt again after edits are allowed.
Help me leave plan mode so Claude Code can apply the approved fix.
Check the implementation boundary
A Git diff exposes the exact scope of an edit. This review confirms that Claude Code followed the approved plan.
- Inspect Claude Code's edit by entering:
/diff
What Should This Diff Prove?
The diff should show one changed line in app.js. The startButton query now uses startBtn.
No changes should appear in index.html, styles.css, or verify.sh.
- Use the tabs to confirm whether app.js matches the repaired state.
✔️ Awesome, I've got everything!
Your app.js file contains the single approved selector fix.
ⓧ I'd like to double check the full code
const SESSION_SECONDS = 25 * 60;
let remainingSeconds = SESSION_SECONDS;
let timerId = null;
const timerDisplay = document.getElementById("timerDisplay");
const startButton = document.getElementById("startBtn");
const resetButton = document.getElementById("resetBtn");
function formatTime(seconds) {
const secondsPart = seconds % 60;
const minutes = (seconds - secondsPart) / 60;
const paddedSeconds = secondsPart < 10 ? `0${secondsPart}` : `${secondsPart}`;
return `${minutes}:${paddedSeconds}`;
}
function renderTimer() {
timerDisplay.textContent = formatTime(remainingSeconds);
}
function startTimer() {
if (timerId !== null) {
return;
}
timerId = window.setInterval(() => {
remainingSeconds -= 1;
renderTimer();
if (remainingSeconds === 0) {
window.clearInterval(timerId);
timerId = null;
}
}, 1000);
}
function resetTimer() {
if (timerId !== null) {
window.clearInterval(timerId);
timerId = null;
}
remainingSeconds = SESSION_SECONDS;
renderTimer();
}
startButton.addEventListener("click", startTimer);
resetButton.addEventListener("click", resetTimer);
renderTimer();
Verify the timer independently
Reproducible verification lets you repeat the agent's check outside its implementation flow. The browser test then proves the visible behavior works.
Before you run the verifier, predict whether your independent result will match Claude Code's evidence.
- Run the verifier yourself in a separate shell for ~/claude-code-focus-timer by entering:
sh verify.sh
What Does This Check Prove?
- The literal checks confirm that the HTML IDs match the JavaScript selectors.
- The timer check confirms that window.setInterval remains present.
- The final guard rejects the old start-button selector.
You should see PASS: Focus Timer selectors and required behavior are present. in the terminal.
Does the Verifier Still Fail?
- Confirm that the separate shell is inside ~/claude-code-focus-timer.
- Compare the startButton query with the repaired app.js file above.
Help me diagnose why verify.sh still fails after the startBtn selector fix.
Before testing the browser, predict which display value should appear after the first timer interval.
- Return to the index.html tab in Safari from earlier.
- Refresh the page with Safari's reload control.
- Click Start.
Within one second, you will see the display change from 25:00 to 24:59.
- Click Reset.
You will see the countdown stop. The display returns to 25:00.
Your timer now has a tested repair you inspected yourself. Next, you will preserve the repository's rules for future Claude Code sessions.
Add Persistent Project Context
Your Focus Timer now counts down reliably in Safari. You also have a passing verifier that protects the repaired selector.
A fresh Claude Code conversation loses the task history behind that repair. A concise CLAUDE.md keeps the repository's key rules available after the conversation is cleared.
In this step, get ready to:
- Create concise project instructions in CLAUDE.md.
- Confirm the instructions survive a fresh Claude Code conversation.
- Add a status message with an expanded verification check.
Create persistent project instructions
Project instructions give every Claude Code conversation the same repository-specific guidance. The most useful instructions capture constraints that future changes could easily overlook.
- Initialize a project guide in the Claude Code session from earlier by entering:
/init
What Did This Create?
The command initializes a project guide named CLAUDE.md. Claude Code can load this file as project memory in later conversations.
- Select CLAUDE.md in the Explorer sidebar of Visual Studio Code.
- Replace the generated contents with this focused guide:
# Project
- This is a dependency-free static Focus Timer built with HTML, CSS, and JavaScript.
- Keep visible interface copy concise.
# Workflow
- Run `sh verify.sh` after changing HTML or JavaScript.
- Keep JavaScript selector names aligned with HTML element IDs.
- Do not add a package manager, framework, build tool, or external dependency.
Why Keep the Guide Concise?
The guide preserves the verification command plus the dependency-free constraint. It also tells Claude to protect selector consistency whenever HTML or JavaScript changes.
These rules stay useful across tasks without carrying the full repair conversation forward.
- Save CLAUDE.md.
- Switch back to the Claude Code session.
- Confirm the guide is loaded by entering:
/context
What Should You See?
The context view should list CLAUDE.md under Memory files. This confirms that its instructions are available to the current conversation.
CLAUDE.md Missing From Memory Files?
Confirm that CLAUDE.md is saved directly inside ~/claude-code-focus-timer.
Confirm the Claude Code session is still using that project directory.
help me find why CLAUDE.md is not loading as project memory
Use the guide in a fresh conversation
Clearing the conversation removes its task history. Project memory remains available so the next request can start with the same repository rules.
- Start a fresh conversation by entering:
/clear
What Does Clear Preserve?
The command starts an empty conversation. Project memory remains available to the new conversation.
- Inspect the fresh conversation's loaded context by entering:
/context
What Does This Prove?
Seeing CLAUDE.md under Memory files proves that the guide survived the cleared conversation. Your project now carries its own workflow expectations.
Your active permission mode controls whether Claude asks before editing files or running the verifier.
- Review each permission request before approving it.
- Approve only the requested project edits.
- Approve the verifier run if Claude requests permission.
- Delegate the status feature by entering this prompt:
Add a visible status message for the ready, running, reset, and complete states. Update verify.sh to cover the new status element. Follow the project instructions and show the verification output.
Why This Delegation Works
The prompt defines four visible states plus a verification requirement. The loaded guide supplies the repository constraints without repeating them in the request.
Claude should finish by showing PASS: Focus Timer selectors and required behavior are present. as verification evidence.
Status Change Did Not Pass?
Ask Claude to inspect the verifier output before making another edit. The HTML element ID must match the JavaScript selector exactly.
help me diagnose why the Focus Timer status feature does not pass verify.sh
- Review Claude's uncommitted project changes by entering:
/diff
What Should the Diff Contain?
The diff should add the status element to index.html. It should add the status styling to styles.css.
It should update app.js plus verify.sh. It should not introduce a package manager or an external dependency.
✔️ Awesome, I've got everything!
Your five project files match the finished implementation. Continue to the browser checks below.
ⓧ I'd like to double check the full code
The CLAUDE.md file should read:
# Project
- This is a dependency-free static Focus Timer built with HTML, CSS, and JavaScript.
- Keep visible interface copy concise.
# Workflow
- Run `sh verify.sh` after changing HTML or JavaScript.
- Keep JavaScript selector names aligned with HTML element IDs.
- Do not add a package manager, framework, build tool, or external dependency.
What Does CLAUDE.md Control?
This file gives future Claude Code conversations the project's stable constraints. It keeps verification plus selector consistency central to later changes.
The index.html file should read:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Focus Timer</title>
<link rel="stylesheet" href="styles.css">
<script src="app.js" defer></script>
</head>
<body>
<main class="timer-card">
<p class="eyebrow">Deep work</p>
<h1>Focus Timer</h1>
<p id="timerDisplay" class="timer">25:00</p>
<div class="actions">
<button id="startBtn" type="button">Start</button>
<button id="resetBtn" class="secondary" type="button">Reset</button>
</div>
<p id="statusMessage" class="status">Ready for a 25-minute focus session.</p>
</main>
</body>
</html>
What Changed in the HTML?
The statusMessage element gives the timer a visible place to report its current state. Its initial copy shows that the timer is ready.
The styles.css file should read:
:root {
color-scheme: light;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f4f1ff;
color: #201a35;
}
* {
box-sizing: border-box;
}
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 24px;
background: linear-gradient(145deg, #f8f6ff, #e8e1ff);
}
.timer-card {
width: min(100%, 420px);
padding: 40px;
border: 1px solid #d8cff8;
border-radius: 24px;
background: #ffffff;
box-shadow: 0 24px 60px rgba(64, 45, 110, 0.16);
text-align: center;
}
.eyebrow {
margin: 0 0 8px;
color: #6c55a3;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
}
h1 {
margin: 0;
font-size: 2rem;
}
.timer {
margin: 28px 0;
color: #5636a5;
font-size: clamp(4rem, 18vw, 6rem);
font-weight: 800;
font-variant-numeric: tabular-nums;
line-height: 1;
}
.actions {
display: flex;
justify-content: center;
gap: 12px;
}
button {
min-width: 112px;
border: 0;
border-radius: 999px;
padding: 12px 20px;
background: #5636a5;
color: #ffffff;
font: inherit;
font-weight: 700;
cursor: pointer;
}
button:hover {
background: #442786;
}
button.secondary {
background: #ece7fb;
color: #38256d;
}
button.secondary:hover {
background: #ddd4f6;
}
.status {
min-height: 1.5em;
margin: 22px 0 0;
color: #5f5870;
}
What Changed in the Styles?
The .status rule reserves space beneath the buttons. Its muted color keeps the message readable without competing with the countdown.
The app.js file should read:
const SESSION_SECONDS = 25 * 60;
let remainingSeconds = SESSION_SECONDS;
let timerId = null;
const timerDisplay = document.getElementById("timerDisplay");
const startButton = document.getElementById("startBtn");
const resetButton = document.getElementById("resetBtn");
const statusMessage = document.getElementById("statusMessage");
function formatTime(seconds) {
const secondsPart = seconds % 60;
const minutes = (seconds - secondsPart) / 60;
const paddedSeconds = secondsPart < 10 ? `0${secondsPart}` : `${secondsPart}`;
return `${minutes}:${paddedSeconds}`;
}
function renderTimer() {
timerDisplay.textContent = formatTime(remainingSeconds);
}
function setStatus(message) {
statusMessage.textContent = message;
}
function startTimer() {
if (timerId !== null) {
return;
}
setStatus("Focus session running.");
timerId = window.setInterval(() => {
remainingSeconds -= 1;
renderTimer();
if (remainingSeconds === 0) {
window.clearInterval(timerId);
timerId = null;
setStatus("Session complete. Take a break.");
}
}, 1000);
}
function resetTimer() {
if (timerId !== null) {
window.clearInterval(timerId);
timerId = null;
}
remainingSeconds = SESSION_SECONDS;
renderTimer();
setStatus("Ready for a 25-minute focus session.");
}
startButton.addEventListener("click", startTimer);
resetButton.addEventListener("click", resetTimer);
renderTimer();
What Changed in the Timer Logic?
The setStatus() helper keeps each message update in one place. The start path reports a running session.
The completion path reports a finished session. The reset path restores the ready message.
The verify.sh file should read:
#!/bin/sh
fail() {
printf 'FAIL: %s\n' "$1"
exit 1
}
require_literal() {
file=$1
text=$2
message=$3
grep -Fq "$text" "$file" || fail "$message"
}
require_literal index.html 'id="startBtn"' 'index.html must define startBtn.'
require_literal app.js 'getElementById("startBtn")' 'app.js must query startBtn.'
require_literal index.html 'id="resetBtn"' 'index.html must define resetBtn.'
require_literal app.js 'getElementById("resetBtn")' 'app.js must query resetBtn.'
require_literal index.html 'id="timerDisplay"' 'index.html must define timerDisplay.'
require_literal app.js 'getElementById("timerDisplay")' 'app.js must query timerDisplay.'
require_literal app.js 'window.setInterval' 'app.js must start a repeating timer.'
require_literal index.html 'id="statusMessage"' 'index.html must define statusMessage.'
require_literal app.js 'getElementById("statusMessage")' 'app.js must query statusMessage.'
if grep -Fq 'getElementById("start-button")' app.js; then
fail 'app.js still contains the incorrect start-button selector.'
fi
printf 'PASS: Focus Timer selectors and required behavior are present.\n'
What Changed in the Verifier?
The expanded verifier checks that index.html defines statusMessage. It also checks that app.js queries the same ID.
That pair of checks protects the new status feature from the same selector mismatch that broke the Start button.
Verify every state and stage the baseline
The browser proves that people can see the new behavior. The verifier proves that the required selectors remain present.
- Switch back to the Focus Timer page in Safari.
- Refresh the page.
You should see Ready for a 25-minute focus session. beneath the buttons.
- Click Start.
You should see Focus session running. while the timer counts down.
- Click Reset.
You should see the timer return to 25:00. The ready message should return beneath it.
The complete-state check takes one full 25-minute focus session. That wait exercises the real completion path.
- Click Start again.
- Use the countdown for one focused work session.
- Wait for the countdown to reach zero.
You should see Session complete. Take a break. when the countdown finishes.
Before you run the verifier, do you expect the new status selector checks to pass?
- Run the independent verification from ~/claude-code-focus-timer with this command:
sh verify.sh
What Should the Verifier Report?
You should see PASS: Focus Timer selectors and required behavior are present. in the terminal. This confirms that the original timer checks plus the new status selector checks all pass.
Verifier Still Failing?
Read the named file plus selector in the failure message. Compare that pair against the full-code reference above.
help me trace a failing Focus Timer verification check to the mismatched file
A staged Git baseline gives the upcoming experiment a clean comparison point. Staging records the trusted contents without requiring a commit.
- Stage the finished project by running:
git add .
What Does Staging Preserve?
This updates the Git index with the finished contents of all five project files. Later working-tree edits can be compared against this trusted baseline.
Before you check Git, which five project files do you expect to see in the staged state?
- Inspect the staged state by running:
git status --short
What Should Git Show?
You should see CLAUDE.md, app.js, index.html, styles.css, and verify.sh listed as staged changes.
This staged baseline is ready for a reversible visual experiment.
That closes the loop. Your repository now carries its own instructions while the timer reports every important state.
Secret mission
Secret Mission: Experiment and Rewind Safely
Test a high-contrast orange theme with a larger timer display. Inspect the experiment from two diff views before using a Claude Code checkpoint to restore the saved purple design.
Clean Up Your Resources
Clean Up Your Resources
Your Focus Timer runs entirely on your Mac, so it creates no ongoing project costs. Decide whether to keep the project available, pause your current session, or delete the local files.
Resources you used:
- The ~/claude-code-focus-timer folder contains your Focus Timer files. The folder also contains your local Git baseline.
Keep everything running
No action needed. Choose this if you plan to continue using the timer.
- Keep the ~/claude-code-focus-timer folder in your home folder.
- Reopen index.html in Safari whenever you want to use the timer.
- Start Claude Code from the same folder when you want to extend the project.
- Retain the staged Git baseline for future comparisons.
Pause - I'll come back to this later
Shut down the active apps while keeping the project folder on disk. Your source files remain ready for the next session.
- Click Reset in Safari to stop an active countdown.
- Close the Safari tab displaying index.html.
- Close the Terminal window running Claude Code.
- Leave the ~/claude-code-focus-timer folder in your home folder.
Delete - I don't want to use this again
Removing this folder permanently deletes the Focus Timer files plus their local Git history. Your Claude Code installation remains available for other projects.
- Move outside the project folder before deleting ~/claude-code-focus-timer by running these commands:
cd ~
rm -rf ~/claude-code-focus-timer
What Does This Remove?
The first command moves Terminal to your home folder. The second command permanently removes the project folder with its source files plus its local Git data.
- Check your home folder in Finder for claude-code-focus-timer.
You should no longer see the project folder listed.
Nice Work!
Nice Work!
Outstanding work! Your repaired Focus Timer now counts down from 25:00 in Safari.
You've learned how to:
- Use plan mode in Claude Code to inspect all four project files without changing them. The investigation traced the broken Start button to a mismatched selector.
- Repair the selector mismatch in app.js. Prove the timer behavior with the repeatable verify.sh check. Confirm Start, Reset, and the visible status message in Safari.
- Store persistent project instructions in CLAUDE.md. Review agent changes with Claude Code's diff viewer. Compare the working tree against the staged baseline with Git.
- Secret Mission: Run a high-contrast visual experiment with Claude Code's direct file-editing tools. Inspect the resulting diff. Restore the saved purple theme with a checkpoint.
Ready to quiz yourself?