HAP meeting with Grace Hopper to review AI-generated code

Station 6: AI and Your Event-Driven Code

When AI doesn't know your architecture

Welcome to Station 6! After Station 5, I had a fully working interactive robot card app. Delegation, view switching, forms — the page was alive.

So I asked the AI for help building more. I wanted an "About" section. What I got back taught me more than any of the previous stations.

The AI does not know my app's architecture. It does not know I built a single-page app. It does not know about my AGENTS.md rules. And even when I caught some mistakes, there was one I missed completely.

Let me show you the three layers of problems I found — and the four-layer defense system that protects me even when I cannot catch everything myself... 🟠

Quick Reference

Your Defense-in-Depth Playbook →

What You'll Learn at This Station

HAP's Discovery: AI code generation is powerful, but AI does not know my app's architecture, my security rules, or the subtle differences between patterns that work and patterns that are technically correct but architecturally wrong. I needed layers of protection that do not depend on me being perfect.

🏗️ Architecture Assumptions

AI guesses wrong

AI assumed my single-page app was a multi-page site and generated navigation links to files that do not exist.

🔍 Catching Security Patterns

innerHTML vs createElement

I caught the innerHTML problem because of AGENTS.md from the DOM lab. But the next issue was subtler.

🛡️ Defense in Depth

4 independent layers

AGENTS.md prevents. ESLint detects. Husky enforces. Copilot code review catches what everything else misses.

HAP looking concerned at laptop screen showing AI-generated code

HAP's Confession:

  • I asked the AI for an about page and got href="about.html". The AI assumed my app was multi-page.
  • I caught the innerHTML on the second try — only because my AGENTS.md reminded me.
  • I missed the onclick property assignment completely. Grace caught it. I did not even know there was a difference.
  • I realized AGENTS.md only works if the AI reads and follows it. ESLint and Husky do not depend on AI cooperation.

The Three-Layer Scene

After building my mini SPA at Station 5, I was feeling confident. I asked the AI: "Help me add an About section to my robot card app." What followed was a lesson in three layers of problems I did not expect.

HAP working at laptop, asking AI for help

HAP's Request to the AI:

"I have a robot card app. I want to add an About section. Can you write the code?"

Sounds simple. But what the AI generated revealed three layers of assumptions I had to catch — and one I could not.

Layer 1: AI Does Not Know My Architecture

The AI's first attempt created a link to about.html. But I do not have an about.html. My whole app is a single page with view functions.

What the AI generated:
// What the AI generated:
function addAboutSection() {
  const aboutLink = document.createElement('a');
  aboutLink.href = 'about.html';
  aboutLink.textContent = 'About';
  nav.appendChild(aboutLink);
}

// The problem: I don't HAVE an about.html.
// My whole app is one page. The AI assumed
// a traditional multi-page site.

I caught this one right away. The AI assumed a traditional multi-page site because I did not tell it otherwise. My fix: tell the AI to make a renderAbout() view function instead.

What I asked for instead:
// What I told the AI to do instead:
// "Make a renderAbout() view function —
//  my app is a single page, not multi-page."

function renderAbout() {
  // Show the about view, hide others
  // (like the view-switching pattern from Station 3)
}

🟠 Lesson:

AI does not know my app is a single-page app unless I tell it. The AI has no context about my project's architecture — it generates code based on the most common patterns it has seen, and multi-page is more common.

Layer 2: AI Reaches for innerHTML

The AI listened. It wrote a renderAbout() view function. But it used innerHTML with a template literal to build the content.

The AI's second attempt:
// The AI's renderAbout() — version 2:
function renderAbout() {
  const container = document.querySelector('.main-content');
  container.innerHTML = `
    <div class="about-section">
      <h2>About Robot Cards</h2>
      <p>This app lets you manage your robot collection.</p>
      <button class="back-btn">Back to Cards</button>
    </div>
  `;
}

// It works. But innerHTML with a template literal
// is the exact pattern Grace warned about in the DOM lab.
// My AGENTS.md says: "Never use innerHTML."
// I caught this one.

This works. The about section renders correctly. But innerHTML with dynamic content is exactly the pattern Grace warned about in the DOM lab. My AGENTS.md already says "Never use innerHTML." I caught this one — but only because I had that rule written down.

🟠 Lesson:

AGENTS.md saved me here. Without that written rule, I might have accepted the innerHTML version because it works. The danger is not in code that fails — it is in code that works but introduces a security risk.

Layer 3: The Subtle Issue

The AI tried again. This time it used createElement — the safe way I learned in the DOM lab. No innerHTML. I was ready to accept it. Then Grace spoke up.

The AI's third attempt (createElement, but...):
// The AI's renderAbout() — version 3 (createElement):
function renderAbout() {
  const container = document.querySelector('.main-content');
  container.textContent = '';

  const section = document.createElement('div');
  section.classList.add('about-section');

  const heading = document.createElement('h2');
  heading.textContent = 'About Robot Cards';

  const description = document.createElement('p');
  description.textContent = 'This app lets you manage your robot collection.';

  const backBtn = document.createElement('button');
  backBtn.classList.add('back-btn');
  backBtn.textContent = 'Back to Cards';

  // The subtle issue:
  backBtn.onclick = () => renderHome();
  //      ^^^^^^^
  // onclick property assignment, not addEventListener

  section.append(heading, description, backBtn);
  container.appendChild(section);
}

Grace Hopper:

"The onclick property works. But it permits only one handler per event. addEventListener permits many. The AI chose convenience. You must decide whether convenience is acceptable for your architecture."

I stared at the code. backBtn.onclick = () => renderHome(). It looked fine to me. But Grace was right — onclick is a property assignment. If anything else sets onclick on that button, it overwrites my handler. addEventListener stacks handlers — multiple things can listen to the same event.

The correct version:
// The correct version — addEventListener:
function renderAbout() {
  const container = document.querySelector('.main-content');
  container.textContent = '';

  const section = document.createElement('div');
  section.classList.add('about-section');

  const heading = document.createElement('h2');
  heading.textContent = 'About Robot Cards';

  const description = document.createElement('p');
  description.textContent = 'This app lets you manage your robot collection.';

  const backBtn = document.createElement('button');
  backBtn.classList.add('back-btn');
  backBtn.textContent = 'Back to Cards';

  // addEventListener — permits multiple handlers
  backBtn.addEventListener('click', () => renderHome());

  section.append(heading, description, backBtn);
  container.appendChild(section);
}

What I Caught and What I Missed

HAP reflecting on the three layers of AI code problems

HAP's Reflection:

I caught the wrong architecture — href="about.html" was obvious because I know my app is a single page.

I caught the innerHTML — but only because AGENTS.md reminded me.

I missed the onclick. Grace caught it. I did not even know there was a difference between onclick and addEventListener.

The more I learn, the more I can see — but there is always another layer. 🟠

Defense in Depth

If Grace had not been here, how would I catch these problems? That question led me to the answer: I need layers of protection that do not depend on me being perfect.

Layer 1: AGENTS.md — Prevention

Tell the AI what to generate and what to avoid. Architecture rules ("this is an SPA, never generate new HTML files") and code rules ("use addEventListener, not onclick"). This is the first line of defense — but it only works if the AI reads and follows it.

Layer 2: ESLint Plugins — Detection

eslint-plugin-unicorn has a prefer-add-event-listener rule that flags btn.onclick = .... eslint-plugin-no-unsanitized (from Mozilla) flags innerHTML assignments. These scan code after it is written — they do not depend on the AI cooperating.

Layer 3: Husky + lint-staged — Enforcement

Already in ready-build from Week 1. Runs ESLint at commit time. Even if I ignore warnings in the editor, the commit hook blocks bad code from entering the repo. The gate does not open for code that breaks the rules.

Layer 4: Copilot Code Review — AI Reviewer

Available via CLI: gh pr edit --add-reviewer @copilot. Analyzes full repo context, not just the diff. It is like having a reviewer who never gets tired and never misses a PR.

AGENTS.md additions for event-driven code:
# AGENTS.md additions for event-driven code

## Architecture
- This is a single-page app (SPA).
- Never generate code that creates new HTML files
  or uses window.location for navigation.
- All "pages" are view functions that show/hide
  content within index.html.

## Code rules
- Use addEventListener, not onclick property assignments.
- Never use innerHTML — use createElement and textContent.
- All event listeners use named callbacks or arrow functions
  passed to addEventListener.

I added these exact rules to the Robot ID Card app. You can see them in the source code on GitHub. The app uses addEventListener everywhere, createElement instead of innerHTML, and classList instead of inline styles. That is the prevention layer in action.

ESLint plugins that catch event-code issues:
// ESLint plugins for event-driven code safety

// eslint-plugin-unicorn
// Rule: prefer-add-event-listener
// Flags: btn.onclick = () => { ... }
// Suggests: btn.addEventListener('click', () => { ... })

// eslint-plugin-no-unsanitized (Mozilla)
// Rule: no-unsanitized/property
// Flags: element.innerHTML = userInput
// Flags: element.outerHTML = template

Grace Hopper:

"Defense in depth. You do not rely on a single mechanism. Each layer is independent. Each catches what the others miss."

Four Versions of renderAbout()

Here are all four versions the AI generated, in order. Each one got closer to correct, but each had a different kind of problem.

Version 1: Wrong Architecture

Used href="about.html". Assumed multi-page. I caught it because I know my app is a single page.

Version 2: Security Risk

Used innerHTML with a template literal. Works, but the DOM lab taught me this is a security risk. AGENTS.md caught it.

Version 3: Subtle Issue

Used createElement (safe) but onclick property (only one handler allowed). Grace caught it. I missed it completely.

Version 4: Correct

Uses createElement and addEventListener. Safe, extensible, follows the pattern from Station 1. This is the version that belongs in the app.

Defense-in-Depth Quick Reference

1

AGENTS.md — Prevention

Add architecture rules ("this is an SPA") and code rules ("use addEventListener, not onclick"). Prevents the AI from generating wrong patterns in the first place.

2

ESLint Plugins — Detection

eslint-plugin-unicorn (prefer-add-event-listener) and eslint-plugin-no-unsanitized (flags innerHTML). Scans code regardless of who wrote it.

3

Husky + lint-staged — Enforcement

Runs ESLint at commit time. Bad code cannot enter the repo, even if warnings are ignored in the editor.

4

Copilot Code Review — AI Reviewer

gh pr edit --add-reviewer @copilot. Reviews the full repo context on every PR. A second pair of eyes that never has to recharge.

Learning Objectives Checklist

Congratulations on completing all six stations of HAP's Learning Lab! Before you finish, verify you have mastered the core event-handling patterns and the principles of responsible AI use:

Event Fundamentals

  • I can wire up an event listener with addEventListener and explain the three-part pattern
  • I can read event.target and event.type to make smart decisions inside a callback
  • I can explain the difference between event.target and event.currentTarget

Interactive Patterns

  • I can switch views on a single page by toggling a .hidden class
  • I can handle form submissions with preventDefault and read values with FormData
  • I can use event delegation with .closest() to handle dynamic content

AI-Assisted Development

  • I can identify when AI-generated code assumes the wrong architecture
  • I can spot innerHTML and onclick patterns and replace them with safer alternatives
  • I can describe the four layers of defense: AGENTS.md, ESLint, Husky, and Copilot review

HAP's Closing Reflection:

From "the page just sits there" to "I can make any element respond to anything, and I have layers of protection that do not depend on me being perfect."

AGENTS.md is prevention. ESLint is detection. Husky is enforcement. And Copilot code review? That is like having a Grace who never has to recharge. 🟠

Quick Reference

Your Defense-in-Depth Playbook →