Practice: Build an interactive web application
Outcome
Section titled “Outcome”Build an independent task-board application with HTML, CSS, and JavaScript. A user can add a task, see every task rendered from application state, and change a task’s completion state without a page reload.
This is Stage 1 of one continuing source-code project. Preserve the repository after this assignment. You will adapt the same application with responsive components and accessible motion in the Responsive design and animation assignment.
What you will practice
- Connect form and checkbox events to deliberate application behavior.
- Store task records in one JavaScript state object and render the interface from that state.
- Validate user input and preserve useful keyboard focus through state changes.
- Use Git checkpoints and browser tests to show how the application reached its final state.
Why this project matters
Section titled “Why this project matters”Many interfaces use the same cycle: an event occurs, application state changes, and the interface renders the new state. The product can be a task board, booking view, settings panel, or shopping basket. The names change, but the technical relationship remains useful.
What is new and what is reused
Section titled “What is new and what is reused”- New: You will design the complete DOM contract and implement the event-state-render cycle without copying the lesson project.
- Reused: External JavaScript,
defer, values, functions, conditions, arrays, objects, DOM selection, form events, checkbox events,textContent, state updates, rendering, keyboard tests, and the solo Git workflow. - Reserved for Stage 2: Container queries, component-level responsive changes, transitions, keyframe animation, and reduced-motion behavior.
Starting point
Before you start
- Complete the JavaScript foundations, DOM events, and interface-state lessons.
- Use VS Code, a current browser with developer tools, Git, and a private GitHub repository.
- Start from a new folder. Do not reuse the lesson repository or its Git history.
- Keep all names, example records, and test data fictional. Do not store personal or sensitive information.
- Current state
- You have a working lesson task tracker, but you do not yet have an independent Level 2 application repository.
- First action
- Create a folder named level-2-task-board and add empty index.html, styles.css, app.js, and README.md files.
- First checkpoint
- The new page loads its own stylesheet and JavaScript file, shows the chosen product heading, and has no Console errors.
- Help trigger
- Use the checkpoint assistance or ask for help when the first Console error remains unresolved after you read its file, line, and error type.
Requirements
The required assignment is complete when every applicable criterion below is met.
Required behavior
- A form contains one visible task-title label, one task-title text field, one visible category label, one category select, and one submit button.
- Submitting an empty or whitespace-only title shows one specific error, sets aria-invalid on the title field, moves focus to that field, and does not change application state.
- Submitting valid input adds one task object to application state with a unique ID, the submitted title, the selected category, and completed set to false.
- Every valid submission renders the complete task list from state, clears the form, and returns focus to the title field.
- Each rendered task has a native checkbox. Changing the checkbox updates the matching task object before the interface renders again.
- A visible summary always reports the correct number of completed tasks and total tasks.
- A status message identifies the task that was added or changed. The result appears without a page reload.
- Reloading the page returns the documented initial state. Persistence is not required in this stage.
Semantic HTML and accessibility
- The page uses appropriate header, main, section, form, label, button, list, and heading elements.
- Every form control has a persistent visible label, and instructions or errors are programmatically connected to the title field.
- All required behavior works with the keyboard. Focus remains visible, and a re-render does not move focus to the page start.
- The status output uses role="status" or an equivalent polite live-region pattern.
- Task titles entered by a user are inserted with textContent or an equivalent safe text API, not innerHTML.
- The base interface remains readable without horizontal page scrolling at a 320 CSS-pixel viewport and at 200% browser zoom.
Technical constraints
- Use index.html, styles.css, and app.js as separate files. Load app.js with defer and use strict mode.
- Keep one state object as the source of truth for task records. Derive the visible list and summary from that state.
- Register event listeners with addEventListener. Do not use inline JavaScript attributes.
- Use an event → state update → render sequence for both task creation and completion changes.
- Use browser-native HTML, CSS, and JavaScript. Do not use a framework, component library, or generated application code for the required path.
- Use one dedicated Git repository and one private GitHub repository. Record each passing checkpoint with a focused commit.
Code quality
- Use specific names for selected elements, state values, event handlers, and render functions.
- Keep stable DOM selections and listener registration outside the render function.
- Remove temporary logs, unused variables, commented-out code, and unresolved Console errors before submission.
- Use comments only to explain intent or a non-obvious constraint.
Design freedom
- Choose one brief below and write original fictional task content for it.
- Choose the product name, categories, typeface, color palette, spacing scale, and exact visual treatment.
- Choose whether the completion control appears before or after the task text, provided its label remains clear.
Out of scope
- A login, database, backend, public deployment, API, drag-and-drop interaction, and multi-user synchronization are not required.
- Task removal, filtering, editing, ordering, due dates, and localStorage are not required.
- Advanced responsive component changes and animation are not required until Stage 2.
- Real names, school information, health information, credentials, analytics, and other personal data must not enter the application or repository.
Definition of done
Section titled “Definition of done”The required assignment is complete when every requirement applies to one identified commit, the test sequence in the self-check passes, the same commit exists on the private remote, and git status reports a clean working tree. Optional extensions do not change this finish line.
Choose one task-board brief
Section titled “Choose one task-board brief”Each brief uses the same technical contract. Choose the context that gives you the clearest task titles and categories.
Brief A: Project delivery board
Section titled “Brief A: Project delivery board”Build a board for a fictional small web project. Use categories such as Content, Design, Development, and Review. Example tasks can include reviewing navigation labels or testing a form.
Brief B: Workshop preparation board
Section titled “Brief B: Workshop preparation board”Build a board for a fictional technical workshop. Use categories such as Room, Equipment, Material, and Communication. Keep participant and staff information fictional.
Brief C: Community event action board
Section titled “Brief C: Community event action board”Build a board for a fictional public event. Use categories such as Venue, Program, Accessibility, and Promotion. Do not include real attendee, volunteer, or contact information.
Write the selected brief, intended user, and chosen categories in README.md. Keep the core data model stable across the three briefs:
const task = { id: 1, title: "Test the registration form", category: "Review", completed: false,};The values can differ. The four property roles must remain clear so the later responsive, testing, SEO, and analytics work can use one stable product model.
Checkpoint 1: Open an independent project baseline
Section titled “Checkpoint 1: Open an independent project baseline”Create this visible structure:
level-2-task-board/├── index.html├── styles.css├── app.js└── README.mdIn index.html, create a valid document with a descriptive page title. Link styles.css, and load app.js with defer. Add a header, a main region, and the product’s main heading. Keep this checkpoint small. The form and application state belong to later checkpoints.
In app.js, add strict mode and one temporary startup message:
"use strict";
console.log("Task board JavaScript loaded.");Open the page. Confirm the heading appears, the stylesheet request succeeds, and the Console shows the startup message once. Remove the temporary log after the check.
Create and connect the repository
Section titled “Create and connect the repository”Open a terminal in level-2-task-board, then run:
git initgit add index.html styles.css app.js README.mdgit commit -m "Create task board baseline"git statusCreate a private GitHub repository named level-2-task-board. Connect the local repository by following the empty repository’s push an existing repository instructions. Push the baseline, then confirm that the four files appear on GitHub and the repository is private.
Do not copy the hidden .git folder from a lesson project. A copied .git folder would mix the new product with the lesson history and remote.
Reload the page with the Console open. The main heading must appear, the Console must have no errors, and git status must report a clean working tree.
Assistance for Checkpoint 1
Section titled “Assistance for Checkpoint 1”Assistance 1 — Confirm the active folder and loaded files
In VS Code, confirm that the Explorer shows index.html, styles.css, app.js, and README.md directly inside level-2-task-board. In browser developer tools, use the Network or Sources panel to confirm that the loaded app.js comes from this folder.
Assistance 2 — Trace a missing stylesheet or script
Compare each href and src value with the exact filename in the Explorer. A file in the same folder uses styles.css or app.js, without a leading slash. Confirm that the script element includes defer.
Checkpoint: Independent baseline is recorded
- What now works
- The new HTML page loads its own CSS and JavaScript without a Console error, and the private remote contains the baseline commit.
- Files changed
index.html, styles.css, app.js, README.md- What remains
- Build the semantic form, validation path, application state, and rendered task list.
- Next action
- Open index.html and add the labeled task form described in Checkpoint 2.
- If it does not work
- If the wrong project opens, close the browser tab, open index.html from level-2-task-board, and confirm the loaded app.js path before continuing.
Record each passing checkpoint with Git
Section titled “Record each passing checkpoint with Git”At the end of each later checkpoint:
- Run the checkpoint’s browser tests.
- Inspect the first Console error and repair it before staging files.
- Run
git diffand confirm that the diff belongs to the current behavior. - Stage only the files used by the checkpoint.
- Commit with a message that names the working result.
- Push and confirm the commit on GitHub.
- Run
git statusand leave the working tree clean.
Useful commit outcomes include:
Add accessible task form validationRender task records from application stateAdd task completion state changesDocument and verify task board behaviorCheckpoint 2: Build the form and validation path
Section titled “Checkpoint 2: Build the form and validation path”Add a task-entry section to index.html. The form needs these persistent labels and controls:
- Task title text input
- Category select with the categories from your chosen brief
- Add task submit button
Add visible title instructions, a dedicated error output, and a status output. Use stable IDs so the HTML and JavaScript agree. Add role="status" to the status output. Connect the title field to its instructions and error with aria-describedby.
In app.js, select the form, title field, category select, error output, and status output. Stop with an explicit error if a required element is absent. Register one submit listener on the form.
Inside the submit handler:
- Prevent the form’s default navigation.
- Trim the current title value.
- If the result is empty, set
aria-invalid="true", show Enter a task title., move focus to the title field, and return. - If the result is valid, remove
aria-invalid, clear the error, and temporarily log the title and category.
The temporary success log makes the data visible before application state exists. Remove it during Checkpoint 3.
Run these paths in order:
| Input and action | Visible result | State or focus result |
|---|---|---|
| Empty title, submit with Enter | Enter a task title. |
Focus is in the title field; no reload |
| Spaces only, activate the button | Same error | aria-invalid="true"; no reload |
| Valid title and category | No error | Console shows the trimmed title and category once |
Use the Elements panel to confirm that the input has aria-invalid="true" only during the invalid state.
Assistance for Checkpoint 2
Section titled “Assistance for Checkpoint 2”Assistance 1 — Identify the form's DOM contract
Write down the ID for the form, title field, category select, error output, and status output. Compare each ID with its querySelector call before you inspect handler logic.
Assistance 2 — Trace a form that reloads or accepts spaces
If the page reloads, confirm that the listener is registered on the form’s submit event and that event.preventDefault() is the first handler action. If spaces pass validation, confirm that the code tests the result of taskTitleInput.value.trim().
Assistance 3 — Build the validation handler in a stable order
Use these subgoals: cancel navigation; read and trim the title; handle the empty path and return; clear the invalid state; read the category; expose the valid data. Do not add state or rendering until all three table rows pass.
Checkpoint: The form exposes valid task data
- What now works
- The form prevents navigation, rejects empty input with a connected error and focused field, and exposes valid trimmed input once.
- Files changed
index.html, app.js, styles.css- What remains
- Store valid submissions as task objects and render the complete list and summary from state.
- Next action
- Open app.js and declare one state object before the submit handler.
- If it does not work
- If submit behavior differs between Enter and the button, confirm that the listener uses form submit rather than button click.
Checkpoint 3: Store and render task records
Section titled “Checkpoint 3: Store and render task records”Create one state object in app.js with an empty tasks array. Add a stable way to create a unique numeric ID. The ID only needs to remain unique during the current page load.
Add a task-list section to index.html with:
- a heading;
- a summary output;
- one
ulfor rendered tasks.
Write one renderTasks function. On every call, the function must:
- Remove the previous rendered list content.
- Derive the completed count from
state.tasks. - Update the visible summary from the completed and total counts.
- Show one list item with No tasks yet. when the array is empty.
- Otherwise create one list item for each task.
- Create each task title and category as text with
textContent. - Create one native checkbox for each task and give it a programmatic label.
In the valid submit path, create the task object, add it to state.tasks, call renderTasks, update the status output, reset the form, and return focus to the title field. Remove the temporary success log.
Call renderTasks() once after setup so the initial empty state and 0 of 0 tasks complete summary appear before the first interaction.
- Reload the page. Confirm the empty state and zero summary appear.
- Add a task whose title contains punctuation, such as
Review <header> links & labels. - Confirm the exact text appears as text. It must not create an HTML element.
- Add a second task in another category.
- Confirm both records remain visible and the summary reports
0 of 2 tasks complete. - Confirm focus returns to the title field after each valid submission.
Assistance for Checkpoint 3
Section titled “Assistance for Checkpoint 3”Assistance 2 — Find why state and the list disagree
Place one temporary console.log(state.tasks) immediately before renderTasks(). If the new object is absent, inspect the submit update. If the object is present but not visible, inspect the render loop and DOM creation. Remove the log after the check.
Assistance 3 — Separate the state update from rendering
The submit handler owns this order: validate → create object → update state → render → report status → reset form → focus title. The render function reads state and builds the current interface. It does not register the form listener or add another task to state.
Checkpoint: Task records render from state
- What now works
- Valid submissions create task objects, the complete state renders as safe text, the empty state disappears, and the summary uses current state.
- Files changed
index.html, app.js, styles.css- What remains
- Make each checkbox update the matching task, preserve focus through rendering, and complete the final test pass.
- Next action
- Inside the task render loop, connect each checkbox change to its task object's completed property.
- If it does not work
- If one submit creates several records, confirm that the form listener is registered once outside renderTasks.
Checkpoint 4: Update completion state and preserve focus
Section titled “Checkpoint 4: Update completion state and preserve focus”Inside the render loop, register one change listener on each new checkbox. The handler must:
- Update the matching task object’s
completedproperty from the checkbox state. - Call
renderTaskswith the changed task’s ID. - Update the status output with the task title and its new state.
Let renderTasks accept an optional task ID. After it rebuilds the list, use that ID to find and focus the matching new checkbox. This restores the user’s position after the old checkbox element is replaced.
Add a basic CSS baseline. It must provide:
- a readable line length and spacing scale;
- visible default, hover, active, and focus-visible control states;
- labels that do not depend on placeholders;
- task titles that wrap instead of widening the page;
- a complete single-column layout at 320 CSS pixels;
- a visible completed state that does not depend on color alone.
Do not add project motion yet. A state change must be fully understandable in a browser that displays no animation.
- Add two tasks.
- Use Tab and Space to complete the second task.
- Confirm the second task remains focused after rendering.
- Confirm its checkbox is checked, its text treatment identifies completion without color alone, the status names the changed task, and the summary reports
1 of 2 tasks complete. - Uncheck the same task and confirm every result returns to the open state.
- Test the complete application at 320 CSS pixels and 200% browser zoom.
- Reload and confirm the documented initial empty state returns.
- Confirm the Console has no errors.
Assistance for Checkpoint 4
Section titled “Assistance for Checkpoint 4”Assistance 1 — Identify which state should own completion
Inspect the task object associated with the changed checkbox. The checkbox is an interface control; task.completed is the stored value that the next render reads.
Assistance 2 — Repair focus loss after a render
Confirm that every checkbox receives an ID derived from its task ID. Pass the changed task ID into renderTasks, rebuild the list, select the new checkbox with that ID, and call focus() after the new element exists.
Assistance 3 — Verify one completion cycle before styling it
Trace one task through checkbox change → task-object update → complete render → summary derivation → focus restoration → status output. Keep motion and advanced layout out of this checkpoint until that full cycle passes.
Checkpoint: The interactive task board is complete
- What now works
- A keyboard user can add, validate, read, complete, and reopen fictional tasks while state, summary, status, and focus remain synchronized.
- Files changed
index.html, styles.css, app.js, README.md- What remains
- Run the complete self-check, record the verified commit, and preserve this repository for the later responsive and animation stage.
- Next action
- Run every self-check item from a fresh reload and record the exact verified commit in README.md.
- If it does not work
- If a later check fails, return to the first failing path, inspect state before and after its event, and repair that path before rerunning later checks.
Document the verified baseline
Section titled “Document the verified baseline”In README.md, record:
- product name, chosen brief, user, and purpose;
- the four files and what each file owns;
- the event → state update → render model;
- how to open and test the application;
- the required manual test paths and current result;
- known limits, including the deliberate lack of persistence;
- the final verified commit ID;
- the next stage: responsive components and accessible motion.
Commit and push the final baseline:
git statusgit diffgit add index.html styles.css app.js README.mdgit diff --stagedgit commit -m "Complete interactive task board"git pushgit statusDo not start Stage 2 in the same commit. The Stage 1 commit is the recovery point for later layout and motion work.
Self-check
Complete these checks against the required result.
- A fresh reload shows the documented initial empty state and the correct zero summary without a Console error.
- Submitting an empty title and a spaces-only title shows one specific error, changes no task state, and focuses the title field.
- A valid title and category create exactly one task object and one rendered list item without a page reload.
- A title that contains HTML-looking characters renders as literal text rather than markup.
- Two or more tasks remain visible after later submissions because every render uses the complete state array.
- Changing a checkbox updates the matching task, the visible completed treatment, the status message, and the derived summary.
- The changed checkbox receives focus again after the completion render.
- Every required action works with Enter, Tab, and Space where those keys apply, and the focus indicator remains visible.
- The interface has no horizontal page scrolling at 320 CSS pixels or 200% browser zoom.
- The application has no unresolved Console error, temporary log, unused variable, or commented-out implementation.
- README.md states the product, architecture, tests, limits, verified commit, and Stage 2 continuation point.
- The verified commit exists in the private GitHub repository and git status reports a clean working tree.
Assessment
Section titled “Assessment”The review uses observable evidence from the required path:
| Area | Evidence |
|---|---|
| Event handling | Form submit and checkbox change work through registered event listeners without unwanted navigation |
| State and rendering | One state object owns task records; every interface result derives from the current state |
| Accessibility | Labels, validation, keyboard operation, status messages, focus restoration, wrapping, and zoom behavior pass |
| JavaScript quality | DOM selection, validation, state updates, rendering, naming, and safe text insertion have clear roles |
| Verification | The documented test paths pass against one identified commit with a clean Console |
| Development practice | The repository contains focused checkpoints, a useful README, a private remote, and a clean final state |
Visual taste is not assessed. The interface must meet the stated usability and accessibility criteria, but the palette, typography, and exact composition remain design choices.
Assignment-wide assistance
Section titled “Assignment-wide assistance”The checkpoint sections provide support for their current behavior. The two options below cover the complete application structure. Compare IDs and responsibilities before copying code into a project with different names.
Assistance 4 — Review a partial application structure
Use this structure when a blank renderer is the active barrier:
"use strict";
// Select the stable interface elements.const taskForm = document.querySelector("#task-form");const taskTitleInput = document.querySelector("#task-title");const taskCategorySelect = document.querySelector("#task-category");const taskError = document.querySelector("#task-error");const taskSummary = document.querySelector("#task-summary");const taskList = document.querySelector("#task-list");const appStatus = document.querySelector("#app-status");
// Guard the DOM contract.if ( !taskForm || !taskTitleInput || !taskCategorySelect || !taskError || !taskSummary || !taskList || !appStatus) { throw new Error("The task board markup is incomplete.");}
// Store application data.const state = { tasks: [],};let nextTaskId = 1;
function renderTasks(focusTaskId) { // Clear the old list. // Derive and render the summary. // Render the empty state or every task. // Register each checkbox change handler. // Restore focus when focusTaskId identifies a task.}
function handleTaskSubmit(event) { event.preventDefault();
// Read and validate the title. // Create and store one task object. // Render, report status, reset, and focus.}
taskForm.addEventListener("submit", handleTaskSubmit);renderTasks();Complete one comment group at a time. Run the related checkpoint test before you continue.
Assistance 5 — Review one complete reference implementationExample solution
This is one valid reference implementation for Brief A. Your names, categories, content, and visual design can differ. The required behavior and accessibility result must remain equivalent.
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Project delivery board</title> <link rel="stylesheet" href="styles.css" /> <script src="app.js" defer></script> </head> <body> <header class="site-header"> <p class="eyebrow">Studio practice</p> <h1>Project delivery board</h1> <p>Add fictional project tasks and track their completion state.</p> </header>
<main> <section aria-labelledby="add-task-heading"> <h2 id="add-task-heading">Add a task</h2> <form id="task-form" novalidate> <div class="field"> <label for="task-title">Task title</label> <input id="task-title" name="taskTitle" type="text" maxlength="80" aria-describedby="task-title-help task-error" /> <p id="task-title-help">Use 1–80 characters.</p> <p id="task-error" class="error"></p> </div>
<div class="field"> <label for="task-category">Category</label> <select id="task-category" name="taskCategory"> <option value="Content">Content</option> <option value="Design">Design</option> <option value="Development">Development</option> <option value="Review">Review</option> </select> </div>
<button type="submit">Add task</button> </form> </section>
<section aria-labelledby="task-list-heading"> <div class="section-heading"> <h2 id="task-list-heading">Current tasks</h2> <p id="task-summary">0 of 0 tasks complete</p> </div> <ul id="task-list" class="task-list"></ul> </section>
<p id="app-status" class="status" role="status"> Add a task to begin. </p> </main> </body></html>:root { --surface: #f5f7f4; --surface-raised: #fcfdfb; --text: #18231f; --muted: #52645d; --border: #a7b4af; --accent: #1f6f5f; --error: #b42318; --space-1: 0.5rem; --space-2: 0.75rem; --space-3: 1rem; --space-4: 1.5rem; --space-5: 2rem;}
* { box-sizing: border-box;}
body { margin: 0; background: var(--surface); color: var(--text); font-family: system-ui, sans-serif; line-height: 1.5;}
.site-header,main { width: min(100% - 2rem, 48rem); margin-inline: auto;}
.site-header { padding-block: var(--space-5) var(--space-3);}
main { display: grid; gap: var(--space-5); padding-block-end: var(--space-5);}
h1,h2,p { overflow-wrap: anywhere;}
.eyebrow { margin: 0; color: var(--muted); font-weight: 700; text-transform: uppercase;}
form,.task-list { display: grid; gap: var(--space-3);}
.field { display: grid; gap: var(--space-1);}
label { font-weight: 700;}
input,select,button { min-height: 2.75rem; border: 1px solid var(--border); border-radius: 0.35rem; padding: var(--space-2); font: inherit;}
button { border-color: var(--accent); background: var(--accent); color: white; font-weight: 700; cursor: pointer;}
button:hover { filter: brightness(1.1);}
button:active { filter: brightness(0.9);}
:focus-visible { outline: 0.2rem solid currentColor; outline-offset: 0.2rem;}
[aria-invalid="true"] { border-color: var(--error); border-width: 0.15rem;}
.error { min-height: 1.5em; margin: 0; color: var(--error); font-weight: 700;}
.section-heading { display: flex; align-items: baseline; flex-wrap: wrap; justify-content: space-between; gap: var(--space-2);}
.section-heading > * { margin-block: 0;}
.task-list { margin: var(--space-3) 0 0; padding: 0; list-style: none;}
.task-card { border: 1px solid var(--border); border-radius: 0.5rem; background: var(--surface-raised); padding: var(--space-3);}
.task-control { display: flex; align-items: flex-start; gap: var(--space-2);}
.task-control input { flex: 0 0 auto;}
.task-copy { display: grid; min-width: 0; gap: var(--space-1);}
.task-title,.task-category { overflow-wrap: anywhere;}
.task-card[data-completed="true"] .task-title { text-decoration: line-through;}
.task-category,.status { color: var(--muted);}
.status { min-height: 1.5em; margin: 0;}"use strict";
// Select the stable interface elements.const taskForm = document.querySelector("#task-form");const taskTitleInput = document.querySelector("#task-title");const taskCategorySelect = document.querySelector("#task-category");const taskError = document.querySelector("#task-error");const taskSummary = document.querySelector("#task-summary");const taskList = document.querySelector("#task-list");const appStatus = document.querySelector("#app-status");
if ( !taskForm || !taskTitleInput || !taskCategorySelect || !taskError || !taskSummary || !taskList || !appStatus) { throw new Error("The task board markup is incomplete.");}
// Keep task records in one application state object.const state = { tasks: [],};let nextTaskId = 1;
function renderTasks(focusTaskId) { taskList.replaceChildren();
const completedCount = state.tasks.filter((task) => task.completed).length; taskSummary.textContent = `${completedCount} of ${state.tasks.length} tasks complete`;
if (state.tasks.length === 0) { const emptyItem = document.createElement("li"); emptyItem.textContent = "No tasks yet."; taskList.append(emptyItem); return; }
state.tasks.forEach((task) => { const item = document.createElement("li"); item.className = "task-card"; item.dataset.completed = String(task.completed);
const label = document.createElement("label"); label.className = "task-control";
const checkbox = document.createElement("input"); checkbox.type = "checkbox"; checkbox.id = `task-${task.id}`; checkbox.checked = task.completed;
const copy = document.createElement("span"); copy.className = "task-copy";
const title = document.createElement("span"); title.className = "task-title"; title.textContent = task.title;
const category = document.createElement("span"); category.className = "task-category"; category.textContent = task.category;
copy.append(title, category); label.append(checkbox, copy); item.append(label); taskList.append(item);
checkbox.addEventListener("change", () => { task.completed = checkbox.checked; renderTasks(task.id); const stateLabel = task.completed ? "complete" : "open"; appStatus.textContent = `${task.title} is now ${stateLabel}.`; }); });
if (focusTaskId !== undefined) { const checkboxToFocus = document.querySelector(`#task-${focusTaskId}`);
if (checkboxToFocus) { checkboxToFocus.focus(); } }}
function handleTaskSubmit(event) { event.preventDefault();
const taskTitle = taskTitleInput.value.trim();
if (taskTitle === "") { taskTitleInput.setAttribute("aria-invalid", "true"); taskError.textContent = "Enter a task title."; taskTitleInput.focus(); return; }
taskTitleInput.removeAttribute("aria-invalid"); taskError.textContent = "";
const task = { id: nextTaskId, title: taskTitle, category: taskCategorySelect.value, completed: false, }; nextTaskId += 1;
state.tasks.push(task); renderTasks(); appStatus.textContent = `${task.title} was added.`; taskForm.reset(); taskTitleInput.focus();}
taskForm.addEventListener("submit", handleTaskSubmit);renderTasks();After you use the reference, close it and explain your own event-state-render cycle from app.js. Verify all self-check paths against your implementation.
Safe stopping point and re-entry
Section titled “Safe stopping point and re-entry”Stop only at a passing checkpoint when possible. Commit and push that state. Add this note to README.md or a private project note before a longer interruption:
## Resume here
- What works:- First failing or unfinished test:- Next action:- File and function to open:- Last passing commit:- Required browser or server state:When the required assignment is complete, preserve the repository and final commit. The later responsive and animation assignment will continue from this exact application instead of starting another product.