diff --git a/CLAUDE.md b/CLAUDE.md index 0eb40c5..cbd5845 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. \ No newline at end of file diff --git a/Prompt Engineering b/Prompt Engineering deleted file mode 100644 index 8b13789..0000000 --- a/Prompt Engineering +++ /dev/null @@ -1 +0,0 @@ - diff --git a/Round 2 - Detailed prompt/index.html b/Round 2 - Detailed prompt/index.html new file mode 100644 index 0000000..a0f3bba --- /dev/null +++ b/Round 2 - Detailed prompt/index.html @@ -0,0 +1,32 @@ + + + + + + Settings + + + +
+
+

Settings

+

Update your profile details.

+
+
+ + + +
+
+ + + +
+ +

+
+
+
+ + + diff --git a/Round 2 - Detailed prompt/package.json b/Round 2 - Detailed prompt/package.json new file mode 100644 index 0000000..d8d7ad0 --- /dev/null +++ b/Round 2 - Detailed prompt/package.json @@ -0,0 +1,6 @@ +{ + "name": "settings-form", + "private": true, + "type": "module", + "scripts": { "test": "node --test" } +} diff --git a/Round 2 - Detailed prompt/settings.css b/Round 2 - Detailed prompt/settings.css new file mode 100644 index 0000000..ac4e2b6 --- /dev/null +++ b/Round 2 - Detailed prompt/settings.css @@ -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; } diff --git a/Round 2 - Detailed prompt/settings.js b/Round 2 - Detailed prompt/settings.js new file mode 100644 index 0000000..42e53b9 --- /dev/null +++ b/Round 2 - Detailed prompt/settings.js @@ -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(); +}); diff --git a/Round 2 - Detailed prompt/tests/validation.test.mjs b/Round 2 - Detailed prompt/tests/validation.test.mjs new file mode 100644 index 0000000..22e5e14 --- /dev/null +++ b/Round 2 - Detailed prompt/tests/validation.test.mjs @@ -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 })); diff --git a/Round 2 - Detailed prompt/validation.mjs b/Round 2 - Detailed prompt/validation.mjs new file mode 100644 index 0000000..f2d70bd --- /dev/null +++ b/Round 2 - Detailed prompt/validation.mjs @@ -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 }; +} diff --git a/WORKFLOW.md b/WORKFLOW.md new file mode 100644 index 0000000..495338e --- /dev/null +++ b/WORKFLOW.md @@ -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. \ No newline at end of file