How to Use This Resource
Course 4.5 Context
Course 4.5 Module 3 uses these scenarios when students don't bring their own broken tools. Run the Debugging Clinic Protocol: Student Presentation 2 min, Group Diagnosis 3 min, Instructor Synthesis 2 min, Document Pattern. That's 7 min per scenario, roughly 5 scenarios in 35 min.
Standalone Use
An NCO running an informal team debrief on a Tuesday can pull this URL up and run a 30-minute group debug without standing up the whole course apparatus. Pick one scenario, walk through it as a group. Two minutes to read it aloud, five minutes for the room to throw out diagnostic questions, two minutes for whoever has the right instinct to walk through the fix, three minutes to write the pattern down somewhere your section can find it again.
Reuse Outside Course 4.5
Course 3.5 graduates encountering these bugs in the field will Google "edd fetch race debug" and land here. That is intentional. The scenarios are written to be useful on their own, not just inside the course flow. If you are an NCO troubleshooting a tool a Marine in your section built and the symptoms match one of these, work the scenario before going back to the AI.
The Clinic Protocol in One Box
- Student Presentation (2 min). The presenter reads the Tool, Symptom, and Attempted Fixes aloud. No diagnostics yet.
- Group Diagnosis (3 min). The room asks diagnostic questions in order of triage value. The presenter answers from what they know about their own code.
- Instructor Synthesis (2 min). Reveal the answer key. Name the root cause. Connect it back to one of the three recurring patterns.
- Document Pattern. Add a row to your Frontier Map for Frontier or Meta-frontier bugs. Context bugs go in the unit's tribal-knowledge doc, not the Frontier Map.
Scenario 1: Fetch Race / Double-Submit
Tool
Counseling-tracker static page that POSTs form data to a Google Sheets webhook on submit.
Symptom
Occasionally creates duplicate rows in the sheet. When the user clicks Submit fast (double-clicks), it always duplicates.
Student's Attempted Fixes
- "I added
event.preventDefault()at the top of the click handler." - "I added a flag variable,
isSubmitting = true, and check it before the fetch." - "I asked the AI for help. It gave me debounce code wrapped around the click handler. The duplicates still happen on a hard double-click."
Diagnostic Questions (the room asks)
- Where is the click handler attached? On the button, or on the form?
- Is there ALSO a default form-submit handler firing?
- When the user double-clicks, in what order do the events fire?
- What does
event.preventDefault()actually prevent on each handler? - Does the debounce wrap the form-submit handler too, or just the click handler?
- If you disable the submit button on first click, does the second click still produce a form-submit event?
Answer Key (do not open until the room has diagnosed)
Root Cause
The AI's debounce wraps the click handler, but the form has BOTH a click handler on the button AND a default submit handler on the form element. A double-click triggers the click handler once (debounced) and the form-submit twice (not debounced, because the click event bubbles into the form's submit event independently). Two fetches go out. Two rows land in the sheet. The flag variable was checked inside the click handler only, so the form-submit path skipped the guard entirely.
Solution Approach
Single guard flag on the form-submit event. Set the flag true at the start of the async request, set it false in .finally(). Toggle the submit button's disabled attribute in sync so the user gets visual feedback. Remove the click handler entirely. The submit event covers Enter-key submission too, so you do not lose keyboard accessibility.
form.addEventListener('submit', async (event) => {
event.preventDefault();
if (form.dataset.busy === 'true') return;
form.dataset.busy = 'true';
submitBtn.disabled = true;
try {
await fetch(WEBHOOK_URL, { method: 'POST', body: new FormData(form) });
} finally {
form.dataset.busy = 'false';
submitBtn.disabled = false;
}
});
Frontier Classification
Context. The AI didn't know there were two handlers attached because the student only pasted the click handler. The AI's debounce was correct for the code it could see. Fix is to give the AI the whole event-binding picture, or skip the AI and reason about the event flow yourself.
Scenario 2: Stale localStorage After Schema Change
Tool
A TEEP-like tracker the student built last week. They added a new field, t_and_r_reference, to the form yesterday.
Symptom
Existing rows render with undefined in the new column. Saving any edit overwrites the field with empty. The student cleared localStorage locally and it worked, but a Marine in the field reopened an old browser tab from before the field was added and lost data.
Student's Attempted Fixes
- "I wrapped the render in a
|| ''fallback so theundefinedstopped showing." - "But the saves from old tabs still wipe the field on rows that already had the new data."
- "I added a comment in the code that says 'do not open old tabs.' That obviously is not a fix."
Diagnostic Questions (the room asks)
- When the old tab writes back, what version of the code is it running?
- What localStorage key is it writing to?
- What happens on read if the JSON shape changes between code versions?
- Is there a way for one tab to know whether another tab has the new code loaded?
- How would you migrate the data without breaking the still-open old tabs?
- What's the cost of a one-time migration vs. ongoing compatibility shims?
Answer Key (do not open until the room has diagnosed)
Root Cause
Schema version mismatch across tabs. The new tab loaded the new code with the new field. The old tab is still running yesterday's code and writes back JSON without the new field. Both write to the same edd-teep-v1 key, so the old tab silently overwrites the new tab's data on every save. The fallback in render hid the symptom but did not stop the data loss.
Solution Approach
Bump the localStorage key from v1 to v2 (edd-teep-v2). Write a migration function that runs at page load: read v1, if present, copy each row to v2 adding the new field with a default value, leave v1 in place as read-only for any still-open old tabs. New writes go to v2 only. Include a schemaVersion field in the JSON so any old tab that does happen to read v2 knows not to write back. Optionally add a load-time check that compares the in-page code version to the stored schemaVersion and shows a "reload to update" banner if mismatched.
const SCHEMA_VERSION = 2;
const KEY = 'edd-teep-v2';
function migrate() {
if (localStorage.getItem(KEY)) return;
const old = localStorage.getItem('edd-teep-v1');
if (!old) return;
const rows = JSON.parse(old).map(r => ({
...r,
t_and_r_reference: r.t_and_r_reference || '',
schemaVersion: SCHEMA_VERSION
}));
localStorage.setItem(KEY, JSON.stringify(rows));
}
Frontier Classification
Frontier. The AI does not natively reason about cross-tab consistency or schema migration. It will generate a working tool on day one and a working schema change on day two and never warn you that day-two breaks day-one tabs. Students must learn the pattern explicitly and prompt for it. Add it to the Frontier Map.
Scenario 3: CSS Specificity Collision Plus Timezone Bug
Tool
The student's watch-bill output table. They added a "highlight today" class so the current day's row stands out.
Symptom
The row stays default-colored. After adding !important, it highlights, but also highlights yesterday and tomorrow. The student asked the AI for a stricter date comparison, got ===, but rows still mismatch.
Student's Attempted Fixes
- "I made the
.highlightclass!important. Now it highlights, but also highlights yesterday and tomorrow." - "I asked the AI to make the date comparison stricter and it generated code that uses
===. The row still matches yesterday." - "I tried storing the date as a string instead of a Date object. Same problem."
Diagnostic Questions (the room asks)
This is two bugs stacked. Work them separately.
- Why didn't the class apply at all before
!important? - What's the existing CSS rule for
tr td.dayvs.tr.highlight td? - What are the specificity scores of those two selectors?
- When you compare
new Date(dateStr)tonew Date(), what timezone is each in? - What does
toDateString()return at 23:00 local time east of UTC vs. 02:00 local time west of UTC? - If two Marines run this tool at the same wall-clock moment in different timezones, do they see the same "today"?
Answer Key (do not open until the room has diagnosed)
Root Cause (Two Bugs)
(a) Specificity collision. tr.highlight td { background: ...} is being overridden by the existing tr td.day { background: ...} rule, because the td-class selector wins on specificity (0,1,2 vs. 0,2,1, the td-class side has more class selectors at a lower level). The new rule never wins until !important is bolted on, and !important is not actually fixing the highlight scope.
(b) Timezone mismatch. new Date(dateStr) on a bare YYYY-MM-DD string gives midnight UTC. Comparison against new Date() (local "now") hits yesterday's date for any user west of UTC after about 16:00 local, and tomorrow's for users east of UTC before about 08:00 local. The === the AI suggested is a stricter comparison of two values that are already in different timezones, so it does not help.
Solution Approach
Drop !important. Restructure to set the highlight class on the <td> directly (td.day.highlight) matching specificity, OR change the existing tr td.day rule to tr:not(.highlight) td.day so the highlight row falls through. For the date comparison, use toDateString() on both sides (which renders in local time) OR compare on local date parts (getFullYear(), getMonth(), getDate()) so both sides are in the user's local timezone.
function isToday(dateStr) {
const d = new Date(dateStr + 'T00:00:00'); // local midnight
const now = new Date();
return d.getFullYear() === now.getFullYear()
&& d.getMonth() === now.getMonth()
&& d.getDate() === now.getDate();
}
Frontier Classification
Mixed. The specificity collision is Context: the AI didn't know about the existing tr td.day rule in the stylesheet because the student only pasted the new rule. The timezone bug is Frontier: AI commonly produces date comparisons that ignore timezone semantics unless you prompt for it. Two different lessons in one bug.
Scenario 4: Event Delegation on Dynamic Content
Tool
A list-based tool (call it a Tasking Tracker) where rows are re-rendered on every state change.
Symptom
Clicking "Delete" works on initial-render rows. Clicking "Delete" on rows added later in the session is silent. The AI suggested addEventListener on each button after rendering, which the student did, but new rows still don't respond reliably.
Student's Attempted Fixes
- "I added the click handler in the render function for every button."
- "The AI said use
addEventListeneron each button after rendering, which I do, but new rows added later still don't respond." - "I added a
console.login the handler. It never fires for the new rows." - "I tried
removeEventListenerbefore re-adding. Same problem."
Diagnostic Questions (the room asks)
- When you call
render(), what happens to the existing<tbody>and its child buttons? - When you re-attach handlers in render, what DOM nodes are you re-attaching to?
- Are the old handler references being garbage-collected with their nodes?
- What if the user clicks during the render window?
- What is a single handler that survives every re-render?
- If the handler lives on the container, how does it know which row was clicked?
Answer Key (do not open until the room has diagnosed)
Root Cause
The render function tears down and rebuilds the entire <tbody> on each state change. Per-element handlers attached inside render() exist only for the lifetime of that render's DOM nodes. The next render destroys those nodes and their handlers. New buttons get new handlers in the next render, so the user perceives it as working "sometimes." The real architecture fix is event delegation: one handler on a stable parent element that dispatches by inspecting the click target.
Solution Approach
One click listener on <tbody> (or whatever container survives renders). Dispatch based on event.target.closest('[data-action]'). Each button gets a data-action="delete" and data-row-id="N" attribute instead of a per-element handler. The handler reads the attributes, dispatches to the appropriate state mutation, triggers a re-render. The listener survives all future renders because the container element never gets replaced.
tbody.addEventListener('click', (event) => {
const btn = event.target.closest('[data-action]');
if (!btn) return;
const action = btn.dataset.action;
const id = btn.dataset.rowId;
if (action === 'delete') {
state.rows = state.rows.filter(r => r.id !== id);
render();
}
});
Frontier Classification
Frontier. AI commonly defaults to per-element handlers and rarely suggests delegation as the architecture fix unless you ask for it. Once you prompt with "use event delegation," the AI nails it. This is a high-value Frontier Map entry: any list-based tool with re-rendering is a candidate. Document it once, prompt with it forever.
Scenario 5: Tool-Selection Failure
Tool
The student is refactoring a 600-line static-HTML readiness dashboard to add a new pivot view. Three files are involved: dashboard.html, sample-data.json, and a separate stylesheet.
Symptom
The student has been in a genai.mil chat session for 90 minutes, 47 prompt iterations. The AI keeps suggesting fixes that work for ONE file but break the others. The student keeps re-pasting the latest version of each file. The AI keeps losing context. The conversation has become incoherent.
Student's Attempted Fixes
- "I tried explaining the whole architecture again at prompt 20."
- "I tried summarizing the previous prompts in a single block at prompt 32."
- "I tried starting a fresh chat. Same problem 15 prompts in."
- "I switched from GPT-5 chat to the reasoning model in genai.mil and it kept losing the file context between prompts."
Diagnostic Questions (the room asks)
- What tool is the student using?
- What does that tool ingest? What does it NOT ingest?
- Where does Ask Sage differ from genai.mil for multi-file work?
- When does multi-file context outweigh prompt-iteration speed?
- Has the student tried Ask Sage's file ingestion for this refactor?
- What is the prompt that gets Ask Sage to "see" all three files at once?
Answer Key (do not open until the room has diagnosed)
Root Cause
The student is using the wrong tool. genai.mil chat is excellent for single-shot prompts and short conversational iteration, but it does not have persistent multi-file context. Every paste is a fresh re-read of every file. By prompt 30 the chat context is too large and the model is dropping earlier content. The student keeps trying harder at a tool that cannot do the job. Ask Sage ingests DOCX, PDF, JSON, CSV, and code directly, and with a reasoning model it can hold all three files in working context across many prompts.
Solution Approach
Stop the genai.mil session. Open Ask Sage. Upload all three files (dashboard.html, sample-data.json, the stylesheet). Select a reasoning-capable model. Issue one focused prompt that names the desired change set across all three files. Ask for the change set first, then a file-by-file walk-through.
Here are three files for a static-HTML readiness dashboard.
I need to add a pivot view that groups by company and shows
readiness trend over the last 4 weeks. Propose a single
change set across all three files, then walk me through it
file by file.
Frontier Classification
Meta-frontier. Knowing when to switch tools is itself a 201-level skill. The frontier here isn't AI capability, it is AI-tool-fit. This is one of the highest-value lessons in Course 4.5 because it generalizes. Any time you find yourself iterating with the AI more than about 10 times on the same problem, the question to ask is "am I using the wrong tool for this kind of problem?"
Final Synthesis
The Three Patterns That Recur in Static-Stack Debugging
Look across the five scenarios and the same three patterns surface again and again. Name them out loud at the end of every clinic so the room hears the pattern, not just the bug.
- State mismatch. The code in front of you is not the only state in play. Scenarios 2, 3, and 5 are all state mismatches: stored data is on an old schema, a date value is in a different timezone than its comparison, the AI's mental model of your files is several prompts stale. When a fix works "sometimes" or works in one environment and not another, suspect state mismatch first.
- Wrong primitive. The student picked the wrong tool for the job at the code level. Scenarios 1 and 4 are wrong-primitive bugs: a click handler when the form-submit handler was the right anchor, per-element handlers when delegation was the right architecture. Wrong-primitive bugs almost always come with a tell, which is that you find yourself patching the same symptom in multiple places.
- Wrong tool. Scenario 5 explicitly. The AI tool you are using cannot do the thing you are asking it to do. No amount of better prompting closes that gap. The fix is to switch tools, not to keep iterating.
The Two-Step Diagnostic Instinct
When a student brings a broken tool to the group, the instructor's job is to make the diagnostic instinct visible. Two questions, in order:
- Is this Context, Frontier, or Meta-frontier? Context bugs get fixed by giving the AI more of the picture. Frontier bugs require you to know the pattern and prompt for it. Meta-frontier bugs require you to change tools.
- Am I using the right AI for this kind of problem? If the answer is no, stop iterating and switch. The tool-selection question is the one most students skip, which is why scenario 5 is the highest-leverage scenario in the set.
Document the Pattern
Marines should add a row to their Frontier Map for each Frontier-classified or Meta-frontier-classified bug they diagnose in the room. Context-only bugs go in the unit's tribal-knowledge doc, not the Frontier Map, because Context fixes are situational and do not generalize. The Frontier Map is the institutional memory. It is the artifact that survives the Marine rotating out of the section. Without it, the next NCO rediscovers the cross-tab schema bug, the timezone bug, and the event-delegation bug from scratch.
Instructor Note
If the room runs the full five scenarios and no Frontier Map rows get written, the clinic did not work. Names of bugs are not the deliverable. Documented patterns the next Marine can prompt with are the deliverable. End the module by reading the room's new Frontier Map rows aloud. If there are fewer than three, run one more scenario.
When to Use in Training
Three Use Modes
- Course 4.5 Module 3 with no student-brought tools. Pick 4 or 5 of these in order. Run the full clinic protocol on each. Plan 35 to 40 minutes. The classroom always works because the scenarios are pre-baked.
- Course 4.5 Module 3 with student-brought tools. Use 2 or 3 of these as warm-up to teach the protocol with low-stakes bugs, then dispatch the rest of Module 3 to the student-brought problems. Student-brought tools are higher-signal, but the warm-up gets the diagnostic instinct calibrated first.
- Informal team debrief on a Tuesday. An NCO picks one scenario, runs the 7-minute clinic protocol, debriefs in 5 more minutes. 12-minute total commitment. No standing up the course apparatus, no formal session block. Just the URL and a whiteboard.
Sibling resources you may want at the same time: the Capability Gap Map for Course 3.5, the Static Hosting Cheat Sheet for the prototype-layer deployment question, and the Course 4.5 page for the full module breakdown.