Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,15 @@ Build a modern AI-powered frontend application.
- Use reusable components.
- Follow consistent naming conventions.
- Add comments where necessary.

## Project Rules

1. All settings-form validation must be handled through the dedicated validation module instead of duplicating validation logic across UI files.

2. Every form input must have a clear accessible label, and validation errors must be presented in a way that users can understand without relying only on colour.

3. Validation must cover required fields, invalid input, and valid input. Changes to validation behaviour must be covered by tests in the `tests/` directory.

4. Keep presentation, UI behaviour, and validation logic separated into their respective files rather than putting all implementation inside `index.html`.

5. Before considering a settings-form change complete, run the validation tests and verify the expected behaviour manually in the browser.
1 change: 0 additions & 1 deletion Prompt Engineering

This file was deleted.

32 changes: 32 additions & 0 deletions Round 2 - Detailed prompt/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Settings</title>
<link rel="stylesheet" href="settings.css" />
</head>
<body>
<main class="settings-page">
<section class="settings-card" aria-labelledby="settings-title">
<h1 id="settings-title">Settings</h1>
<p class="settings-description">Update your profile details.</p>
<form id="settings-form" novalidate>
<div class="form-field">
<label for="name">Name</label>
<input id="name" name="name" type="text" placeholder="Enter your full name" autocomplete="name" aria-describedby="name-error" />
<p id="name-error" class="field-error" role="alert"></p>
</div>
<div class="form-field">
<label for="email">Email</label>
<input id="email" name="email" type="email" placeholder="you@example.com" autocomplete="email" aria-describedby="email-error" />
<p id="email-error" class="field-error" role="alert"></p>
</div>
<button type="submit">Save</button>
<p id="form-success" class="success-message" role="status" aria-live="polite"></p>
</form>
</section>
</main>
<script type="module" src="settings.js"></script>
</body>
</html>
6 changes: 6 additions & 0 deletions Round 2 - Detailed prompt/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "settings-form",
"private": true,
"type": "module",
"scripts": { "test": "node --test" }
}
16 changes: 16 additions & 0 deletions Round 2 - Detailed prompt/settings.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
:root { font-family: Arial, sans-serif; color: #1f2937; background: #f3f4f6; }
body { margin: 0; }
.settings-page { display: grid; min-height: 100vh; place-items: center; padding: 1.5rem; }
.settings-card { width: min(100%, 28rem); padding: 2rem; border-radius: .75rem; background: #fff; box-shadow: 0 8px 24px rgb(31 41 55 / 12%); }
h1 { margin: 0; }
.settings-description { margin: .5rem 0 1.5rem; color: #4b5563; }
.form-field { margin-bottom: 1.25rem; }
label { display: block; margin-bottom: .5rem; font-weight: 600; }
input { box-sizing: border-box; width: 100%; padding: .7rem .75rem; border: 1px solid #9ca3af; border-radius: .375rem; font: inherit; }
input:focus { outline: 3px solid #bfdbfe; border-color: #2563eb; }
input[aria-invalid="true"] { border-color: #b91c1c; }
.field-error { min-height: 1.25rem; margin: .35rem 0 0; color: #b91c1c; font-size: .875rem; }
button { padding: .7rem 1.1rem; border: 0; border-radius: .375rem; background: #2563eb; color: #fff; cursor: pointer; font: inherit; font-weight: 600; }
button:hover { background: #1d4ed8; }
button:focus-visible { outline: 3px solid #93c5fd; outline-offset: 2px; }
.success-message { min-height: 1.25rem; margin: 1rem 0 0; color: #047857; }
22 changes: 22 additions & 0 deletions Round 2 - Detailed prompt/settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { getSettingsSubmitResult } from "./validation.mjs";

const form = document.querySelector("#settings-form");
const nameInput = document.querySelector("#name");
const emailInput = document.querySelector("#email");
const nameError = document.querySelector("#name-error");
const emailError = document.querySelector("#email-error");
const successMessage = document.querySelector("#form-success");

function showError(input, errorElement, message) {
input.setAttribute("aria-invalid", String(Boolean(message)));
errorElement.textContent = message || "";
}

form.addEventListener("submit", (event) => {
event.preventDefault();
const result = getSettingsSubmitResult({ name: nameInput.value, email: emailInput.value });
showError(nameInput, nameError, result.errors.name);
showError(emailInput, emailError, result.errors.email);
successMessage.textContent = result.submitted ? "Settings saved successfully." : "";
if (!result.submitted) (result.errors.name ? nameInput : emailInput).focus();
});
9 changes: 9 additions & 0 deletions Round 2 - Detailed prompt/tests/validation.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from "node:test";
import assert from "node:assert/strict";
import { getSettingsSubmitResult, validateSettings } from "../validation.mjs";

test("requires a name", () => assert.deepEqual(validateSettings({ name: " ", email: "alex@example.com" }), { name: "Name is required." }));
test("requires an email", () => assert.deepEqual(validateSettings({ name: "Alex", email: "" }), { email: "Email is required." }));
test("rejects an invalid email", () => assert.deepEqual(validateSettings({ name: "Alex", email: "invalid" }), { email: "Enter a valid email address." }));
test("blocks invalid submission", () => assert.equal(getSettingsSubmitResult({ name: "", email: "invalid" }).submitted, false));
test("accepts valid submission", () => assert.deepEqual(getSettingsSubmitResult({ name: "Alex Johnson", email: "alex@example.com" }), { errors: {}, submitted: true }));
17 changes: 17 additions & 0 deletions Round 2 - Detailed prompt/validation.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

export function validateSettings({ name, email }) {
const errors = {};
if (!name.trim()) errors.name = "Name is required.";
if (!email.trim()) {
errors.email = "Email is required.";
} else if (!EMAIL_PATTERN.test(email.trim())) {
errors.email = "Enter a valid email address.";
}
return errors;
}

export function getSettingsSubmitResult(values) {
const errors = validateSettings(values);
return { errors, submitted: Object.keys(errors).length === 0 };
}
29 changes: 29 additions & 0 deletions WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Workflow Comparison: Round 1 vs Round 2

## Overview

The two branches implement the same settings-form feature using different prompting approaches. Round 1 was produced from a single vague prompt and accepted with minimal iteration. Round 2 used a more precise prompt with file references, constraints, expected behaviour, and a verification step. The branch diff shows a clear structural difference between the approaches.

## Correctness

Round 1 keeps the implementation largely inside `index.html`, while Round 2 separates responsibilities into `settings.css`, `settings.js`, and `validation.mjs`. Round 2 also adds `package.json` and `tests/validation.test.mjs`, making the validation behaviour easier to verify independently. The diff contains 30 insertions and 224 deletions, showing that Round 2 is not simply a copy of Round 1 but a substantially reorganised implementation.

## Accessibility

The settings form should provide clear labels for every input, readable validation messages, and usable keyboard interaction. These requirements are easier to review when the structure and behaviour are separated into dedicated files. A precise prompt also makes accessibility expectations explicit instead of leaving them to the AI's interpretation.

## Edge Cases

Validation should handle empty required fields, invalid input, and valid input correctly. Round 2's dedicated `validation.mjs` and `validation.test.mjs` make these cases more explicit and testable. This reduces the chance that a visually correct form fails for unexpected input.

## Review Effort

Round 1 is initially simpler because it is contained in one HTML file, but reviewing correctness and validation behaviour requires inspecting the whole file. Round 2 introduces more files, but the separation makes individual responsibilities easier to locate and test. The automated validation test also reduces manual review effort.

## AI Mistake Caught

One important issue discovered during the workflow was that the AI-generated Round 2 setup did not initially provide a clean, directly comparable structure with Round 1. The branch diff exposed that Round 1's `index.html` was removed while Round 2 used a different file structure. This demonstrated why checking the actual Git diff and running the implementation is necessary instead of assuming the generated output is correct.

## Conclusion

The Round 2 workflow required more upfront specification but produced a more structured and verifiable implementation. The main lesson is that precise prompts, explicit constraints, and a test-and-verify step reduce ambiguity and make AI-generated code easier to review.