Build a Focus Sprint Board with Codex
Use OpenAI Codex to build and review a persistent local task board.
Introduction
30 Second Summary
A task list feels reliable while it stays on screen. One refresh can wipe out your plan if the browser has nowhere to save it.
In this project, you will use Codex to build a Focus Sprint Board that preserves tasks across browser refreshes. A review-first workflow keeps you in control through visible diffs plus Git checkpoints.
What You'll Build
Picture refreshing your board in Microsoft Edge and seeing every task return with its completion state intact.
By the end of this project, you'll have:
- A responsive Focus Sprint Board where you add focused tasks. You can mark each task complete or delete it while the progress count updates.
- Reliable browser persistence that restores your task text after a refresh. Each completion state returns too.
- A repeatable review-first Codex workflow that starts with a reproduced failure. You inspect the proposed change before preserving the working result with Git.
- Secret Mission: Add All, Active, and Done filters without changing the saved task data.
Are there any prerequisites?
You need a Windows device, internet access, a ChatGPT account, Visual Studio Code, and Git.
Codex desktop access must be available through the Free rollout or an eligible paid ChatGPT plan.
Before We Start
Before any files change, define the outcome you want from your Focus Sprint Board. This gives your Codex prompts, visible diff reviews, browser checks, bug reports, and Git checkpoints a clear purpose.
Set Up Codex and the Local Workspace
Before Codex edits local files, it needs a clear folder boundary. A predictable local server also gives the board a stable place to run.
Python provides that server for Microsoft Edge. Git gives you checkpoints for every working version.
Visual Studio Code keeps the files and terminal together. Your approved Codex workspace limits the agent to the project folder.
In this step, get ready to:
- Confirm Codex access in the ChatGPT desktop app.
- Install Python 3.14.7 and verify Microsoft Edge.
- Create an empty local Git repository that Visual Studio Code and Codex share.
Confirm Codex desktop access
The ChatGPT desktop app gives Codex access to approved local folders. Confirming access now prevents you from starting a workflow that your current account cannot finish.
This is the project's only account sign-in. You never enter an API key.
- Visit OpenAI's Windows quickstart.
- Download the ChatGPT desktop app for Windows.
- Run the downloaded installer.
- Press the Windows key to open Windows search.
- Type ChatGPT into the search box.
- Press Enter to open the desktop app.
- Sign in with your confirmed ChatGPT account.
- Select Codex in the desktop app.
✔️ Codex is available
Your account has Codex desktop access. The agent is ready to receive a scoped local folder.
ⓧ Codex is not visible
Free desktop access is subject to rollout. An eligible paid ChatGPT plan is required when Codex is unavailable for the current account.
- Confirm the desktop app is signed in with the ChatGPT account you intend to use.
- Review the current access options on the official Codex pricing page.
- Continue only after Codex appears in the desktop app.
Need Help Checking Access?
Describe what appears in the desktop app without sharing account details. Help me check whether my ChatGPT desktop app has Codex access.
Install Python and verify Microsoft Edge
The Python Install Manager controls which Python runtimes are available on Windows. This project uses manager version 26.3 with the stable Python 3.14.7 runtime.
- Press the Windows key to open Windows search.
- Type Visual Studio Code into the search box.
- Press Enter to open Visual Studio Code.
- Press Ctrl+` to open the integrated terminal.
- Check the installed Python runtimes by running this command:
py list
What Does This Command Do?
- The py list command asks the Python Install Manager for its installed runtimes.
- The result determines whether you can continue or need to install the required runtime.
Choose the tab that matches your terminal output.
✔️ I see version 3.14.7 or higher
Python is ready. Your runtime list includes Python 3.14.7.
ⓧ I see an older version
The manager is available. The required Python runtime still needs to be added.
The first runtime installation may offer an optional PATH update. This project uses the py command, so the extra alias is optional.
- Install the latest stable Python 3.14 runtime and list the result by running these commands:
py install 3.14
py list
What Do These Commands Do?
- The first command asks the manager for the latest stable release in the Python 3.14 family.
- The second command should list Python 3.14.7 after installation finishes.
ⓧ Command not found
The Python Install Manager needs to be installed before it can add the runtime.
- Visit the official Python downloads page.
- Download Python Install Manager 26.3 for Windows.
- Run the downloaded installer.
- Select Install in the installer.
- Return to Visual Studio Code.
- Press Ctrl+Shift+` to create a fresh integrated terminal.
- Install the stable Python runtime and list the result by running these commands:
py install 3.14
py list
What Do These Commands Do?
- The first command installs the latest stable Python 3.14 runtime through the manager.
- The second command should list Python 3.14.7 as an installed runtime.
Python Still Missing From the Terminal?
Close the current terminal before creating a fresh one because an existing terminal may not detect a new installation. Help me diagnose my Python Install Manager setup.
Share the non-sensitive terminal output in the NextWork community if you want another set of eyes.
Microsoft Edge is the browser where you will verify every visible board change. Opening it once confirms the browser is ready before the local server starts.
- Press the Windows key to open Windows search.
- Type Microsoft Edge into the search box.
- Press Enter to open the browser.
✔️ Microsoft Edge opens
Microsoft Edge is installed and available for local browser verification.
ⓧ Microsoft Edge is unavailable
- Visit the official Microsoft Edge download page.
- Download the Windows installer.
- Run the downloaded installer.
- Press the Windows key after installation finishes.
- Type Microsoft Edge into the search box.
- Press Enter to verify that the browser opens.
Create the scoped Git workspace
A workspace is the folder that Visual Studio Code treats as one project. The same folder becomes Codex's approved file boundary.
Why Scope Codex to One Folder?
Codex limits local file access through its sandbox and approval policy. Selecting only focus-sprint-board keeps this project separate from unrelated files.
- Return to Visual Studio Code.
- Select File from the top menu.
- Select Open Folder.
- Select your Desktop in the folder picker.
- Select New Folder.
- Enter focus-sprint-board as the folder name.
- Press Enter to create the folder.
Visual Studio Code may show a workspace trust prompt after the folder opens. This empty folder belongs to you.
- Select Select Folder.
- Approve the new workspace if Visual Studio Code asks whether you trust it.
- Press Ctrl+` to open the integrated terminal inside focus-sprint-board.
Before you run these checks, which Git and Python versions do you expect the terminal to report?
- Verify both command-line tools by running these commands:
git --version
py list
What Should You See?
- The first line reports an installed Git version.
- The Python runtime list includes Python 3.14.7.
Those two checks pass in the project terminal. Your editor can now run both the checkpoint workflow and the local server.
An empty Git repository gives the folder local checkpoint history. Everything remains on your device.
Before you initialize it, where do you expect Git to place its repository data?
- Initialize the current focus-sprint-board folder by running this command:
git init
What Does This Command Do?
The git init command creates an empty Git repository inside the current folder. The repository is ready for local commits without uploading the project.
- Select Source Control in the Visual Studio Code sidebar.
- Confirm Visual Studio Code recognizes the empty repository with no project files waiting to be committed.
Git Repository Not Detected?
Confirm the terminal path ends with focus-sprint-board before initializing the repository. Help me check why Visual Studio Code does not detect my Git repository.
Codex now needs the same folder that Visual Studio Code uses. Matching folder names proves that both tools share one project boundary.
Before you select the workspace, what folder name should Codex show when the scope is correct?
- Switch back to the ChatGPT desktop app.
- Select Open folder from the Codex start screen.
- Choose your Desktop in the folder picker.
- Select the focus-sprint-board folder.
- Approve local folder access when Codex asks.
- Confirm Codex names focus-sprint-board as the active workspace.
That completes the shared workspace. Codex can work inside the empty project while Visual Studio Code tracks every local change.
Codex Showing the Wrong Folder?
Use the folder picker again to select focus-sprint-board directly from your Desktop. Help me scope Codex to the correct local project folder.
✔️ My workspace is ready
Great work. Your browser and command-line tools are ready inside a local project that Codex can access safely.
ⓧ I'd like to double check the setup
Check each end state before you continue:
- Confirm Microsoft Edge opens for local browser verification.
- Confirm the Python runtime list includes Python 3.14.7 through the Windows Python Install Manager.
- Confirm Visual Studio Code recognizes focus-sprint-board as an empty local Git repository.
- Confirm Codex names focus-sprint-board as its approved workspace.
Your tools are verified and your project boundary is set. Next, you'll direct Codex to build the first working version of the Focus Sprint Board.
Build the First Sprint Board
Your empty Git repository gives Codex a safe boundary for changing local files. This step turns that blank workspace into a working task board.
A small in-memory prototype gives you an immediate result. It also creates the limitation that you will investigate in the next step.
In this step, get ready to:
- Direct Codex with an outcome-based prompt for the first prototype.
- Review the proposed files before serving the board locally.
- Test every interaction before creating a Git checkpoint.
Prompt Codex for the prototype
An outcome-based prompt defines what success looks like before discussing implementation. Clear boundaries keep the first version focused on the behavior you need to test.
- Switch back to Codex in the ChatGPT desktop app.
- State the goal of creating index.html, styles.css, and app.js for a Focus Sprint Board.
- Define the required behaviors as adding tasks, completing tasks, deleting tasks, and updating the progress count.
- Request a responsive interface that works on desktop and narrow screens.
- Set the boundaries to plain browser files with no frameworks or external dependencies.
- Specify that this first version must keep task data in the tasks array in memory.
- Require the DOM references taskForm, taskInput, taskList, progressText, formMessage, and emptyState.
- Ask Codex to show a file summary and diff before you accept any edits.
Why Plain Browser Files?
Plain HTML defines the board structure. CSS controls its responsive appearance.
JavaScript handles every interaction without a framework. This keeps your attention on directing and reviewing Codex.
Codex only needs permission to edit the approved focus-sprint-board folder. Its access remains scoped to the project you selected earlier.
- Send the completed prompt to Codex.
- Approve file edits inside focus-sprint-board if Codex requests permission.
You should see a summary covering all three files. You should also see a proposed diff for the new board.
- Leave the proposed edits pending while you review them in the next substep.
Codex Proposed the Wrong Scope?
- Confirm that Codex still shows focus-sprint-board as its workspace.
- Restate that the prototype must use only the three named files.
- Restate that task data must stay in memory for this version.
- Ask Codex to revise the proposal without applying its current edits.
- Get targeted help with the proposal through help me tighten my Codex prompt.
Review the diff and serve the board
A diff shows every proposed addition before it reaches your working files. Reviewing it keeps you responsible for the behavior Codex creates.
- Inspect the file summary in Codex.
- Review the proposed diff for index.html.
- Confirm that index.html references styles.css and loads app.js with defer.
- Review the proposed diff for styles.css.
- Confirm that app.js defines updateProgress(), createTaskItem(), and renderTasks().
- Confirm that the initial task state is let tasks = [];.
- Approve the three file edits after the diff matches the requested scope.
The board now has a complete browser interface. A local HTTP server gives it a stable address for testing in the browser.
- Return to the Visual Studio Code terminal from the previous step.
- Start the Python development server from focus-sprint-board by running this command:
python -m http.server --bind 127.0.0.1
What Does This Command Do?
Python serves the current folder over local HTTP. The server binds to 127.0.0.1 so the board is available only from your computer.
The terminal prints the localhost address for the server. Keep this terminal running while you test the board.
Before you open the address, what page elements do you expect a successful three-file build to show? Hold that prediction.
- Enter the localhost address printed by Python into Microsoft Edge.
You should see the Focus Sprint Board heading above a task input. You should also see the Add task button, the progress count, and the empty-state message.
Board Not Loading Correctly?
- Check that the server command ran from the focus-sprint-board folder.
- Confirm that index.html sits beside styles.css and app.js.
- Check the Codex diff for the exact stylesheet and script references shown below.
- Get help diagnosing the page through help me debug my local sprint board.
✔️ Awesome, I've got everything!
Your board is loading from the Python server with its layout, task form, progress count, and empty state visible.
- Keep the server terminal running for the interaction test.
ⓧ I'd like to double check the full code
Compare each generated file with the reference version below.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Focus Sprint Board</title>
<link rel="stylesheet" href="styles.css" />
<script src="app.js" defer></script>
</head>
<body>
<main class="app-shell">
<header class="hero">
<p class="eyebrow">BUILD WITH CODEX</p>
<h1>Focus Sprint Board</h1>
<p class="subtitle">Turn your next hour into a short, visible plan.</p>
</header>
<section class="task-card" aria-labelledby="tasks-heading">
<div class="section-heading">
<h2 id="tasks-heading">Sprint tasks</h2>
<p id="progress-text">0 of 0 complete</p>
</div>
<form id="task-form">
<label class="sr-only" for="task-input">Task description</label>
<input
id="task-input"
name="task"
type="text"
maxlength="80"
autocomplete="off"
placeholder="Add one focused task"
required
/>
<button type="submit">Add task</button>
</form>
<p id="form-message" class="form-message" role="status"></p>
<ul id="task-list" class="task-list"></ul>
<p id="empty-state" class="empty-state">
Your sprint is clear. Add the first task.
</p>
</section>
</main>
</body>
</html>
This file defines every visible part of the board. Its element IDs connect the interface to JavaScript.
:root {
color-scheme: light;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
"Segoe UI", sans-serif;
color: #172033;
background: #eef2ff;
font-synthesis: none;
}
* {
box-sizing: border-box;
}
body {
min-width: 320px;
min-height: 100vh;
margin: 0;
background:
radial-gradient(circle at top left, #dbeafe 0, transparent 34rem),
linear-gradient(145deg, #f8fafc, #eef2ff);
}
button,
input {
font: inherit;
}
button {
cursor: pointer;
}
.app-shell {
width: min(760px, calc(100% - 2rem));
margin: 0 auto;
padding: 4rem 0;
}
.hero {
margin-bottom: 1.5rem;
}
.eyebrow {
margin: 0 0 0.5rem;
color: #4f46e5;
font-size: 0.78rem;
font-weight: 800;
letter-spacing: 0.16em;
}
h1,
h2,
p {
margin-top: 0;
}
h1 {
margin-bottom: 0.5rem;
font-size: clamp(2.2rem, 7vw, 4.4rem);
line-height: 0.98;
letter-spacing: -0.045em;
}
.subtitle {
max-width: 34rem;
margin-bottom: 0;
color: #526078;
font-size: 1.08rem;
}
.task-card {
padding: 1.5rem;
border: 1px solid rgba(148, 163, 184, 0.32);
border-radius: 1.5rem;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0 24px 70px rgba(51, 65, 85, 0.16);
backdrop-filter: blur(12px);
}
.section-heading {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}
.section-heading h2 {
margin-bottom: 0;
font-size: 1.25rem;
}
#progress-text {
margin-bottom: 0;
color: #64748b;
font-size: 0.9rem;
font-weight: 700;
}
#task-form {
display: grid;
grid-template-columns: 1fr auto;
gap: 0.75rem;
}
#task-input {
width: 100%;
padding: 0.85rem 1rem;
border: 1px solid #cbd5e1;
border-radius: 0.85rem;
color: #172033;
background: #ffffff;
}
#task-input:focus {
border-color: #6366f1;
outline: 3px solid rgba(99, 102, 241, 0.18);
}
#task-form button {
padding: 0.85rem 1.1rem;
border: 0;
border-radius: 0.85rem;
color: #ffffff;
background: #4f46e5;
font-weight: 800;
}
#task-form button:hover {
background: #4338ca;
}
.form-message {
min-height: 1.4rem;
margin: 0.45rem 0 0;
color: #b91c1c;
font-size: 0.9rem;
}
.task-list {
display: grid;
gap: 0.75rem;
margin: 0;
padding: 0;
list-style: none;
}
.task-item {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.9rem 1rem;
border: 1px solid #e2e8f0;
border-radius: 1rem;
background: #f8fafc;
}
.task-label {
display: flex;
flex: 1;
align-items: center;
gap: 0.75rem;
min-width: 0;
}
.task-label input {
width: 1.1rem;
height: 1.1rem;
accent-color: #4f46e5;
}
.task-text {
overflow-wrap: anywhere;
}
.task-item.completed .task-text {
color: #7c879a;
text-decoration: line-through;
}
.delete-button {
padding: 0.45rem 0.7rem;
border: 1px solid #fecaca;
border-radius: 0.7rem;
color: #b91c1c;
background: #fff1f2;
font-weight: 700;
}
.delete-button:hover {
background: #ffe4e6;
}
.empty-state {
margin: 0;
padding: 1.4rem;
border: 1px dashed #cbd5e1;
border-radius: 1rem;
color: #64748b;
text-align: center;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
[hidden] {
display: none !important;
}
@media (max-width: 560px) {
.app-shell {
padding: 2rem 0;
}
.task-card {
padding: 1rem;
}
.section-heading,
#task-form {
grid-template-columns: 1fr;
}
.section-heading {
display: grid;
}
#task-form button {
width: 100%;
}
}
This stylesheet creates the card layout, task states, and responsive form. The media query adapts the interface for narrow screens.
const taskForm = document.querySelector("#task-form");
const taskInput = document.querySelector("#task-input");
const taskList = document.querySelector("#task-list");
const progressText = document.querySelector("#progress-text");
const formMessage = document.querySelector("#form-message");
const emptyState = document.querySelector("#empty-state");
let tasks = [];
function updateProgress() {
const completedCount = tasks.filter((task) => task.completed).length;
progressText.textContent = `${completedCount} of ${tasks.length} complete`;
}
function createTaskItem(task) {
const item = document.createElement("li");
item.className = task.completed ? "task-item completed" : "task-item";
const label = document.createElement("label");
label.className = "task-label";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = task.completed;
checkbox.setAttribute("aria-label", `Mark ${task.text} complete`);
checkbox.addEventListener("change", () => {
task.completed = checkbox.checked;
renderTasks();
});
const taskText = document.createElement("span");
taskText.className = "task-text";
taskText.textContent = task.text;
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.className = "delete-button";
deleteButton.textContent = "Delete";
deleteButton.setAttribute("aria-label", `Delete ${task.text}`);
deleteButton.addEventListener("click", () => {
tasks = tasks.filter((candidate) => candidate.id !== task.id);
renderTasks();
});
label.appendChild(checkbox);
label.appendChild(taskText);
item.appendChild(label);
item.appendChild(deleteButton);
return item;
}
function renderTasks() {
taskList.textContent = "";
tasks.forEach((task) => {
taskList.appendChild(createTaskItem(task));
});
emptyState.hidden = tasks.length > 0;
updateProgress();
}
taskForm.addEventListener("submit", (event) => {
event.preventDefault();
const text = taskInput.value;
if (!text) {
formMessage.textContent = "Enter a task before adding.";
return;
}
tasks.push({
id: Date.now(),
text,
completed: false,
});
renderTasks();
taskForm.reset();
formMessage.textContent = "";
});
renderTasks();
This script stores tasks in memory and renders the current array. Its event listeners add tasks, toggle completion, and delete tasks by ID.
Test the interactions and create a checkpoint
Before you test the controls, predict how the progress count should change as two tasks move through the board. Keep that sequence in mind.
- Enter Plan focus block in the task input.
- Click Add task.
You should see the new task in the list. The progress count should read 0 of 1 complete.
- Enter Review sprint notes in the task input.
- Click Add task.
You should see both tasks. The progress count should now read 0 of 2 complete.
- Select the checkbox beside Plan focus block.
You should see a line through the completed task. The progress count should change to 1 of 2 complete.
- Click Delete beside Review sprint notes.
You should see the second task disappear immediately. The progress count should now read 1 of 1 complete.
That first working slice is complete. Your board now responds to every required interaction in the browser.
- Return to Visual Studio Code.
- Select Source Control in the left sidebar.
- Stage index.html.
- Stage styles.css.
- Stage app.js.
- Enter Build working task board in the commit message field.
- Create the commit from Source Control.
- Select Source Control Graph to confirm the new commit.
You should see Build working task board as the latest local commit. Your tested prototype now has a restore point.
Your first sprint board works in memory and has a Git checkpoint. Next, you will test what happens when the browser reloads the app.
Reproduce the Refresh Failure
Your working Focus Sprint Board handles each task interaction during one browser session.
Its Git checkpoint gives you a safe baseline before investigating a reload. The current task state has only been tested while the page stays loaded.
A controlled refresh gives Codex concrete evidence for a focused diagnosis.
In this step, get ready to:
- Create a controlled refresh test with two tasks.
- Give Codex the reproduction evidence without allowing file edits.
- Approve a persistence plan that preserves the existing behavior.
Reproduce the data loss
A refresh reruns the startup path in app.js. Testing that boundary shows whether the board can rebuild the browser state you created.
- Switch back to the Microsoft Edge tab from earlier.
- Enter Protect focus block in the field showing Add one focused task.
- Select Add task.
- Enter Review sprint plan in the task field.
- Select Add task.
- Select the checkbox beside Review sprint plan.
Before you refresh, do you think both task rows will return with the checkbox in the same state?
- Refresh the current page with the circular control beside the Microsoft Edge address bar.
You'll see both task rows disappear. The progress count returns to 0 of 0 complete.
The completed checkbox state disappears with the task text. You now have a reproducible shortfall that Codex can investigate.
Give Codex a reproducible bug report
A reproducible bug report gives Codex evidence it can trace. Your report links the starting state to the refresh result.
- Switch back to the Codex workspace from earlier.
- Describe the starting state as two visible tasks with one marked complete.
- Describe the observed result as both tasks disappearing after a refresh.
- Direct Codex to inspect app.js.
- Require Codex to preserve the existing HTML structure.
- Require Codex to preserve the existing styling.
The request boundary keeps this investigation separate from implementation. Codex should reason from the evidence before it proposes any file change.
- Ask Codex to explain the cause of the refresh failure.
- Ask Codex for the smallest persistence plan.
- Set the request boundary to analysis only with no file edits.
- Submit the request to Codex.
Codex should trace the symptom to let tasks = [] recreating an empty array whenever the page loads. Its response should propose browser persistence without changing any file.
Why Use Analysis Only?
Analysis-only keeps the project files at their known Git checkpoint while Codex reasons from the evidence. The proposed plan becomes a review boundary for the next change.
Review the persistence plan before code changes
A focused plan protects the behavior you already tested. Each proposed change should address the refresh failure without replacing the board.
- Confirm the plan preserves the task shape { id, text, completed }.
- Confirm the plan retains the current add behavior.
- Confirm the plan retains the current checkbox behavior.
- Confirm the plan retains the current delete behavior.
- Confirm the plan defines STORAGE_KEY with the value focus-sprint-tasks-v1.
- Confirm the plan adds a focused loadTasks() function.
- Confirm the plan adds a focused saveTasks() function.
The storage steps should cover every place where task state changes. This keeps the fix narrow enough to verify with the same refresh test.
- Confirm the plan uses localStorage inside the focused persistence functions.
- Confirm the plan initializes tasks from loadTasks().
- Confirm saveTasks() runs after a task is added.
- Confirm saveTasks() runs after a checkbox change.
- Confirm saveTasks() runs after a task is deleted.
- Confirm the plan verifies task text after a refresh.
- Confirm the plan verifies completion state after a refresh.
- Ask Codex for an analysis-only revision if any checklist item is missing.
Before you inspect the project, do you expect Codex's analysis-only request to have changed any files?
- Switch back to Visual Studio Code from earlier.
- Select Source Control from the left sidebar.
- Check whether any changed files are listed.
- Open Source Control Graph from the same view.
- Check the latest local checkpoint.
- Return to the Codex response from earlier.
You should see no pending file changes. The latest checkpoint remains Build working task board.
The Codex response should contain the accepted persistence plan. Your prototype remains unchanged until you approve implementation in the next step.
Did Codex Edit a File?
- Reject the unexpected edits in the Codex change review.
- Return to Visual Studio Code Source Control to confirm the changed-file list is empty.
- Help me restore the analysis-only checkpoint.
You turned a disappearing task into a reproducible case while keeping every file unchanged. Next, you'll apply the narrow persistence change before repeating the same refresh test.
Persist Sprint Tasks
You have already reproduced the refresh failure and given Codex enough evidence to diagnose it. The approved plan now has one focused goal: preserve task state without changing the board’s existing behavior.
The board’s visible interactions are already proven. This step adds localStorage plus JSON serialization so the browser can restore task text and completion state after a refresh.
In this step, get ready to:
- Approve the focused persistence plan in Codex.
- Review the proposed app.js diff before accepting it.
- Verify persistence across refreshes and create a Git checkpoint.
Implement the approved persistence plan
Browser storage saves strings for a specific origin. The task array must be converted to JSON when it changes and parsed back into an array when the page loads.
- Switch back to the Codex workspace from earlier.
- Approve implementation of the analysis-only persistence plan.
- Limit the proposed edit to app.js.
- Preserve the existing task object shape.
- Preserve the current add, complete, and delete behavior.
- Require the approved focus-sprint-tasks-v1 storage key plus the loadTasks() and saveTasks() functions.
- Require every task change to call saveTasks() before renderTasks().
- Ask Codex to present the proposed change as a diff for review.
You should see a proposed diff limited to app.js.
Why store the tasks as JSON?
Browser storage accepts string values. JSON converts the task array into one string while preserving each task’s identifier, text, and completion value.
Loading reverses that conversion before the first render. The board can then rebuild the same task list after a refresh.
Review and accept the persistence diff
Code review is the control point between an agent’s suggestion and your working project. A small diff makes every new storage decision visible before it reaches the app.
- Compare the first section of the proposed app.js diff with this reference:
const STORAGE_KEY = "focus-sprint-tasks-v1";
const taskForm = document.querySelector("#task-form");
const taskInput = document.querySelector("#task-input");
const taskList = document.querySelector("#task-list");
const progressText = document.querySelector("#progress-text");
const formMessage = document.querySelector("#form-message");
const emptyState = document.querySelector("#empty-state");
let tasks = loadTasks();
function loadTasks() {
const savedTasks = localStorage.getItem(STORAGE_KEY);
if (savedTasks === null) {
return [];
}
try {
const parsedTasks = JSON.parse(savedTasks);
return Array.isArray(parsedTasks) ? parsedTasks : [];
} catch {
return [];
}
}
function saveTasks() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
What does this code do?
- The STORAGE_KEY constant gives this board one stable place to store its task array.
- The loadTasks() function returns an empty array when no saved value exists.
- The parsing check accepts only an array. Invalid or unexpected saved values fall back to an empty task list.
- The saveTasks() function serializes the current task array before writing it to browser storage.
- Confirm let tasks = loadTasks(); replaces the original empty array.
- Confirm the checkbox change calls saveTasks() before renderTasks().
- Confirm the delete handler calls saveTasks() before renderTasks().
- Confirm the submit handler calls saveTasks() before renderTasks().
- Confirm index.html and styles.css are absent from the diff.
- Accept the proposed app.js edit.
That control point is cleared. Codex has added the persistence layer without replacing the interactions you already tested.
Verify persistence and create a checkpoint
Persistence is proven by changing state and reloading the same local origin. The task must return with the same text and completion value.
- Switch back to the Focus Sprint Board in Microsoft Edge.
- Add a task named Protect focus block.
- Select the task’s checkbox to mark it complete.
Before you refresh, take a moment to predict whether the completed checkbox will return selected.
- Refresh the page in Microsoft Edge.
You’ll see Protect focus block return with its checkbox selected. That closes the refresh gap you reproduced earlier.
- Add a second task named Delete this test task.
- Delete Delete this test task from the board.
Before the second refresh, predict whether the deleted test task will return.
- Refresh the page in Microsoft Edge again.
The deleted test task stays absent. Protect focus block remains completed.
Task state missing after refresh?
- Confirm you refreshed the same local address served by the Python server from earlier.
- Check that each task-changing handler calls saveTasks() before renderTasks().
- Check that STORAGE_KEY contains focus-sprint-tasks-v1 everywhere it is used.
- Help me debug persistence after refresh.
The browser test proves the new runtime behavior. A local checkpoint now preserves the reviewed persistence change separately from the original prototype.
- Return to the Source Control sidebar in Visual Studio Code.
- Stage app.js.
- Enter Persist sprint tasks as the commit message.
- Create the commit from the Source Control panel.
Before you check the history, predict how many project checkpoints should now be visible.
- Open Source Control Graph to verify the local history.
You’ll see Build working task board followed by Persist sprint tasks. Your working prototype and persistence change now have separate recovery points.
The persistence checkpoint should now match the reference below.
✔️ Awesome, I've got everything!
Your completed task survives refreshes. Your Git history also contains both project checkpoints.
ⓧ I'd like to double check the full code
- Compare your complete app.js file with this reference:
const STORAGE_KEY = "focus-sprint-tasks-v1";
const taskForm = document.querySelector("#task-form");
const taskInput = document.querySelector("#task-input");
const taskList = document.querySelector("#task-list");
const progressText = document.querySelector("#progress-text");
const formMessage = document.querySelector("#form-message");
const emptyState = document.querySelector("#empty-state");
let tasks = loadTasks();
function loadTasks() {
const savedTasks = localStorage.getItem(STORAGE_KEY);
if (savedTasks === null) {
return [];
}
try {
const parsedTasks = JSON.parse(savedTasks);
return Array.isArray(parsedTasks) ? parsedTasks : [];
} catch {
return [];
}
}
function saveTasks() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
function updateProgress() {
const completedCount = tasks.filter((task) => task.completed).length;
progressText.textContent = `${completedCount} of ${tasks.length} complete`;
}
function createTaskItem(task) {
const item = document.createElement("li");
item.className = task.completed ? "task-item completed" : "task-item";
const label = document.createElement("label");
label.className = "task-label";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = task.completed;
checkbox.setAttribute("aria-label", `Mark ${task.text} complete`);
checkbox.addEventListener("change", () => {
task.completed = checkbox.checked;
saveTasks();
renderTasks();
});
const taskText = document.createElement("span");
taskText.className = "task-text";
taskText.textContent = task.text;
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.className = "delete-button";
deleteButton.textContent = "Delete";
deleteButton.setAttribute("aria-label", `Delete ${task.text}`);
deleteButton.addEventListener("click", () => {
tasks = tasks.filter((candidate) => candidate.id !== task.id);
saveTasks();
renderTasks();
});
label.appendChild(checkbox);
label.appendChild(taskText);
item.appendChild(label);
item.appendChild(deleteButton);
return item;
}
function renderTasks() {
taskList.textContent = "";
tasks.forEach((task) => {
taskList.appendChild(createTaskItem(task));
});
emptyState.hidden = tasks.length > 0;
updateProgress();
}
taskForm.addEventListener("submit", (event) => {
event.preventDefault();
const text = taskInput.value.trim();
if (!text) {
formMessage.textContent = "Enter a task before adding.";
taskInput.focus();
return;
}
tasks.push({
id: Date.now(),
text,
completed: false,
});
saveTasks();
renderTasks();
taskForm.reset();
formMessage.textContent = "";
taskInput.focus();
});
renderTasks();
Your board now restores task text and completion state after every refresh. Next, you’ll review the agent-generated app against failure cases and tighten its user feedback.
Harden the Task Board
Your persistence checkpoint proved that the board can restore task text after a refresh. It also restores each task's completion state.
A working Codex change still needs human review. Malformed saved data can stop the board during startup.
Blank input can clutter the sprint. Clear feedback keeps the user in control.
In this step, get ready to:
- Audit the existing board against four acceptance checks.
- Apply the smallest safe changes through Codex.
- Test the hardened behavior in Microsoft Edge before creating a Git checkpoint.
Review the board against clear checks
Acceptance checks give Codex a concrete definition of reliable behavior. They also help you distinguish an existing safeguard from a genuine gap.
What are the acceptance checks?
- Whitespace-only input is rejected after .trim() removes the spaces.
- Invalid saved JSON returns an empty task list through try...catch.
- User-written task text reaches the page through textContent.
- The empty-state visibility matches the current task count.
- Switch back to the focus-sprint-board workspace in Codex.
- Ask Codex to review index.html, styles.css, and app.js against the four acceptance checks above.
- Require an analysis-only response with no file edits.
- Read the findings in the Codex response panel.
- Confirm that each proposed change maps to a failed acceptance check.
The review should separate safeguards that already exist from gaps that need small edits. This keeps the next diff focused.
Apply the smallest fixes
A focused diff is easier to understand before you accept it. Each changed line should support one of the acceptance checks.
- Ask Codex to apply only the smallest fixes needed to pass the four checks.
- Verify that loadTasks() returns [] for missing saved data.
- Verify that loadTasks() catches malformed saved JSON.
- Verify that Array.isArray(parsedTasks) rejects non-array saved data.
- Verify that the submit handler reads taskInput.value.trim().
- Verify that the blank-input branch displays Enter a task before adding..
- Verify that createTaskItem() assigns user text through textContent.
Why inspect each edit?
Codex can propose more code than the bug requires. Reviewing each acceptance check protects the working persistence behavior while you approve the hardening changes.
- Verify that renderTasks() sets emptyState.hidden from tasks.length.
- Verify that taskForm.reset() clears the form after a successful addition.
- Verify that taskInput.focus() restores keyboard focus after a successful addition.
- Accept the proposed app.js edit in Codex.
Before you submit the form, do you expect three space characters to become a task or trigger feedback?
- Switch back to the board in Microsoft Edge.
- Enter three space characters in the task field.
- Click Add task.
You'll see Enter a task before adding. below the form. No task is added.
That is your first hardening win: blank entries now stop at the form.
Blank task still appears?
- Check that the submit handler uses taskInput.value.trim() before its blank-input condition.
- Check that the blank-input branch assigns the warning to formMessage.textContent.
- Help me inspect the blank-input behavior.
Test recovery and create the checkpoint
The final test exercises corrupted saved data before returning to normal task interactions. A passing board should recover safely while preserving its usual add, complete, refresh, and delete behavior.
This test deliberately replaces the board's saved browser data. It affects only the current localhost board.
- Right-click the board page in Microsoft Edge.
- Select Inspect.
- Select Application in DevTools.
- Expand Local Storage in the sidebar.
- Select the localhost origin used by the board.
- Double-click the Value cell for focus-sprint-tasks-v1.
- Replace its contents with {broken.
Before you refresh, do you think malformed saved data will stop the page or produce a usable empty board?
- Refresh the board page.
You'll see the empty-state message. The page remains usable.
That result proves loadTasks() now recovers safely from malformed JSON.
Before the final interaction test, which parts of the board should change as a task moves through its full lifecycle?
- Add a task named Protect focus block.
- Mark Protect focus block complete.
- Refresh the board page.
- Confirm that the checkbox for Protect focus block remains selected.
- Delete Protect focus block.
- Enter three space characters in the task field.
- Click Add task.
You'll see Your sprint is clear. Add the first task. with 0 of 0 complete.
You'll also see Enter a task before adding. below the form. The completed state survived the refresh before deletion.
Board fails after corrupted storage?
- Check that the try...catch surrounds JSON.parse(savedTasks).
- Check that the catch block returns [].
- Help me debug corrupted localStorage recovery.
✔️ My app passes every check
Your board now rejects blank tasks, recovers from malformed saved data, preserves completed tasks after refresh, and restores focus after successful additions.
ⓧ I'd like to double check the full code
Only app.js changes in this step. Compare it with this final core-project version.
const STORAGE_KEY = "focus-sprint-tasks-v1";
const taskForm = document.querySelector("#task-form");
const taskInput = document.querySelector("#task-input");
const taskList = document.querySelector("#task-list");
const progressText = document.querySelector("#progress-text");
const formMessage = document.querySelector("#form-message");
const emptyState = document.querySelector("#empty-state");
let tasks = loadTasks();
function loadTasks() {
const savedTasks = localStorage.getItem(STORAGE_KEY);
if (savedTasks === null) {
return [];
}
try {
const parsedTasks = JSON.parse(savedTasks);
return Array.isArray(parsedTasks) ? parsedTasks : [];
} catch {
return [];
}
}
function saveTasks() {
localStorage.setItem(STORAGE_KEY, JSON.stringify(tasks));
}
function updateProgress() {
const completedCount = tasks.filter((task) => task.completed).length;
progressText.textContent = `${completedCount} of ${tasks.length} complete`;
}
function createTaskItem(task) {
const item = document.createElement("li");
item.className = task.completed ? "task-item completed" : "task-item";
const label = document.createElement("label");
label.className = "task-label";
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = task.completed;
checkbox.setAttribute("aria-label", `Mark ${task.text} complete`);
checkbox.addEventListener("change", () => {
task.completed = checkbox.checked;
saveTasks();
renderTasks();
});
const taskText = document.createElement("span");
taskText.className = "task-text";
taskText.textContent = task.text;
const deleteButton = document.createElement("button");
deleteButton.type = "button";
deleteButton.className = "delete-button";
deleteButton.textContent = "Delete";
deleteButton.setAttribute("aria-label", `Delete ${task.text}`);
deleteButton.addEventListener("click", () => {
tasks = tasks.filter((candidate) => candidate.id !== task.id);
saveTasks();
renderTasks();
});
label.appendChild(checkbox);
label.appendChild(taskText);
item.appendChild(label);
item.appendChild(deleteButton);
return item;
}
function renderTasks() {
taskList.textContent = "";
tasks.forEach((task) => {
taskList.appendChild(createTaskItem(task));
});
emptyState.hidden = tasks.length > 0;
updateProgress();
}
taskForm.addEventListener("submit", (event) => {
event.preventDefault();
const text = taskInput.value.trim();
if (!text) {
formMessage.textContent = "Enter a task before adding.";
taskInput.focus();
return;
}
tasks.push({
id: Date.now(),
text,
completed: false,
});
saveTasks();
renderTasks();
taskForm.reset();
formMessage.textContent = "";
taskInput.focus();
});
renderTasks();
The runtime checks prove the behavior. A Git checkpoint makes this reviewed version recoverable.
- Return to Source Control in Visual Studio Code.
- Stage app.js.
- Enter Harden task board behavior as the commit message.
- Create the commit.
- Select Source Control Graph.
- Confirm that Harden task board behavior is the newest commit.
You've closed the review loop. Your hardened board is tested, saved, and backed by a recoverable Git checkpoint.
Secret mission
Add Task Filters
Your board can preserve a long sprint. Saved tasks become harder to scan as the list grows. Add All, Active, and Done filters that change the visible list while leaving every saved task intact.
Clean Up Your Resources
Clean Up Your Resources
The Focus Sprint Board runs entirely on your Windows device with no ongoing cloud or API charges. Choose whether to keep its resources running, pause them for later, or delete them.
Resources you used:
- The local focus-sprint-board folder with its source files and local Git history.
- Task records saved by Microsoft Edge under the focus-sprint-tasks-v1 localStorage key.
- The temporary Python development server currently serving the app on localhost.
Keep everything running
No action is needed. Choose this if you are still extending the board with Codex.
- Keep the focus-sprint-board folder so its source files and Git checkpoints remain available.
- Leave the saved Edge tasks in place so the board restores them after a refresh.
- Leave the Python development server running while you are actively using the app.
Pause - I'll come back to this later
Shut down the running process to free the terminal while keeping your project files and saved tasks.
- Return to the Visual Studio Code terminal that is running the Python development server.
- Close that terminal to stop the local server.
- Close the Microsoft Edge tab containing the Focus Sprint Board.
Your project is safely paused. Its source files, Git history, and saved task records remain available for your next session.
Delete - I don't want to use this again
Choose this if you want to remove the board and start fresh. Your installed apps and Codex account stay untouched.
- Return to the Focus Sprint Board in Microsoft Edge.
- Select All in the filter bar.
- Remove every visible task with its Delete button.
- Refresh the page.
You should see 0 of 0 complete with the empty-state message Your sprint is clear. Add the first task. This confirms that no saved task records return after a refresh.
- Return to the Visual Studio Code terminal running the Python development server.
- Close that terminal to stop the local server.
- Close Visual Studio Code.
- Close Codex.
The browser data and local server are now cleared. The remaining resource is the project folder with its Git history.
- Press the Windows key to open the search bar.
- Type File Explorer into the search bar.
- Press Enter to open File Explorer.
- Navigate to the location where you created focus-sprint-board.
- Select the focus-sprint-board folder.
- Press Shift+Delete to permanently remove the selected folder.
- Confirm the permanent deletion when Windows asks for approval.
You should no longer see focus-sprint-board in its previous location. Cleanup is complete with the project files, local Git history, saved task records, and development server removed.
Nice Work!
Nice Work!
Mission complete! You built a persistent Focus Sprint Board through a review-first Codex workflow.
You've learned how to:
- Build a responsive task board: Create the board structure with HTML. Style its responsive interface with CSS. Implement task creation with JavaScript. Implement task completion. Implement task deletion. Keep the progress count current.
- Lead a review-first Codex workflow: Define explicit outcomes. Reproduce the refresh failure before requesting a fix. Review every proposed diff. Protect working versions with local Git checkpoints.
- Persist and harden browser state: Preserve task text plus completion state with localStorage. Serialize the task array with JSON. Reject whitespace-only input. Recover from malformed saved data. Insert user text safely with textContent.
- Secret Mission: Add the All filter. Add the Active filter. Add the Done filter. Keep progress based on every saved task. Preserve filtering across a browser refresh.
Ready to quiz yourself?