Build DevTrack with JavaScript

Build a responsive job tracker with HTML, CSS, JavaScript, and localStorage.

Introduction

30 Second Summary

Job applications can quickly disappear into scattered notes or crowded inboxes. Soon, it becomes difficult to remember where each opportunity stands.

In this project, you will build DevTrack to record every job application in one browser dashboard. The finished tracker stays useful across screen sizes and browser refreshes.

What You'll Build

Your finished DevTrack dashboard turns each job application into a card you can filter, remove, and recover after a refresh on any screen size.

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

  • An accessible application form that turns company, role, and hiring status details into a new card without reloading the page.
  • A responsive dashboard that stays usable on narrow or wide screens. Its live controls filter applications, update the count, and delete unwanted cards immediately.
  • A published project foundation on GitHub where you can demonstrate saved cards returning after a browser refresh.
  • Secret Mission: Turn each status badge into a control that cycles an application through Applied, Interview, and Offer while preserving the latest stage.

Do I need anything before starting?

You need basic familiarity with HTML, CSS, and JavaScript syntax. You also need Visual Studio Code, Git, and an active GitHub account.

Before We Start

Before the hands-on work begins, this checkpoint locks in the user problem DevTrack solves. That problem gives every later design choice a clear purpose.

Set Up the DevTrack Workspace

DevTrack has a clear user problem. It still needs a reliable workspace where every later change can be tested immediately.

A GitHub repository gives the project an online home. Git creates the connected local copy on your computer.

Visual Studio Code gives you one place to edit the files. Live Preview serves the page through a local server so each saved change has a visible result.

In this step, get ready to:
  • Create a GitHub repository named devtrack with a README.
  • Prepare a connected local copy of the repository.
  • Serve a starter DevTrack page through Live Preview.
Prepare the GitHub repository

A repository stores the project's files plus their change history. Cloning that repository places a connected copy on your Desktop.

  • Open GitHub in your browser.
  • Sign in to your GitHub account if prompted.
  • Select New repository from your GitHub dashboard.
  • Enter devtrack as the repository name.
  • Turn Add README on.
  • Select Create repository.

GitHub now shows the new devtrack repository with its README file.

  • Open the repository's Code menu.
  • Select HTTPS.
  • Copy the repository URL.
  • Record the copied address here: your GitHub repository HTTPS URL.
  • Press the Windows key to open Windows search.
  • Type PowerShell into the search field.
  • Press Enter to open PowerShell.
  • Move to your Desktop by running this command:
cd ~/Desktop

What does this command do?

PowerShell changes its current folder to your Desktop. The clone therefore lands somewhere easy to find.

  • Type the documented clone template shown below into PowerShell.
  • Replace YOUR-USERNAME with the username shown in your GitHub repository HTTPS URL.
  • Replace YOUR-REPOSITORY with devtrack.
  • Press Enter to clone the repository:
git clone https://github.com/YOUR-USERNAME/YOUR-REPOSITORY

What does this command do?

The clone command downloads the repository into a new devtrack folder. The local repository keeps its connection to GitHub for later pushes.

You should see cloning progress before PowerShell returns to its prompt. Your Desktop now contains the connected devtrack folder.

Having trouble cloning the repository?

Check that the repository URL contains your GitHub username. Confirm that the repository name ends with devtrack.

If GitHub requests authentication, complete the sign-in flow before retrying the clone.

Help me troubleshoot my GitHub clone.

Create the starter files

The local repository needs separate files for page structure, styling, and behavior. This step adds the files before the later build steps fill them out.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into the search field.
  • Press Enter to open it.
  • Select File from the top menu.
  • Select Open Folder....
  • Open the devtrack folder from your Desktop.

The Explorer view now shows the cloned devtrack workspace with its README file.

  • Select New File... in the Explorer view.
  • Name the file index.html.

You should see index.html listed beneath the repository folder.

  • Select New File... in the Explorer view.
  • Name the file styles.css.

You should now see styles.css in the Explorer file list.

  • Select New File... in the Explorer view.
  • Name the file script.js.

The Explorer now contains all three starter files.

A valid HTML document gives Live Preview a complete page to serve. The heading provides the first visible proof that your workspace works.

  • Select index.html in the Explorer view.
  • Add the starter document by pasting this code:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DevTrack</title>
  </head>
  <body>
    <h1>DevTrack</h1>
  </body>
</html>

What does this code do?

  • The document declaration tells the browser to use modern HTML behavior.
  • The metadata gives the page its title plus mobile-friendly sizing.
  • The h1 element creates the visible DevTrack heading.
  • Save index.html with Ctrl+S.

Your Explorer file list now contains index.html, styles.css, and script.js. The editor shows the complete starter document.

Seeing problems in the starter document?

Check that every opening HTML tag has the matching closing tag shown above. Confirm that the heading text uses the exact capitalization DevTrack.

Help me compare my starter HTML.

✔️ Awesome, I've got everything!

Your starter workspace now has the three files needed for the next build step.

ⓧ I'd like to double check the full code

Compare your saved 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.0" />
    <title>DevTrack</title>
  </head>
  <body>
    <h1>DevTrack</h1>
  </body>
</html>

This file contains the exact starter document served in this step.

Your styles.css file should be empty.

Your script.js file should be empty.

Serve DevTrack with Live Preview

Live Preview hosts index.html through a local server inside Visual Studio Code. This gives the project a consistent browser preview instead of relying on a direct file: address.

  • Select the Extensions view from the left sidebar.
  • Search for Live Preview.
  • Select the extension published by Microsoft.
  • Select Install.

Live Preview is now installed in Visual Studio Code. Your workspace has its local preview tool ready.

Live Preview not installing?

Confirm that the selected extension is published by Microsoft. Check your internet connection if the installation remains incomplete.

Help me install Live Preview in Visual Studio Code.

  • Return to the index.html editor tab.

Before you launch the preview, picture the heading you expect the browser to display.

  • Right-click inside the editor.
  • Select Show Preview.

You should see the DevTrack heading in the Live Preview browser. That first rendered page proves your repository, files, editor, and local server are working together.

Don't see the DevTrack heading?

Confirm that index.html is the active editor tab before selecting Show Preview. Save the file if the preview shows an older version.

Check that the heading contains the exact text DevTrack.

Help me troubleshoot my Live Preview page.

Your DevTrack workspace is live and connected to GitHub. Next up, you will replace this starter heading with the semantic tracker interface.

Build the Semantic Tracker Interface

Your DevTrack workspace is already running in Live Preview. The visible heading proves that your local feedback loop works.

The starter page cannot collect application data. It also cannot communicate a hiring pipeline.

You will use semantic HTML to build an accessible baby version of the tracker. Its form plus tracker regions create the foundation for every later feature.

In this step, get ready to:
  • Build the semantic page frame.
  • Create the application form controls.
  • Create the tracker controls plus its output regions.
Build the semantic page frame

Semantic elements describe the purpose of each page region. They give assistive technology a clear map of DevTrack.

  • In the index.html tab from earlier, find the existing <title>DevTrack</title> line.
  • Replace that line with the following page title plus file connections:
    <title>DevTrack Job Application Tracker</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="script.js" defer></script>

What Does This Head Code Do?

  • The <title> element names the page in the browser tab.
  • The stylesheet link connects the existing CSS file to the page.
  • The defer attribute delays the existing JavaScript file until the document has been parsed.
  • Save index.html.
  • Return to the Live Preview browser tab.

You should see DevTrack Job Application Tracker in the browser tab title. The page still displays the DevTrack heading.

Browser Tab Still Says DevTrack?

Confirm that the new <title> line remains inside <head>. Save index.html once more if the editor tab shows unsaved changes.

If the title still stays the same, refresh the Live Preview browser tab.

Help me check why my updated HTML title is not appearing in Live Preview.

  • In index.html, find the existing <h1>DevTrack</h1> line.
  • Replace that line with this semantic header:
    <header class="site-header">
      <div class="container">
        <p class="eyebrow">Full-stack foundations project</p>
        <h1>DevTrack</h1>
        <p>Keep every job application and hiring stage in one clear dashboard.</p>
      </div>
    </header>

What Does This Header Do?

  • The <header> element identifies the page introduction.
  • The container class gives the next styling step a shared layout hook.
  • The eyebrow plus description explain the dashboard before the user reaches its controls.
  • Save index.html.
  • Return to Live Preview.

You should see the DevTrack heading between a foundations label and a dashboard description. That visible change confirms the new header is rendering.

Only See the Old Heading?

Check that the complete <header> block sits between <body> and </body>.

Make sure the opening <header> tag has a matching </header> tag.

Help me find why my semantic DevTrack header is not rendering in Live Preview.

Add accessible application controls

An accessible form pairs every control with a visible label. Required fields also provide built-in browser validation.

  • In index.html, place this dashboard block immediately after </header> by copying the code below:
    <main class="container dashboard">
      <section class="panel" aria-labelledby="add-application-heading">
        <h2 id="add-application-heading">Add an application</h2>
        <form id="application-form" class="application-form">
          <label for="company">Company</label>
          <input id="company" name="company" type="text" required />

          <label for="role">Role</label>
          <input id="role" name="role" type="text" required />

          <label for="status">Status</label>
          <select id="status" name="status">
            <option value="Applied">Applied</option>
            <option value="Interview">Interview</option>
            <option value="Offer">Offer</option>
          </select>

          <button class="primary-button" type="submit">Add application</button>
        </form>
      </section>
    </main>

What Does This Form Do?

  • The <main> element contains the primary dashboard content.
  • Each <label> points to its control through matching for plus id values.
  • The required attributes stop empty company or role submissions.
  • The status selector limits each application to Applied, Interview, or Offer.
  • Save index.html.
  • Return to Live Preview.

You should see labeled fields for Company and Role. You should also see a Status selector plus an Add application button.

  • Select Add application while both text fields are empty.

Your browser should highlight the Company field. This confirms that its required validation is active.

Form Controls Missing or Misaligned?

Confirm that the dashboard block appears after </header> but before </body>.

Check every opening <form>, <select>, and <section> tag for its matching closing tag.

Help me diagnose missing form controls in my DevTrack HTML.

Add tracker feedback and filtering

The tracker needs stable DOM targets for its count plus cards. A filter gives users control over which hiring stages appear.

  • In index.html, find the closing </main> tag immediately after the application form section.
  • Replace that tag with the following tracker section plus footer:
      <section class="panel" aria-labelledby="tracker-heading">
        <div class="tracker-header">
          <div>
            <p class="eyebrow">Your pipeline</p>
            <h2 id="tracker-heading">Applications</h2>
          </div>

          <label class="filter-control" for="status-filter">
            Filter
            <select id="status-filter">
              <option value="All">All statuses</option>
              <option value="Applied">Applied</option>
              <option value="Interview">Interview</option>
              <option value="Offer">Offer</option>
            </select>
          </label>
        </div>

        <p id="application-count" class="application-count" aria-live="polite"></p>
        <div id="application-list" class="application-list" aria-live="polite"></div>
      </section>
    </main>

    <footer class="site-footer">
      <div class="container">Built with HTML, CSS, and JavaScript.</div>
    </footer>

How Does the Tracker Structure Work?

  • The status-filter selector starts with All statuses so the complete pipeline is the default view.
  • The application-count element provides a dedicated region for the live total.
  • The empty application-list element is the target for application cards plus empty-state text.
  • The aria-live regions allow assistive technology to announce content updates.
  • The <footer> element closes the page with its technology summary.
  • Save index.html.

Before you return to the preview, ask yourself which tracker control you expect beside the Applications heading.

  • Return to Live Preview.

You should see the application form followed by an Applications tracker. The tracker includes an All statuses filter plus an empty area for future cards.

You should also see the footer text at the bottom of the page. That is your first complete DevTrack interface working in the browser.

Tracker Section Not Visible?

Confirm that the tracker <section> sits inside <main>.

Check that the final </main> tag appears after the tracker section. The footer should appear after that closing tag.

Help me find a misplaced closing tag in my DevTrack tracker interface.

✔️ Awesome, I've got everything!

Your index.html file now matches the complete semantic DevTrack interface.

ⓧ I'd like to double check the full code

  • Compare your index.html file against this complete version:
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>DevTrack Job Application Tracker</title>
    <link rel="stylesheet" href="styles.css" />
    <script src="script.js" defer></script>
  </head>
  <body>
    <header class="site-header">
      <div class="container">
        <p class="eyebrow">Full-stack foundations project</p>
        <h1>DevTrack</h1>
        <p>Keep every job application and hiring stage in one clear dashboard.</p>
      </div>
    </header>

    <main class="container dashboard">
      <section class="panel" aria-labelledby="add-application-heading">
        <h2 id="add-application-heading">Add an application</h2>
        <form id="application-form" class="application-form">
          <label for="company">Company</label>
          <input id="company" name="company" type="text" required />

          <label for="role">Role</label>
          <input id="role" name="role" type="text" required />

          <label for="status">Status</label>
          <select id="status" name="status">
            <option value="Applied">Applied</option>
            <option value="Interview">Interview</option>
            <option value="Offer">Offer</option>
          </select>

          <button class="primary-button" type="submit">Add application</button>
        </form>
      </section>

      <section class="panel" aria-labelledby="tracker-heading">
        <div class="tracker-header">
          <div>
            <p class="eyebrow">Your pipeline</p>
            <h2 id="tracker-heading">Applications</h2>
          </div>

          <label class="filter-control" for="status-filter">
            Filter
            <select id="status-filter">
              <option value="All">All statuses</option>
              <option value="Applied">Applied</option>
              <option value="Interview">Interview</option>
              <option value="Offer">Offer</option>
            </select>
          </label>
        </div>

        <p id="application-count" class="application-count" aria-live="polite"></p>
        <div id="application-list" class="application-list" aria-live="polite"></div>
      </section>
    </main>

    <footer class="site-footer">
      <div class="container">Built with HTML, CSS, and JavaScript.</div>
    </footer>
  </body>
</html>

How to Read This Reference

This full-file view combines the page head with the header. It also combines the application form with the tracker plus footer.

Next up, you will turn this raw structure into a responsive dashboard with a clear visual hierarchy.

Turn the Page into a Responsive Dashboard

Your semantic interface is now visible in Live Preview. The page has the right content structure, but the browser defaults give every element the same visual weight.

Raw browser styling also becomes awkward on smaller screens. This step uses CSS Grid to establish the layout. A responsive breakpoint keeps the dashboard usable when space becomes tight.

In this step, get ready to:
  • Create the dashboard's colors, typography, spacing, focus styles, and panels.
  • Arrange the form and tracker with a two-column layout.
  • Collapse the dashboard into one column on narrow screens.
Style the page foundation

CSS custom properties keep repeated colors in one place. Global rules create a predictable canvas before individual components receive their styles.

  • In styles.css, add the page theme and global sizing rules with this code:
:root {
  color-scheme: light;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: #172033;
  background: #f4f7fb;
  --primary: #4f46e5;
  --primary-dark: #3730a3;
  --surface: #ffffff;
  --muted: #667085;
  --border: #d9e0ea;
  --danger: #b42318;
  --shadow: 0 16px 40px rgba(23, 32, 51, 0.1);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
}

What does this CSS do?

  • The custom properties store the shared colors and shadow used across the dashboard.
  • The universal selector includes borders and padding within each element's declared size.
  • The body rules remove the browser margin and establish the minimum page dimensions.
  • Save styles.css.
  • Return to Live Preview to confirm the pale background replaces the plain white page.

Still seeing a white background?

Confirm that the theme starts with :root. Check that styles.css is still linked inside index.html.

Help me find why the CSS theme is missing from DevTrack.

The page now has a shared visual theme. Next, the controls need consistent behavior and a centered content width.

  • Below the body rules in styles.css, add the control defaults and container styles with this code:
button,
input,
select {
  font: inherit;
}

button,
select {
  cursor: pointer;
}

.container {
  width: min(1100px, calc(100% - 2rem));
  margin-inline: auto;
}

How do these defaults help?

  • The controls inherit the page font so the form feels like part of the same interface.
  • The pointer cursor signals which controls can be selected.
  • The container limits wide content while preserving space at both screen edges.
  • Save styles.css.
  • Refresh Live Preview to confirm the page content is centered with space along both edges.

Is the content touching the edges?

Check that .container begins with a period. Confirm that the width calculation includes 2rem.

Help me troubleshoot the DevTrack container width.

The header introduces DevTrack before the dashboard begins. A gradient and larger title make that hierarchy visible immediately.

  • Below the container rules in styles.css, add the header styles with this code:
.site-header {
  padding: 3rem 0 4.5rem;
  color: #ffffff;
  background: linear-gradient(135deg, #312e81, #4f46e5);
}

.site-header h1 {
  margin: 0.25rem 0;
  font-size: clamp(2.25rem, 7vw, 4.5rem);
}

.site-header p:last-child {
  max-width: 620px;
  margin-bottom: 0;
  font-size: 1.05rem;
}

.eyebrow {
  margin: 0;
  color: #c7d2fe;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

What shapes the header?

  • The gradient separates the project introduction from the working dashboard.
  • The title uses a flexible size that grows with the viewport.
  • The eyebrow style turns short labels into compact section markers.
  • Save styles.css.
  • Refresh Live Preview to confirm the header has a purple gradient with a large DevTrack title.

Does the header still look plain?

Confirm that .site-header matches the class in index.html. Check the closing brace after the gradient rule.

Help me fix the DevTrack header styles.

Build the dashboard layout

The dashboard contains two related work areas. A grid gives the form a stable width while the tracker uses the remaining space.

  • Below the header styles in styles.css, add the dashboard grid and panel styles with this code:
.dashboard {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 1.5rem;
  margin-top: -2rem;
  padding-bottom: 3rem;
}

.panel {
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: 1rem;
  background: var(--surface);
  box-shadow: var(--shadow);
}

.panel h2 {
  margin-top: 0;
}

How does the layout work?

  • The first grid column stays between 280 and 360 pixels wide.
  • The second column expands to use the remaining space.
  • The negative top margin pulls both panels over the lower edge of the header.
  • Save styles.css.
  • Refresh Live Preview to confirm the form and tracker appear as raised panels in two columns.

Are the panels still stacked?

Confirm that the main element still has the dashboard class. Check that display: grid appears inside .dashboard.

Help me troubleshoot the two-column DevTrack grid.

Form controls need clear spacing and visible keyboard focus. These rules make every field easier to scan and operate.

  • Below the panel styles in styles.css, add the form and focus styles with this code:
.application-form {
  display: grid;
  gap: 0.75rem;
}

.application-form label,
.filter-control {
  color: var(--muted);
  font-size: 0.875rem;
  font-weight: 700;
}

input,
select {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.65rem;
  color: #172033;
  background: #ffffff;
}

input:focus,
select:focus,
button:focus-visible {
  outline: 3px solid #c7d2fe;
  outline-offset: 2px;
}

Why style focus states?

  • The grid gap separates each label from the next control group.
  • Shared input styles create consistent field dimensions.
  • The outline shows keyboard users which control currently has focus.
  • Save styles.css.
  • Press the Tab key in Live Preview to confirm each form control receives a visible outline.

Can you see the focus outline?

Click the company field before pressing Tab. Confirm that each focus selector includes a colon.

Help me fix the focus styles in DevTrack.

The primary action and tracker heading need distinct alignment. The button draws attention to form submission while the tracker header keeps its filter within reach.

  • Below the focus rules in styles.css, add the button and tracker header styles with this code:
.primary-button {
  margin-top: 0.5rem;
  padding: 0.8rem 1rem;
  border: 0;
  border-radius: 0.65rem;
  color: #ffffff;
  background: var(--primary);
  font-weight: 800;
}

.primary-button:hover {
  background: var(--primary-dark);
}

.tracker-header {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
}

What changes here?

  • The primary button uses the theme color to signal the form's main action.
  • The hover rule darkens the button when a pointer rests over it.
  • The flexible tracker header places its title and filter at opposite edges.
  • Save styles.css.
  • Refresh Live Preview to confirm the Add application button is purple and the filter sits beside the tracker heading.

Is the button missing its color?

Confirm that the form button still has the primary-button class. Check that var(--primary) matches the custom property near the top of styles.css.

Help me troubleshoot the DevTrack button and tracker header.

The tracker also needs spacing for its count and future application cards. These selectors prepare the list without changing the semantic HTML.

  • Below the tracker header rules in styles.css, add the tracker controls and card container styles with this code:
.tracker-header .eyebrow {
  color: var(--primary);
}

.filter-control {
  display: grid;
  gap: 0.35rem;
  min-width: 150px;
}

.application-count {
  color: var(--muted);
}

.application-list {
  display: grid;
  gap: 0.75rem;
}

.application-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 0.8rem;
  background: #f8fafc;
}

How is the tracker prepared?

  • The filter receives a minimum width so its options remain readable.
  • The application list creates consistent gaps between future cards.
  • Each card uses a flexible row that separates its details from its delete control.
  • Save styles.css.
  • Refresh Live Preview to confirm the pipeline label is purple and the filter has a consistent width.

Does the filter still look compressed?

Confirm that the filter label has the filter-control class. Check that the minimum width is declared inside that selector.

Help me fix the DevTrack tracker spacing.

Finish cards and narrow-screen styles

Application cards arrive in the next step when JavaScript creates them. Styling their content now gives each generated card a readable company, role, and status hierarchy.

  • Below the application card rules in styles.css, add the card text and status badge styles with this code:
.application-card h3,
.application-card p {
  margin: 0;
}

.application-card p {
  margin-top: 0.25rem;
  color: var(--muted);
}

.status-badge {
  display: inline-flex;
  margin-top: 0.65rem;
  padding: 0.3rem 0.65rem;
  border: 0;
  border-radius: 999px;
  color: var(--primary-dark);
  background: #e0e7ff;
  font-size: 0.8rem;
  font-weight: 800;
}

How will card details stay clear?

  • Removing default margins keeps the generated card text compact.
  • The role receives muted color so the company remains the strongest label.
  • The rounded status badge makes each hiring stage easy to scan.
  • Save styles.css.
  • Confirm that .status-badge appears below the application card rules in styles.css.

Is the status badge rule missing?

Check that the selector begins with a period. Confirm that its closing brace appears before the next selector.

Help me check the DevTrack card text and status badge CSS.

Delete controls need a visual warning without overpowering the primary action. The current empty state also needs enough space to feel intentional.

  • Below the status badge rules in styles.css, add the delete button, empty state, and footer styles with this code:
.delete-button {
  padding: 0.55rem 0.7rem;
  border: 1px solid #fecdca;
  border-radius: 0.55rem;
  color: var(--danger);
  background: #fef3f2;
  font-weight: 700;
}

.empty-state {
  margin: 0;
  padding: 2rem 1rem;
  border: 1px dashed var(--border);
  border-radius: 0.8rem;
  color: var(--muted);
  text-align: center;
}

.site-footer {
  padding: 1.5rem 0 2.5rem;
  color: var(--muted);
  text-align: center;
}

What do these styles communicate?

  • The delete button uses red tones to distinguish a destructive action.
  • The dashed empty state reserves a clear area for future application cards.
  • The centered footer closes the page with muted supporting text.
  • Save styles.css.
  • Refresh Live Preview to confirm the empty tracker area has a dashed border and centered text.

Is the empty state still plain text?

Confirm that the placeholder in index.html has the empty-state class. Check that the selector includes the leading period in styles.css.

Help me troubleshoot the DevTrack empty state styles.

A media query changes layout rules when the available width falls below a defined breakpoint. DevTrack uses this technique to protect the form and tracker from being squeezed.

  • At the bottom of styles.css, add the narrow-screen layout with this code:
@media (max-width: 760px) {
  .site-header {
    padding-bottom: 3.5rem;
  }

  .dashboard {
    grid-template-columns: 1fr;
  }

  .tracker-header,
  .application-card {
    align-items: stretch;
    flex-direction: column;
  }
}

How does the breakpoint respond?

  • The media query activates when the viewport is 760 pixels wide or narrower.
  • The dashboard switches from two grid columns to one.
  • The tracker header and future cards stack their contents vertically.
  • Save styles.css.

Before you resize the preview, where do you expect the tracker panel to move as the available width shrinks?

  • Drag the edge of Live Preview to make the browser area narrower.
  • Expand Live Preview again to restore the wide layout.

Below the breakpoint, you will see the form and tracker stack into one column. On a wide view, you will see the form on the left and the tracker on the right.

Does the layout stay in two columns?

Confirm that the media query sits after the regular dashboard rules. Check that its closing brace appears at the bottom of styles.css.

Help me troubleshoot the DevTrack responsive breakpoint.

✔️ Awesome, I've got everything!

Your dashboard now has its complete wide and narrow layouts.

  • Save styles.css before continuing.

ⓧ I'd like to double check the full code

:root {
  color-scheme: light;
  font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  color: #172033;
  background: #f4f7fb;
  --primary: #4f46e5;
  --primary-dark: #3730a3;
  --surface: #ffffff;
  --muted: #667085;
  --border: #d9e0ea;
  --danger: #b42318;
  --shadow: 0 16px 40px rgba(23, 32, 51, 0.1);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  min-width: 320px;
  min-height: 100vh;
}

button,
input,
select {
  font: inherit;
}

button,
select {
  cursor: pointer;
}

.container {
  width: min(1100px, calc(100% - 2rem));
  margin-inline: auto;
}

.site-header {
  padding: 3rem 0 4.5rem;
  color: #ffffff;
  background: linear-gradient(135deg, #312e81, #4f46e5);
}

.site-header h1 {
  margin: 0.25rem 0;
  font-size: clamp(2.25rem, 7vw, 4.5rem);
}

.site-header p:last-child {
  max-width: 620px;
  margin-bottom: 0;
  font-size: 1.05rem;
}

.eyebrow {
  margin: 0;
  color: #c7d2fe;
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.dashboard {
  display: grid;
  grid-template-columns: minmax(280px, 360px) minmax(0, 1fr);
  gap: 1.5rem;
  margin-top: -2rem;
  padding-bottom: 3rem;
}

.panel {
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: 1rem;
  background: var(--surface);
  box-shadow: var(--shadow);
}

.panel h2 {
  margin-top: 0;
}

.application-form {
  display: grid;
  gap: 0.75rem;
}

.application-form label,
.filter-control {
  color: var(--muted);
  font-size: 0.875rem;
  font-weight: 700;
}

input,
select {
  width: 100%;
  padding: 0.75rem;
  border: 1px solid var(--border);
  border-radius: 0.65rem;
  color: #172033;
  background: #ffffff;
}

input:focus,
select:focus,
button:focus-visible {
  outline: 3px solid #c7d2fe;
  outline-offset: 2px;
}

.primary-button {
  margin-top: 0.5rem;
  padding: 0.8rem 1rem;
  border: 0;
  border-radius: 0.65rem;
  color: #ffffff;
  background: var(--primary);
  font-weight: 800;
}

.primary-button:hover {
  background: var(--primary-dark);
}

.tracker-header {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
}

.tracker-header .eyebrow {
  color: var(--primary);
}

.filter-control {
  display: grid;
  gap: 0.35rem;
  min-width: 150px;
}

.application-count {
  color: var(--muted);
}

.application-list {
  display: grid;
  gap: 0.75rem;
}

.application-card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem;
  border: 1px solid var(--border);
  border-radius: 0.8rem;
  background: #f8fafc;
}

.application-card h3,
.application-card p {
  margin: 0;
}

.application-card p {
  margin-top: 0.25rem;
  color: var(--muted);
}

.status-badge {
  display: inline-flex;
  margin-top: 0.65rem;
  padding: 0.3rem 0.65rem;
  border: 0;
  border-radius: 999px;
  color: var(--primary-dark);
  background: #e0e7ff;
  font-size: 0.8rem;
  font-weight: 800;
}

.delete-button {
  padding: 0.55rem 0.7rem;
  border: 1px solid #fecdca;
  border-radius: 0.55rem;
  color: var(--danger);
  background: #fef3f2;
  font-weight: 700;
}

.empty-state {
  margin: 0;
  padding: 2rem 1rem;
  border: 1px dashed var(--border);
  border-radius: 0.8rem;
  color: var(--muted);
  text-align: center;
}

.site-footer {
  padding: 1.5rem 0 2.5rem;
  color: var(--muted);
  text-align: center;
}

@media (max-width: 760px) {
  .site-header {
    padding-bottom: 3.5rem;
  }

  .dashboard {
    grid-template-columns: 1fr;
  }

  .tracker-header,
  .application-card {
    align-items: stretch;
    flex-direction: column;
  }
}

Your dashboard now adapts to the space around it. Next, you will use JavaScript to turn the styled interface into a working application tracker.

Add, Filter, and Delete Applications

Your responsive DevTrack dashboard is already running in Live Preview. Now the form needs to turn each submission into a visible application card.

In this step, JavaScript stores the current applications in an array. Events trigger fresh DOM updates whenever you add, filter, or delete an application.

In this step, get ready to:
  • Connect the form controls to JavaScript state.
  • Create safe application cards from submitted form values.
  • Filter and delete applications before testing a refresh.
Connect the interface to JavaScript state

The form already exposes stable element IDs. JavaScript can use those IDs to keep references to the controls that supply input or display tracker results.

  • Switch back to the empty script.js tab from earlier.
  • Connect the interface to JavaScript state by pasting this code into script.js:
const form = document.querySelector("#application-form");
const companyInput = document.querySelector("#company");
const roleInput = document.querySelector("#role");
const statusInput = document.querySelector("#status");
const statusFilter = document.querySelector("#status-filter");
const applicationCount = document.querySelector("#application-count");
const applicationList = document.querySelector("#application-list");

let applications = [];

What does this code do?

  • Each document.querySelector() call finds one element from index.html.
  • The input references provide the values entered through the form.
  • The output references let JavaScript update the count and application list.
  • The empty applications array becomes the current source of tracker data.
  • Save script.js.
  • Return to the Live Preview menu from earlier.
  • Select Open the editor's webview DevTools.
  • Switch to the console panel.
  • Inspect the current application state by entering this expression:
applications

What does this check prove?

The console displays []. This confirms that script.js loaded successfully and the tracker starts with an empty array.

Filtering starts with the selected status. The tracker returns every application for All or creates a filtered array for one hiring stage.

  • Return to script.js.
  • Add the filtering function below the applications array by pasting this code:
function getVisibleApplications() {
  if (statusFilter.value === "All") {
    return applications;
  }

  return applications.filter(
    (application) => application.status === statusFilter.value,
  );
}

How does filtering work?

  • The All branch returns the complete application array.
  • The filter() branch creates an array containing applications with the selected status.
  • The original array remains available for the total count.
  • Save script.js.
  • Return to the console panel from earlier.
  • Check the current filter result by entering this expression:
getVisibleApplications()

What does the result show?

The function returns [] because the tracker has no applications yet. The result confirms that the status control and array are connected.

Seeing a console error?

Check that every selector matches the corresponding ID in index.html. A missing hash symbol or changed ID prevents JavaScript from finding that element.

Confirm that script.js is saved before checking the console.

Ask for help with the DOM references in DevTrack.

Build the rendering engine

The renderer converts application objects into visible cards. It also replaces an empty list with a clear message and updates both counts.

The function is long enough to assemble in two parts. Add both parts in sequence before saving the file.

  • Place your cursor below the closing brace of getVisibleApplications().
  • Add the first half of renderApplications() by pasting this code:
function renderApplications() {
  const visibleApplications = getVisibleApplications();
  applicationList.textContent = "";
  applicationCount.textContent = `${visibleApplications.length} shown, ${applications.length} total`;

  if (visibleApplications.length === 0) {
    const emptyMessage = document.createElement("p");
    emptyMessage.className = "empty-state";
    emptyMessage.textContent =
      applications.length === 0
        ? "No applications yet. Add your first one."
        : "No applications match this filter.";
    applicationList.append(emptyMessage);
    return;
  }

How does the first half work?

  • The function gets the applications that match the selected filter.
  • Setting textContent to an empty string removes the previous list before each render.
  • The count reports the visible number beside the total number.
  • An empty result creates the appropriate message and ends the function early.
  • Keep your cursor directly after the first half.
  • Complete renderApplications() by pasting this second half:
  for (const application of visibleApplications) {
    const card = document.createElement("article");
    card.className = "application-card";

    const details = document.createElement("div");
    const company = document.createElement("h3");
    const role = document.createElement("p");
    const statusBadge = document.createElement("span");
    const deleteButton = document.createElement("button");

    company.textContent = application.company;
    role.textContent = application.role;
    statusBadge.className = "status-badge";
    statusBadge.textContent = application.status;
    deleteButton.className = "delete-button";
    deleteButton.textContent = "Delete";
    deleteButton.addEventListener("click", () => deleteApplication(application.id));

    details.append(company, role, statusBadge);
    card.append(details, deleteButton);
    applicationList.append(card);
  }
}

How are the cards created?

  • The loop creates one article for each visible application.
  • The card receives separate company, role, status, and delete elements.
  • Assigning form values through textContent keeps them as plain text.
  • Each delete button remembers the ID of its own application.
  • Save script.js.
  • Return to the console panel from earlier.
  • Render the current tracker by entering this statement:
renderApplications();

What does this render prove?

The function clears the tracker area and calculates the count from the empty array. It then creates the empty-state message as a DOM element.

Good progress. Your JavaScript now controls the visible count and empty tracker state.

Deletion changes the source array before rendering again. Every card can therefore disappear from both the data and the page through one function.

  • Locate the line that starts function renderApplications() {.
  • Insert the delete function immediately above that line by pasting this code:
function deleteApplication(id) {
  applications = applications.filter((application) => application.id !== id);
  renderApplications();
}

What does deletion change?

  • The function receives the selected application's ID.
  • The filter keeps every application with a different ID.
  • The next render rebuilds the cards from the updated array.
  • Save script.js.
  • Return to the console panel from earlier.
  • Inspect the loaded delete function by entering its name:
deleteApplication

What should the console show?

The console displays the function definition. This confirms that each future delete-button listener can call it.

Two final calls keep the tracker synchronized with its controls. One responds to filter changes while the other paints the initial empty state after each page load.

  • Place your cursor below the closing brace of renderApplications().
  • Connect the filter and initial render by pasting these lines:
statusFilter.addEventListener("change", renderApplications);

renderApplications();

How does the tracker stay current?

  • The change listener renders the list whenever the selected filter changes.
  • The final function call renders the tracker as soon as the script loads.
  • Save script.js.
  • Return to the Live Preview browser.

You should see 0 shown, 0 total above the message No applications yet. Add your first one.

Add applications and test every action

The submit event turns each set of form values into an application object. Calling preventDefault() keeps the browser on the dashboard while JavaScript resets and re-renders the form.

  • Return to script.js.
  • Locate statusFilter.addEventListener("change", renderApplications); near the bottom of the file.
  • Insert the submit listener immediately above that line by pasting this code:
form.addEventListener("submit", (event) => {
  event.preventDefault();

  const application = {
    id: Date.now(),
    company: companyInput.value,
    role: roleInput.value,
    status: statusInput.value,
  };

  applications = [application, ...applications];
  form.reset();
  renderApplications();
});

What happens after submission?

  • The listener intercepts the form's submit event.
  • The application object receives an ID plus the current company, role, and status values.
  • The new object moves to the beginning of the applications array.
  • The form resets before the tracker renders its updated cards.
  • Save script.js.
  • Return to the Live Preview browser.

Your finished script.js should match the reference below.

✔️ Awesome, I've got everything!

Your interactive tracker script is complete and saved.

ⓧ I'd like to double check the full code

const form = document.querySelector("#application-form");
const companyInput = document.querySelector("#company");
const roleInput = document.querySelector("#role");
const statusInput = document.querySelector("#status");
const statusFilter = document.querySelector("#status-filter");
const applicationCount = document.querySelector("#application-count");
const applicationList = document.querySelector("#application-list");

let applications = [];

function getVisibleApplications() {
  if (statusFilter.value === "All") {
    return applications;
  }

  return applications.filter(
    (application) => application.status === statusFilter.value,
  );
}

function deleteApplication(id) {
  applications = applications.filter((application) => application.id !== id);
  renderApplications();
}

function renderApplications() {
  const visibleApplications = getVisibleApplications();
  applicationList.textContent = "";
  applicationCount.textContent = `${visibleApplications.length} shown, ${applications.length} total`;

  if (visibleApplications.length === 0) {
    const emptyMessage = document.createElement("p");
    emptyMessage.className = "empty-state";
    emptyMessage.textContent =
      applications.length === 0
        ? "No applications yet. Add your first one."
        : "No applications match this filter.";
    applicationList.append(emptyMessage);
    return;
  }

  for (const application of visibleApplications) {
    const card = document.createElement("article");
    card.className = "application-card";

    const details = document.createElement("div");
    const company = document.createElement("h3");
    const role = document.createElement("p");
    const statusBadge = document.createElement("span");
    const deleteButton = document.createElement("button");

    company.textContent = application.company;
    role.textContent = application.role;
    statusBadge.className = "status-badge";
    statusBadge.textContent = application.status;
    deleteButton.className = "delete-button";
    deleteButton.textContent = "Delete";
    deleteButton.addEventListener("click", () => deleteApplication(application.id));

    details.append(company, role, statusBadge);
    card.append(details, deleteButton);
    applicationList.append(card);
  }
}

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const application = {
    id: Date.now(),
    company: companyInput.value,
    role: roleInput.value,
    status: statusInput.value,
  };

  applications = [application, ...applications];
  form.reset();
  renderApplications();
});

statusFilter.addEventListener("change", renderApplications);

renderApplications();
  • Enter Northstar Labs in the Company field.
  • Enter Frontend Developer in the Role field.
  • Select Applied in the Status control.
  • Click Add application.
  • Enter Orbit Systems in the Company field.
  • Enter Backend Developer in the Role field.
  • Select Interview in the Status control.
  • Click Add application.

You should see two application cards with their company, role, and status values. The count now reads 2 shown, 2 total.

Don't see the application cards?

Check the console for a spelling error in renderApplications() or the submit listener. One mismatched variable name can stop the remaining script.

Confirm that each form field contains a value. The required controls prevent an incomplete submission.

Help me debug why DevTrack does not add application cards.

  • Select Interview in the Filter control.

You should see only the Orbit Systems card. The count should read 1 shown, 2 total.

  • Select All statuses in the Filter control.

You should see both cards again. This proves that filtering changes the visible list while preserving the complete array.

  • Click Delete on the Northstar Labs card.

You should see only the Orbit Systems card. The count should now read 1 shown, 1 total.

Filter or delete not responding?

Check that the filter listener uses change and calls renderApplications.

Check that each delete button passes application.id into deleteApplication().

Help me debug DevTrack filtering or deletion.

Before you refresh, do you think the Orbit Systems card will remain in the tracker?

  • Refresh the Live Preview browser.

The remaining card disappears. You should see 0 shown, 0 total with the empty-state message again.

That shortfall is intentional. The applications array exists only in the current page session, so a refresh starts it from its empty declaration.

Your tracker now adds, filters, and deletes application cards during a page session. Next up, you'll make that state survive a refresh before publishing DevTrack.

Persist and Publish DevTrack

Your responsive tracker can add, filter, and delete applications in browser memory. However, each refresh clears that work because the applications only live in the current page session.

This step adds browser persistence with localStorage and JSON. You will then use Git to publish the completed project to GitHub.

In this step, get ready to:
  • Save application data as a JSON string after each add or delete action.
  • Restore saved applications before the first render.
  • Publish the completed DevTrack files to GitHub.
Save applications in the browser

LocalStorage saves strings for the current website origin. JSON.stringify() converts the applications array into a string that the browser can store.

  • Return to script.js in Visual Studio Code.
  • Place the storage key above the form reference by adding this line:
const STORAGE_KEY = "devtrack-applications";

Why Use One Storage Key?

The STORAGE_KEY constant gives the save and load functions one shared name for the browser entry.

  • Find let applications = []; in script.js.
  • Place the save function directly below that line by adding this code:
function saveApplications() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(applications));
}

What Does This Function Do?

  • The localStorage.setItem call writes data under the shared storage key.
  • The JSON.stringify() call converts the current applications array into storable text.
  • Scroll to the form submit listener in script.js.
  • Find this sequence:
  applications = [application, ...applications];
  form.reset();
  renderApplications();

What Happens Currently?

This sequence updates browser memory before resetting the form. It renders the new card without saving the updated array.

  • Replace that sequence with this saved version:
  applications = [application, ...applications];
  saveApplications();
  form.reset();
  renderApplications();

What Changed?

The submit listener now calls saveApplications() immediately after updating the array. The saved JSON therefore includes the application that was just added.

  • Save script.js.
  • In Live Preview, enter Acme Labs in the Company field.
  • Enter Frontend Developer in the Role field.
  • Select Applied in the Status selector.
  • Select Add application.
  • In Live Preview's expandable menu, select Open the editor's webview DevTools.
  • Switch to the console panel in the developer tools.
  • Read the saved browser entry by running this expression:
localStorage.getItem(STORAGE_KEY)

What Should This Return?

This expression reads the entry created by saveApplications(). You will see a JSON string containing the saved company, role, status, and ID values.

Your new card is now represented in browser storage. The next change keeps that stored copy accurate when you delete a card.

  • Return to script.js.
  • Find the current deleteApplication() function:
function deleteApplication(id) {
  applications = applications.filter((application) => application.id !== id);
  renderApplications();
}

What Happens During Deletion?

The current function removes the matching object from browser memory. Its stored copy still contains the deleted application.

  • Replace that function with this saved version:
function deleteApplication(id) {
  applications = applications.filter((application) => application.id !== id);
  saveApplications();
  renderApplications();
}

How Does Deletion Stay Saved?

The function now saves the filtered array before rendering it. This keeps the page state and stored state synchronized.

  • Save script.js.
  • Select Delete on the Acme Labs card.
  • Return to the developer tools console.
  • Read the updated browser entry by running the expression again:
localStorage.getItem(STORAGE_KEY)

What Does the Updated Value Prove?

The stored JSON no longer contains the deleted card. This proves that both add and delete actions update the same browser entry.

Do You See a Missing Storage Value?

Confirm that you saved script.js after adding the storage key. Add another application before checking the console again.

Check that saveApplications() appears immediately after the applications array changes in both listeners.

Help me debug why DevTrack is not saving applications to localStorage.

Restore applications after a refresh

Saving creates the browser entry. Restoring requires JSON.parse() to turn that stored string back into an array.

A try...catch fallback protects the tracker if the stored text cannot be parsed. DevTrack starts with an empty array in that case.

  • In script.js, place the load function directly below saveApplications() by adding this code:
function loadApplications() {
  const savedApplications = localStorage.getItem(STORAGE_KEY);

  if (!savedApplications) {
    return;
  }

  try {
    applications = JSON.parse(savedApplications);
  } catch {
    applications = [];
  }
}

How Does Loading Work?

  • The localStorage.getItem call reads the JSON string saved under STORAGE_KEY.
  • The early return keeps the existing empty array when no saved entry exists.
  • The JSON.parse() call restores valid saved text as application objects.
  • The fallback replaces invalid stored text with an empty array.
  • Scroll to the bottom of script.js.
  • Find these final lines:
statusFilter.addEventListener("change", renderApplications);

renderApplications();

What Happens on Startup Currently?

The page immediately renders the empty in-memory array. It never checks browser storage first.

  • Replace those final lines with this load-first sequence:
statusFilter.addEventListener("change", renderApplications);

loadApplications();
renderApplications();

Why Load Before Rendering?

The loadApplications() call restores the array before renderApplications() builds the visible cards. The first render therefore reflects saved browser data.

  • Save script.js.
  • In Live Preview, enter Northstar Studio in the Company field.
  • Enter UI Engineer in the Role field.
  • Select Interview in the Status selector.
  • Select Add application.

Before you refresh, predict whether the Northstar Studio card will return.

  • Refresh the Live Preview browser.

You will see the Northstar Studio card return with its UI Engineer role and Interview status. That closes the refresh gap that erased every application in the previous step.

Does the Card Disappear After Refreshing?

Confirm that loadApplications() appears before the final renderApplications() call. Reversing those calls renders the empty array first.

Check that the storage key is spelled devtrack-applications everywhere it appears.

Help me debug why my saved DevTrack application disappears after refreshing Live Preview.

✔️ Awesome, I've got everything!

Great work. Save script.js before publishing the project.

ⓧ I'd like to double check the full code

const STORAGE_KEY = "devtrack-applications";

const form = document.querySelector("#application-form");
const companyInput = document.querySelector("#company");
const roleInput = document.querySelector("#role");
const statusInput = document.querySelector("#status");
const statusFilter = document.querySelector("#status-filter");
const applicationCount = document.querySelector("#application-count");
const applicationList = document.querySelector("#application-list");

let applications = [];

function saveApplications() {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(applications));
}

function loadApplications() {
  const savedApplications = localStorage.getItem(STORAGE_KEY);

  if (!savedApplications) {
    return;
  }

  try {
    applications = JSON.parse(savedApplications);
  } catch {
    applications = [];
  }
}

function getVisibleApplications() {
  if (statusFilter.value === "All") {
    return applications;
  }

  return applications.filter(
    (application) => application.status === statusFilter.value,
  );
}

function deleteApplication(id) {
  applications = applications.filter((application) => application.id !== id);
  saveApplications();
  renderApplications();
}

function renderApplications() {
  const visibleApplications = getVisibleApplications();
  applicationList.textContent = "";
  applicationCount.textContent = `${visibleApplications.length} shown, ${applications.length} total`;

  if (visibleApplications.length === 0) {
    const emptyMessage = document.createElement("p");
    emptyMessage.className = "empty-state";
    emptyMessage.textContent =
      applications.length === 0
        ? "No applications yet. Add your first one."
        : "No applications match this filter.";
    applicationList.append(emptyMessage);
    return;
  }

  for (const application of visibleApplications) {
    const card = document.createElement("article");
    card.className = "application-card";

    const details = document.createElement("div");
    const company = document.createElement("h3");
    const role = document.createElement("p");
    const statusBadge = document.createElement("span");
    const deleteButton = document.createElement("button");

    company.textContent = application.company;
    role.textContent = application.role;
    statusBadge.className = "status-badge";
    statusBadge.textContent = application.status;
    deleteButton.className = "delete-button";
    deleteButton.textContent = "Delete";
    deleteButton.addEventListener("click", () => deleteApplication(application.id));

    details.append(company, role, statusBadge);
    card.append(details, deleteButton);
    applicationList.append(card);
  }
}

form.addEventListener("submit", (event) => {
  event.preventDefault();

  const application = {
    id: Date.now(),
    company: companyInput.value,
    role: roleInput.value,
    status: statusInput.value,
  };

  applications = [application, ...applications];
  saveApplications();
  form.reset();
  renderApplications();
});

statusFilter.addEventListener("change", renderApplications);

loadApplications();
renderApplications();

How to Use This Reference

Compare the storage key, save and load functions, save calls, and final startup order with your file. Every persistence change should appear in the same position.

Publish the completed project

Browser persistence completes the local application. Version control now records the finished source so the repository contains the same project you can see in Live Preview.

  • Use Visual Studio Code's top menu to open the integrated terminal.
  • Confirm that the terminal path ends with the cloned devtrack folder.

GitHub might ask you to confirm your identity during the push. This authentication step allows your signed-in account to update the repository.

  • Stage, commit, and push the completed files by running this workflow:

Before you run these commands, predict what the GitHub repository page will show afterward.

git add .
git commit -m "Add existing file"
git push origin main

What Does This Workflow Do?

  • The first command stages the changed project files in the local repository.
  • The second command records the staged files as a named commit.
  • The third command sends that commit to the main branch on the connected remote repository.
  • Wait for the integrated terminal to finish the push.
  • Return to the DevTrack GitHub repository page from earlier.
  • Refresh the repository page.
  • Confirm that index.html, styles.css, and script.js appear in the file list.

You have the complete foundation published. DevTrack now restores saved applications after a refresh and keeps its finished source in GitHub.

Did the Publish Workflow Stop?

If the commit does not complete, read the terminal message for a missing identity setting or an unchanged working tree. Resolve the named issue before running the workflow again.

If the push is blocked, complete GitHub's authentication flow for the account that owns the repository. Confirm that the destination branch is main.

Help me troubleshoot why my DevTrack Git commit or push did not complete.

Secret mission

Advance an Application Through the Hiring Pipeline

Turn each status badge into a control that moves its application through the hiring pipeline. The latest stage stays saved when you refresh DevTrack.

Clean Up Your Resources

Clean Up Your Resources

Choose whether to keep DevTrack available, pause your local workspace, or delete its resources. This project has no ongoing usage-based costs.

Resources you used:

  • The local devtrack project folder containing index.html, styles.css, and script.js.
  • The devtrack repository on GitHub.
  • The devtrack-applications localStorage entry for the Live Preview origin.

Keep everything running

No action is needed. Choose this option if you want to continue developing DevTrack or use it in a later project.

  • Your devtrack folder stays available with the latest Secret Mission code.
  • Your GitHub repository keeps the version you last pushed.
  • Your devtrack-applications entry continues restoring saved applications after each refresh.

Pause - I'll come back to this later

Pausing ends the active preview session. Your complete DevTrack project remains ready for your return.

  • Stop the Live Preview session.
  • Close Visual Studio Code.

Your local files remain saved. Your repository and browser data also stay available.

Delete - I don't want to use this again

This option removes every DevTrack resource listed above.

Review the Impact First

These actions target only the DevTrack resources listed above. Your installed tools remain in place. Your GitHub account also remains active.

The storage command clears every localStorage item for the current Live Preview origin.

Clear Saved Applications
  • Return to the Live Preview page in Visual Studio Code.
  • Expand the Live Preview menu.
  • Select Open the editor's webview DevTools.
  • Select Console in DevTools.
  • Clear the saved browser data by running this command:
localStorage.clear();

What Does This Command Do?

The localStorage.clear() command removes every localStorage item for the current Live Preview origin. Refreshing DevTrack then starts the application with an empty array.

  • Refresh the Live Preview page.

You should see the empty-state message No applications yet. Add your first one..

Still Seeing Saved Cards?

Make sure the console belongs to the Live Preview webview. A console for another page targets a different origin.

Help me clear DevTrack's saved browser data.

Delete the GitHub Repository
  • Return to the devtrack repository on GitHub.
  • Select Settings.
  • Scroll to Danger Zone.
  • Select Delete this repository.
  • Complete GitHub's confirmation prompts.
  • Type devtrack when GitHub asks for the repository name.
  • Select Delete this repository to confirm.

The devtrack repository should no longer be available at its former GitHub URL.

Remove the Local Project Folder
  • Press the Windows key to open search.
  • Type File Explorer into search.
  • Press Enter to open Windows File Explorer.
  • Locate the devtrack folder you cloned earlier.
  • Select the devtrack folder.
  • Press the Delete key to remove it.

The devtrack folder should no longer appear in its original location.

Nice Work!

Nice Work!

You did it! DevTrack is now a responsive job-application dashboard for tracking each hiring stage. Its persistent hiring pipeline is your front-end foundation for later projects in this learning path.

You've learned how to:

  • Build an accessible application-entry form with semantic HTML that captures each company, role, and hiring status.
  • Create a responsive dashboard with CSS Grid that shifts from two columns to one on narrow screens.
  • Use JavaScript state and DOM events to add application cards. Filter cards by hiring status. Delete unwanted cards. Preserve the latest data with localStorage and JSON serialization. Publish the finished source with Git to GitHub.
  • Secret Mission: Advance each saved application through Applied, Interview, and Offer with a persistent status control.

Ready to quiz yourself?