Skip to content

Configure and explain web analytics

You will configure Google Analytics 4 (GA4) for one controlled local test. The application will send one custom task_created event to a teacher-provided training property only after an explicit test-mode action. You will verify the event path and explain what its event count can and cannot show.

Analytics turns selected interactions into recorded events and reports. A report is useful only when the team can explain the question, event trigger, parameters, collection boundary, exclusions, and limits. More collection does not make a better measurement plan.

What you will practice

  • Trace data from an application action through a tag, web data stream, property, DebugView, and report.
  • Define one measurement question before adding collection code.
  • Distinguish an event, event parameter, dimension, and metric.
  • Configure a GA4 training tag that is inactive during normal application use.
  • Send one custom event exactly once after a successful state change.
  • Exclude task text, identifiers, personal data, and invalid attempts from the custom event.
  • Verify source, Network, DebugView, and regression evidence without treating synthetic data as real visitor behavior.
  • Explain the decisions that an event count supports and the questions it cannot answer.
  • New: Web analytics, measurement question, measurement plan, GA4 account, property, web data stream, Measurement ID, Google tag, custom event, event parameter, dimension, metric, DebugView, Realtime, external data transfer, synthetic analytics data, and controlled test mode.
  • Reused: The task tracker, successful submit path, functions, state mutation, event listeners, derived values, DevTools Network and Console, evidence statuses, regression testing, private GitHub repository, quality board, and publication boundary.

Starting point

Before you start

  • The final technical SEO commit, technical-seo-report.md, interactive-test-report.md, and quality-plan.md.
  • The tested task application with one successful handleTaskSubmit path and a documented reset procedure.
  • The private GitHub repository and the final-regression project item still in Ready.
  • A teacher-provided GA4 training property, web data stream, Measurement ID, and property access for DebugView.
  • Written approval to send synthetic lesson events from localhost to that training property.
  • VS Code, a local HTTP server, a current browser with DevTools, Git, and access to the official GA4 documentation.
Current state
The application has functional, SEO, and source evidence. It does not yet have an approved measurement question, external-data boundary, analytics configuration, event test, interpretation, or final regression after analytics changes.
First action
Create analytics-measurement-plan.md and record the teacher-provided property name, stream name, Measurement ID, access level, approved test window, and data-removal or expiry owner.
First checkpoint
The plan names one answerable question, one custom event, its exact successful trigger, excluded data, tool owner, test boundary, evidence statuses, and the decision that the result could support.
Help trigger
Use the recovery note or ask for help if no approved training property exists, the property or stream owner is unclear, the tag loads during normal use, an event contains task text or personal data, one action sends several custom events, DebugView shows the wrong property, or analytics code changes application behavior.

You have completed the lesson when:

  • analytics-measurement-plan.md identifies the tested commit, application question, tool owner, training property, web data stream, Measurement ID, test window, synthetic-data boundary, and cleanup or expiry owner;
  • the teacher confirms that the property is for training, enhanced measurement is disabled for this exercise, and real visitors must not use the test route;
  • the plan defines one task_created custom event and no second required custom event;
  • the event fires once after a valid task record enters state and does not fire for empty or whitespace-only input;
  • the event sends only input_length_band and test_context as custom parameters;
  • no task text, task ID, name, email address, user ID, school record, free-form error, URL query value, or other personal or sensitive value becomes a custom parameter;
  • the normal local URL does not load the Google tag or send analytics requests;
  • the test URL works only on localhost or 127.0.0.1, requires analytics_test=1, and uses the exact teacher-provided Measurement ID;
  • the GA4 configuration disables automatic page-view sending, Google signals, and ad-personalization signals for the exercise;
  • the custom event uses debug mode and an approved synthetic task value;
  • DevTools shows one expected tag load and one task_created collection request after the successful test action;
  • DebugView shows the matching event name and allowed parameter values in the correct training property;
  • the plan explains the complete data path, the event_name dimension, the Event count metric, and the difference between DebugView and a processed report;
  • the interpretation makes no claim about real visitors, unique people, intent, satisfaction, accessibility, causation, or production performance;
  • the normal application URL passes the previous scripted paths, keyboard, focus, state, narrow, zoom, source, and Console regression checks;
  • the final-regression project item links to the analytics and regression evidence and moves to Done only after those checks pass;
  • the final tested commit is recorded in the reports; and
  • the plan and verified source changes are committed and pushed to the private repository.

Confirm the training boundary before configuration

Section titled “Confirm the training boundary before configuration”

Do not create a personal analytics property or connect a production school property to complete the lesson. Ask the teacher for this preflight record:

Required fact Evidence to record
Property owner Named school or training owner
Property purpose Level 2 synthetic analytics practice
Web data stream Exact stream name and approved source context
Measurement ID Exact G- identifier from that stream
Learner access Access that can open the correct property and DebugView
Automatic collection Enhanced measurement disabled for this controlled exercise
Allowed data Synthetic event and the two named custom parameters only
Test window Start and end condition for lesson events
Cleanup or expiry Named owner and action after the exercise

If any fact is missing, set configuration evidence to Blocked, name the missing fact and owner, and give the plan to the teacher. You can write the measurement plan and study the GA4 demo account while you wait, but you cannot claim that you configured and verified the training tool.

Use one stable term for each part of the data path:

Part Role in this lesson
Application action A valid form submission adds a synthetic task record to state
Google tag Browser code queues and sends the selected event to Google Analytics
Measurement ID Identifies the teacher-provided web data stream destination
Web data stream Receives web measurement data for one GA4 property
Property Holds the configured measurement data and reports
Event Names an observed action, such as task_created
Event parameter Adds bounded context, such as input_length_band: "short"
Dimension Describes data by a category, such as event name
Metric Counts or measures data, such as Event count
DebugView Shows debug-enabled events and parameters for implementation checks
Realtime or standard report Aggregates received data for analysis, subject to processing and report rules

The required data path is:

approved local test action
-> successful state change
-> task_created event
-> Google tag
-> teacher web data stream
-> training property
-> DebugView evidence
-> bounded interpretation

An event parameter collects context. A dimension or metric makes collected data available for analysis in a report. DebugView can show a custom parameter during implementation even when no custom dimension has been created for standard reporting.

Checkpoint: The tool and data boundary are approved

What now works
The plan names the approved property, stream, Measurement ID, access, test window, cleanup owner, one measurement question, one custom event, two allowed custom parameters, exclusions, and expected evidence.
Files changed
analytics-measurement-plan.md
What remains
Add the controlled tag configuration and connect the event to the confirmed successful state change.
Next action
Keep the normal application URL open, clear the Network log, and prove that no Google Analytics request occurs before you edit app.js.
If it does not work
If property details or approval are missing, record Blocked and send the exact preflight table to the teacher. Do not create or reuse an unapproved property.

Add this structure to analytics-measurement-plan.md:

analytics-measurement-plan.md — starting structure
# Analytics measurement plan: Project task tracker
## Tested build and environment
- Starting commit:
- Local URL:
- Browser and version:
- Operating system:
- Test date:
## Ownership and external-data boundary
- Tool: Google Analytics 4
- Property owner:
- Training property:
- Web data stream:
- Measurement ID:
- Approved test window:
- Enhanced measurement confirmed off by:
- Cleanup or expiry owner:
- Real visitor collection: Not approved
## Measurement question
- Question: How often does an approved synthetic test session create a valid task?
- Decision supported: Confirm whether the selected successful action reaches the training property once.
- Decision not supported: Any product, visitor, or production decision.
## Event specification
Add the required event table.
## Source, Network, and DebugView evidence
Add the required test matrix.
## Interpretation and limits
- Observed event count:
- What the evidence shows:
- What the evidence does not show:
- Configuration issues:
- Next action:
## Final regression and handoff
- Final tested commit:
- Interactive regression result:
- Technical SEO regression result:
- Normal-mode no-collection result:
- Final-regression project item:
- Open limits:

Use these statuses: Not run, Pass, Issue, Blocked, and Accepted limitation.

Add this table under Event specification:

Field Required value
Measurement question How often does an approved synthetic test session create a valid task?
Event name task_created
Trigger One valid submit has added one task object to state.tasks
Non-trigger Empty or whitespace-only input; page load; field focus; button click before validation
input_length_band short, medium, or long, derived from trimmed text length
test_context level_2_lesson
Excluded values Complete task text, task ID, user ID, names, contact details, free-form strings
Expected event count Zero for invalid input; one for one successful synthetic submit
Intended report dimension Event name
Intended report metric Event count

task_created is a custom event because no required GA4 recommended event describes creating an in-memory practice task. The name starts with a letter and uses lowercase letters and an underscore. Keep the same spelling and case in source, Network evidence, DebugView, and the plan.

The custom event must not include taskName. A task field accepts free-form text, so a tester could enter a real name, email address, school detail, or another personal value. Derive a bounded length category locally and discard the text from the analytics path.

GA4 can add standard web context to collected events, including page and device information. This is why the exercise uses an approved training property, a synthetic tester, a local-only switch, and no real visitor traffic. The two custom parameters are not the complete provider data inventory.

Do not send:

  • the task text or a substring of it;
  • the task record ID;
  • the validation error text;
  • an email address, name, phone number, account identifier, or school record;
  • a user ID, device fingerprint, or persistent project-specific identifier;
  • the full query string as a custom parameter; or
  • a value because it might be useful later.

Before adding analytics code:

  1. Start the application through its documented local HTTP method.
  2. Open the normal URL without analytics_test=1.
  3. Clear the Network and Console logs.
  4. Submit one synthetic valid task and one invalid value.
  5. Confirm the application behaves as documented.
  6. Filter Network for googletagmanager, google-analytics, and analytics.google.
  7. Record that no analytics request exists in the starting build.

After implementation, you will repeat the same normal-mode check. The unchanged result proves that the training integration remains inactive during ordinary use.

Add a controlled GA4 training configuration

Section titled “Add a controlled GA4 training configuration”

Add this block near the start of app.js, after the initial state and before the event handlers. Replace the placeholder only with the exact teacher-provided training Measurement ID.

app.js — local-only training configuration
const TRAINING_MEASUREMENT_ID = "G-REPLACE_WITH_TEACHER_ID";
const LOCAL_ANALYTICS_HOSTS = new Set(["localhost", "127.0.0.1"]);
const analyticsTestRequested = new URLSearchParams(
window.location.search,
).get("analytics_test") === "1";
const hasTrainingMeasurementId = /^G-[A-Z0-9]+$/.test(
TRAINING_MEASUREMENT_ID,
) && !TRAINING_MEASUREMENT_ID.includes("REPLACE");
const analyticsTestEnabled = (
LOCAL_ANALYTICS_HOSTS.has(window.location.hostname)
&& analyticsTestRequested
&& hasTrainingMeasurementId
);
function gtag() {
window.dataLayer.push(arguments);
}
function startAnalyticsTest() {
if (!analyticsTestEnabled) {
return;
}
window.dataLayer = window.dataLayer || [];
window.gtag = gtag;
gtag("js", new Date());
gtag("config", TRAINING_MEASUREMENT_ID, {
allow_ad_personalization_signals: false,
allow_google_signals: false,
debug_mode: true,
page_location: `${window.location.origin}${window.location.pathname}`,
send_page_view: false,
});
const analyticsScript = document.createElement("script");
analyticsScript.async = true;
analyticsScript.src = (
"https://www.googletagmanager.com/gtag/js?id="
+ encodeURIComponent(TRAINING_MEASUREMENT_ID)
);
document.head.append(analyticsScript);
}

This configuration has three independent gates:

  • the host must be localhost or 127.0.0.1;
  • the URL must contain analytics_test=1; and
  • the Measurement ID must have a non-placeholder G- format.

The function returns before it creates dataLayer, configures GA4, or loads the external script when any gate is false. send_page_view: false prevents the configuration command from sending its default page-view event. The teacher also disables enhanced measurement for this training stream so it does not add page-change collection outside this source setting.

The two advertising settings reduce the exercise scope. They do not replace the property-owner review or a production privacy implementation.

Call the setup function once after its declaration:

app.js — initialize the inactive-by-default integration
startAnalyticsTest();

Do not call it from a render function or submit handler. Repeated setup calls can make the data path harder to reason about and test.

Run these source and browser checks:

ID Condition Expected result
ANA-01 Normal localhost URL No tag script, data layer, or analytics request
ANA-02 Localhost with analytics_test=1 and placeholder ID No tag load; configuration remains incomplete
ANA-03 Non-local host with query and training ID No tag load
ANA-04 Localhost with query and approved training ID One Google tag script request; application still works

Use the normal URL for ANA-01. For ANA-02 and ANA-04, change only the condition named in the row. Do not publish the page to create ANA-03; inspect the guard and, when useful, test the expression in the Console with a synthetic host string.

Checkpoint: Normal use does not start analytics

What now works
The normal URL makes no analytics request, the source requires all three gates, the approved test URL loads one training tag, and the task tracker still passes its starting-state and primary-action checks.
Files changed
app.js, analytics-measurement-plan.md
What remains
Derive an allowed parameter and send task_created exactly once after the successful state change.
Next action
Add recordTaskCreated after startAnalyticsTest, then connect one call to the valid submit path.
If it does not work
If the tag loads in normal mode, close the test route, restore the last passing commit, and trace each gate before testing any event.

Add this function after startAnalyticsTest:

app.js — send one bounded custom event
function recordTaskCreated(taskName) {
if (!analyticsTestEnabled) {
return;
}
let inputLengthBand = "long";
if (taskName.length <= 24) {
inputLengthBand = "short";
} else if (taskName.length <= 60) {
inputLengthBand = "medium";
}
gtag("event", "task_created", {
input_length_band: inputLengthBand,
test_context: "level_2_lesson",
});
}

The function reads the already trimmed text only to choose one of three labels. It does not add the text to dataLayer or the event parameters.

Trimmed length Parameter value
1–24 characters short
25–60 characters medium
61 or more characters long

This is a practice classification, not a claim that one band is better. The categories make the parameter bounded and testable.

Connect the event to the successful state change

Section titled “Connect the event to the successful state change”

Find handleTaskSubmit. Keep the invalid-input return. Add one call only after the task object enters state:

app.js — instrument the successful path
state.tasks.push({
id: state.nextTaskId,
name: taskName,
completed: false,
});
state.nextTaskId += 1;
recordTaskCreated(taskName);
taskNameInput.removeAttribute("aria-invalid");
taskError.textContent = "";
taskForm.reset();
renderTasks();
taskNameInput.focus();

Do not attach analytics to the submit button’s click event. A form can be submitted with the keyboard, and a click can occur before validation succeeds. The established submit handler already knows whether state changed.

The analytics call follows the confirmed mutation. It does not decide whether the task is valid and does not change the state or DOM. When analytics is disabled or its external script is blocked, the core task cycle still runs.

invalid submit
-> validation message
-> return
-> no state change
-> no task_created event
valid submit
-> trim and validate
-> add one task object
-> derive one length band
-> queue one task_created event in test mode
-> render the changed state

Search the complete source for task_created and recordTaskCreated(. There must be one event definition and one production call site. Documentation strings and the function declaration can create additional text matches; identify the one executable call inside the successful handler.

Add this matrix under Source, Network, and DebugView evidence:

ID Check Expected result Actual result Status and evidence
EVT-01 Inspect successful handler One call occurs after one state mutation Not run Not run
EVT-02 Inspect event parameters Only length band and lesson context are custom values Not run Not run
EVT-03 Use normal URL and submit valid data Application changes; no analytics request Not run Not run
EVT-04 Use test URL and submit empty data Validation appears; no task_created request Not run Not run
EVT-05 Submit one short synthetic task One new task and one task_created request Not run Not run
EVT-06 Inspect collection request Correct Measurement ID, event name, and allowed values; no task text Not run Not run
EVT-07 Inspect correct DebugView Matching event and parameters appear in training property Not run Not run
EVT-08 Reload normal URL Tag is absent; required application reset still works Not run Not run

Use an approved synthetic task such as Run analytics test. Do not enter a real person’s task, name, email address, or school detail.

  1. Open the correct property before generating data.

    In GA4, use the property selector and compare the property and stream names with the plan. Open Admin → DebugView. Interface labels can change; use the current official DebugView guide when the route differs.

  2. Open the controlled local route.

    Use the application’s reported localhost URL and add ?analytics_test=1. Do not add the parameter to a public or preview URL.

  3. Clear the browser evidence.

    Open DevTools. Clear Network and Console. Enable Network log preservation only when a reload would otherwise remove evidence. Filter for gtag/js and g/collect.

  4. Prove the non-trigger.

    Submit an empty value. Confirm the application validation, unchanged task count, and absence of a new task_created collection request.

  5. Prove the one event.

    Submit Run analytics test once. Confirm one new task, one new task_created request, no JavaScript error, and no duplicate custom-event request.

  6. Inspect the request.

    Confirm the destination uses the teacher’s Measurement ID. Find the event name and the two allowed parameter values. Search the request details for the complete synthetic task text and confirm it is absent.

  7. Inspect DebugView.

    Select the current debug device when required. Open task_created and confirm input_length_band: short and test_context: level_2_lesson. Record the observed time and property. Other provider-generated events can exist; count the custom event by its exact name.

  8. Return to normal mode.

    Remove the query string, reload, clear Network, and repeat one valid submit. Confirm the application works and no new analytics request occurs.

DebugView is implementation evidence. A collection request proves that the browser attempted to send data. DebugView proves that the selected property received and classified the debug event. Neither proves that a future production configuration is complete or compliant.

If the request or DebugView evidence is missing

Section titled “If the request or DebugView evidence is missing”

Change one condition at a time:

Symptom Check first
No tag script request Local host, query parameter, placeholder guard, and setup call
Tag loads but event request is absent Successful handler call site, Console error, and content blocker
Several custom-event requests Duplicate handler, duplicate call site, or repeated setup
Wrong property receives the event Measurement ID in source and selected GA4 property
Request exists but DebugView is empty debug_mode, correct debug device, access, property, and processing delay
Task text appears in the request Event object or another analytics call that passes free-form input
Application fails when the tag is blocked Analytics code owns a core state, validation, or render decision

Preserve the first failing condition and form a specific cause hypothesis before editing. Do not disable a privacy control or browser security setting on a managed device without teacher approval.

Checkpoint: One synthetic action reaches the training property

What now works
Invalid input sends no custom event; one approved valid submit produces one task, one task_created request, and one matching DebugView event with only the two allowed custom parameters. Normal mode remains inactive.
Files changed
app.js, analytics-measurement-plan.md, interactive-test-report.md
What remains
Interpret the count, run the full final regression, close the planned quality item, and record the final commit.
Next action
Write What the evidence shows and What the evidence does not show before reading any standard report.
If it does not work
If evidence disagrees between source, Network, and DebugView, record each layer separately. Resume at the earliest layer that does not match its expected result.

For this event:

  • Event name is a dimension: it groups records under task_created.
  • Event count is a metric: it totals how many matching events GA4 processed for the selected scope and time.
  • input_length_band is an event parameter: it adds one bounded category to each event.
  • DebugView shows implementation details for debug-enabled events.
  • Realtime and standard reports aggregate data and can use different processing windows and report rules.

One approved submit should produce one custom event. Therefore, one matching DebugView event supports this limited statement:

In the recorded synthetic test, the configured successful path sent one task_created event with the expected bounded parameters to the teacher’s training property.

It does not support these statements:

  • one event equals one unique person;
  • real visitors can or will complete the task;
  • a missing event proves a visitor abandoned the form;
  • the application is useful, understandable, accessible, or satisfying;
  • the event caused a later action;
  • all browsers, privacy settings, blockers, or network conditions behave the same way;
  • a training result predicts production traffic; or
  • collecting more fields would make the conclusion more accurate.

The required measurement supports one implementation decision: whether the selected successful action is connected to the intended training stream exactly once.

It does not yet support a product decision. A product question would need an approved public context, a defined visitor group, a suitable comparison or target, adequate data quality, a privacy-approved collection plan, and evidence beyond one synthetic session.

Add a short explanation to the plan that answers:

  1. What does the Google tag do in this application?
  2. What does the Measurement ID select?
  3. When does task_created fire, and when does it not fire?
  4. Which custom values leave the application?
  5. Why is Event count different from a count of unique people?
  6. What is DebugView evidence useful for?
  7. Which decision can the result support?
  8. Which important questions remain unanswered?

Use the exact source and recorded evidence. Do not copy a general marketing description of analytics.

Move QA: Run final regression and review evidence from Ready to In progress. Keep every other item out of In progress.

Use the normal URL without analytics_test=1 for the final product regression. Follow the reset procedure and rerun:

  1. the fifteen scripted cases from interactive-test-report.md;
  2. empty, whitespace-only, repeated, long, and HTML-looking input paths;
  3. keyboard-only form and checkbox paths with visible and restored focus;
  4. state, list, summary, and status-message agreement;
  5. the primary interaction near 320 CSS pixels and at 200% zoom;
  6. saved-source and rendered-DOM checks from the technical SEO report;
  7. HTML, CSS, startup, and interaction Console checks; and
  8. the normal-mode Network check for no Google tag or analytics collection request.

Record each actual result against the final source commit. Do not reuse the earlier Pass values without executing the conditions after the analytics change.

If a regression fails:

  • keep the final-regression item In progress;
  • reproduce the same condition twice;
  • record the earliest incorrect state or request;
  • test one cause hypothesis;
  • repair the smallest confirmed cause;
  • rerun the failed condition, connected primary path, and normal-mode analytics guard; and
  • update the evidence before changing the board status.

Move the item to Done only when the final source passes. Link analytics-measurement-plan.md, interactive-test-report.md, technical-seo-report.md, and the final commit in the private issue or project item. The board should then have no open required Level 2 quality item.

End analytics-measurement-plan.md with a result in this form:

analytics-measurement-plan.md — final handoff
## Final result
- Analytics configuration: Pass for approved local training mode
- Normal-mode collection: No Google tag or analytics request observed
- Custom event: task_created
- Allowed custom parameters: input_length_band and test_context
- Synthetic test result: One valid action produced one matching event
- Invalid test result: No custom event
- DebugView property and time:
- Final regression: Pass against final tested commit
- Production analytics: Not approved and not configured
- Cleanup or expiry owner:
- Final tested commit:
- Open limits: Synthetic single-session evidence; no real visitor conclusion

Remove the query parameter after the test. Do not leave a browser tab generating repeated events. Follow the teacher’s cleanup or expiry instruction; do not delete property data or change property settings unless the owner authorized that action.

Stop the test. Restore all three gates and confirm that the early return occurs before dataLayer and the external script are created.

Move the analytics call out of the button click path. Keep one call after successful validation and state mutation in the form submit handler.

Search for duplicate call sites and duplicate submit listeners. Confirm setup and listener registration occur once, outside render functions.

Remove the free-form value from every analytics call. Keep only the locally derived length band. Retest with synthetic text and inspect the complete request again.

Stop generating events. Compare the source Measurement ID with the plan and the selected property. Resume only after the teacher confirms the correct training destination.

The report shows more events than the one test

Section titled “The report shows more events than the one test”

Filter by the exact event name, test context, time, and debug device when available. Other learners and provider-generated events can share the training property. Do not interpret the property total as one learner’s result.

Record the environment and blocked request as evidence. Use a teacher-approved testing environment if one exists. Do not weaken a managed-device control without approval.

Remove analytics from the state, validation, and render decisions. The application must remain functional when the integration is disabled, slow, or blocked.

Self-check

Complete these checks against the required result.

  1. Confirm that the plan names the tool owner, training property, web data stream, Measurement ID, access, test window, automatic-collection state, and cleanup or expiry owner.
  2. Point to the one measurement question, one event definition, exact successful trigger, non-triggers, and supported implementation decision.
  3. Confirm that the normal application URL does not create dataLayer, load the Google tag, or send an analytics request.
  4. Confirm that all three gates are required: local host, analytics_test=1, and an approved non-placeholder G- identifier.
  5. Inspect the configuration and confirm that page-view sending, Google signals, and ad-personalization signals are disabled for the exercise.
  6. Submit empty and whitespace-only input in test mode and confirm that no task record and no task_created event appear.
  7. Submit Run analytics test once and confirm one new task, one custom collection request, and one matching DebugView event.
  8. Inspect the request and confirm that only input_length_band and test_context are custom parameters and the task text is absent.
  9. Explain event, parameter, dimension, metric, data stream, property, DebugView, and report through this implementation.
  10. State what the event count shows and why it does not count unique people or prove a real visitor outcome.
  11. Rerun the complete application, accessibility, responsive, source, Console, and normal-mode collection checks against the final source.
  12. Confirm that the final-regression item is Done, the reports name the final tested commit, the training route is closed, and the working tree is clean.

Complete the required result before choosing an extension. Extensions do not change the definition of done.

Review the complete diff before staging. Do not include screenshots with property access details, personal data, an API secret, or unrelated files.

Terminal window
git status
git diff
git add app.js analytics-measurement-plan.md interactive-test-report.md technical-seo-report.md quality-plan.md
git diff --staged
git commit -m "Configure controlled analytics event"
git push

Stage only the files that actually changed. Record the final tested commit hash in the reports. If the hash record requires one small follow-up commit, identify which commit contains the tested application and which commit only updates the record.

The lesson is complete when one approved synthetic action reaches the teacher’s training property exactly once, the normal application remains analytics-free, the data limits are explained, the final regression passes, and the quality board is closed.

Continue to Final project: Build, evaluate, and present a web application to apply the Level 2 interaction, CMS, responsive, animation, quality, SEO, and analytics work to the assessed product.

If you stop, leave the application on its normal URL and the repository in a committed state. Add this resume record to the plan: Last passing check; current evidence layer; active issue; property and stream names without credentials; next exact action; and the normal-mode collection result.