# Test notes

> v1 covered `index.html` only (sections below). v2 adds the 5 region pages and the
> property-card template — see **"v2 — region pages and card template"** near the
> end for what changed and what's still open.

## v1 — index.html (hub page)

## What was actually tested (with evidence)

| Test | Method | Result |
|---|---|---|
| HTML structure (H1/H2/H3 counts, canonical, title, meta) | `grep` against the built file | See `seo-checklist.md` §2–3 for exact counts |
| JSON-LD syntax validity | Python `json.loads()` on all 4 `<script type="application/ld+json">` blocks | 0 parse errors |
| FAQPage schema ↔ visible content match | Python script extracting both and diffing after whitespace/entity normalization | 0 mismatches across 7 Q&A pairs |
| No `<img>` / no broken `alt` risk | `grep -c '<img'` | 0 — page uses inline SVG only (see README) |
| No stray `noindex` | `grep -i noindex` | 0 matches |
| Visual render, desktop (~800px pane width) | Local static server (`python -m http.server`) + browser screenshots | Hero, overview, property types, regions, yield table, legal, taxes table all render as designed |
| Visual render, mobile (375×812 emulated) | Same server, resized viewport | Layout reflows to single column, stats stack, hero visual wraps below copy |
| Mobile nav toggle | Clicked hamburger button, re-checked via JS (`aria-expanded`, class, computed `max-height`) | Opens correctly (`aria-expanded="true"`, dropdown renders at full width below header) — a same-tick screenshot taken immediately after the click caught the mid-transition frame and looked empty; a second screenshot taken a moment later showed it open correctly |
| FAQ accordion | Clicked the first `<summary>` | Expands/collapses using the native `<details>` element — zero JavaScript dependency, works even if `main.js` fails to load |
| Contact form demo | Filled name + email, clicked submit | `main.js` intercepts `submit`, prevents the native page reload, and shows: "Thanks — this is a demo build, so no message was actually sent. Connect a form backend before launch." |
| Browser console errors | `read_console_messages` (errors only) after load and after interactions | 0 errors |

## What was NOT tested / cannot be verified from this environment

These are the honest limitations, as required by the brief ("list all remaining
issues and tests that cannot be verified automatically"):

1. **Core Web Vitals / Lighthouse / PageSpeed Insights** — these tools measure a
   publicly reachable URL; the page only exists on a local static server right now.
   Run them after deploying to Cloudflare Pages.
2. **Google Rich Results Test / Schema Markup Validator** — same reason (needs a
   public URL). JSON-LD syntax and FAQ-content match were verified locally instead
   (see above); this is not a substitute for Google's own parser.
3. **Real cross-browser testing** (Safari, Firefox, older Chromium) — only tested in
   the Claude Code browser pane (Chromium-based). CSS used is broad-support (flexbox,
   grid, `<details>`, CSS custom properties) with no bleeding-edge features, so risk
   is low, but it has not been run on an actual Safari/Firefox instance.
4. **Spelling/grammar pass by a human native-English copy editor** — content was
   written and internally checked, not run through a third-party proofing tool.
5. **Legal/tax/visa fact-check by a licensed Thai lawyer or accountant** — every
   figure is sourced to a real public document (see `seo-checklist.md` §8 and the
   page's own "Sources & Disclaimers" section), but sourcing is not the same as
   professional verification. **Do not publish this page as final without a legal
   review of the Legal Basics and Taxes sections** — this is explicitly flagged
   because real estate/visa/tax content directly affects a reader's financial
   decisions.
6. **Internal links to not-yet-built pages** — region cards, property-type cards and
   "Related Guides & Tools" cards intentionally do not link to real URLs yet (no
   `/thailand/phuket/`, no `/rental-yield-roi-calculator/`, etc., exist). This avoids
   shipping broken links, but it also means the "related properties"/"related guides"
   structural section from the original brief is currently informational text, not a
   working related-content widget. This becomes a real, testable set of internal
   links once phase 2 (region/category pages) is built — see README "What's next".
7. **Accessibility audit tooling** (axe, WAVE, Lighthouse a11y score) was not run;
   only manual checks (landmarks, skip link, `:focus-visible` styles, labelled form
   fields, `aria-expanded`/`aria-controls` on the nav toggle, `aria-live="polite"` on
   the form status message) were done by reading the markup and interacting with it.
8. **Contact form has no backend** — confirmed working as a front-end demo only (see
   above). No email/CRM is connected. This is called out again here because it is the
   single most important thing to fix before this page can generate real leads.

## Next review gate (v1, resolved)

The brief asked for one page to be perfected and reviewed before moving to property
cards for new developments across Thailand. **This gate passed** — the hub page was
approved and the region-page phase below was built next.

## v2 — region pages and card template

### What was tested (with evidence)

| Test | Method | Result |
|---|---|---|
| Title/description/H1 length and count, all 5 region pages | Python script against built files | See `seo-checklist.md` §"v2" for the full table — all within С68 norms, 1 H1 each |
| FAQPage JSON-LD ↔ visible `<details>` match, all 5 region pages | Script diff after whitespace/entity normalization | 4/5 matched on first run; Pattaya's "guaranteed rental yield" question had a curly-vs-straight-quote mismatch between the visible text and the JSON-LD, found and fixed; all 5 now match exactly |
| JSON-LD syntax validity, all 7 pages (hub + 5 regions + template) | `json.loads()` on every block | 0 parse errors across all blocks |
| Internal link integrity, all 7 pages | Script resolving every `href="/..."` against the real file tree | 0 broken links |
| No accidental `noindex` on the 5 region pages | Checked each page's robots meta | All 5 are `index, follow`; only the card template is `noindex, follow`, as intended |
| Visual render, desktop and mobile | Local server + browser screenshots on `/phuket/` and `/pattaya/` | Breadcrumb, hero stats, price/yield table, region-pills (with correct `aria-current` highlighting), FAQ accordion all render correctly at both sizes |
| Property-card template render | Local server + browser screenshot | Sample banner, gallery placeholder grid, spec list, stats all render as designed; no `<img>`/broken-alt risk since it's also SVG/placeholder-only |
| Browser console errors | `read_console_messages` (errors only) on the template page | 0 errors |

### What was NOT tested / cannot be verified from this environment

Same categories as v1 (PageSpeed/Lighthouse, Google Rich Results Test, real
cross-browser testing, professional proofing, accessibility-tool audit) apply to
all 6 new pages too, for the same reason — no public URL yet. In addition:

1. **Region-page price/yield figures are not independently verified.** They come
   from brokerage reports and listing sites (FazWaz, Pearl Property, SuperAgent,
   Ananda, Lazudi, Bamboo Routes, C9 Hotelworks/Hospitality Net, Asterofasia), dated
   2025–2026, gathered via web search for this task. Each page says so next to its
   numbers. **Before launch, have someone with current local market access
   spot-check at least the headline price and yield figures** — web search results
   can be stale, promotional, or wrong.
2. **The property-card template has no real content to test.** Every field is a
   bracketed placeholder by design (see README). There is nothing to fact-check
   here because nothing on it is a claim yet — the test that matters is the one
   above: confirming it does NOT emit fake commerce schema or get indexed.
3. **Cross-region content overlap was checked by reading, not tooled.** Each region
   page's FAQ and body copy were written to be unique to that region (different
   numbers, different districts, different airport) to avoid the С68
   "cannibalization" problem, but this was a manual check, not a similarity-scoring
   tool run across all 5 pages.
4. **Compare-region internal linking is one level only.** Region pages link to each
   other via the "Compare Other Regions" pill row and link back to the hub, but
   there is no dedicated `/compare/phuket-vs-pattaya/`-style page yet — that silo
   (present in the original apartwell matrix this project adapted) was not part of
   this request and is not built.

## Next review gate (v2)

Recommended before moving to real property cards:
- Spot-check the region pages' price/yield tables against current local listings.
- Confirm the final domain (or keep `realestateinvestmentthailand.com`).
- Decide whether to commission real photography for the regions now, or keep SVG/
  placeholder visuals through the property-card phase too.
- Get real, developer-verified data for at least one new-development project before
  turning the `/new-developments/sample-listing/` template into a live card.

## Pattaya batch 1 — 10 real cards (v6)

### What was tested (with evidence)

| Test | Method | Result |
|---|---|---|
| Title (45–60) / description (140–158) / single H1, all 10 cards | Python script against built files | First pass: 8 titles under 45 chars, 2 descriptions under 140 chars — all fixed and re-verified; final pass 0 violations |
| Direct-answer paragraph: 40–60 words, has a number, no opening pronoun | Same script | 1 violation on first pass (Grand Solaire at 62 words) — trimmed; final pass 0 violations |
| "real estate investment" keyword density (target 2–4/1000, ceiling 6) | Same script, regex count over `<main>` text | First pass: 0 occurrences on all 10 cards (phrase missing entirely) — added 3 natural occurrences per card (Location section, "Why This Project" intro, CTA paragraph); final pass 2.48–2.94/1000 on all 10 |
| FAQPage JSON-LD ↔ visible `<details>` match + 40–90 word answers, all 10 cards | Script diff + word count | First pass: 0 text mismatches, but 17 answers across 9 cards were under 40 words — each extended with a genuine clarifying clause (not padding) and re-verified; final pass 0 violations, 5/5 match on every card |
| JSON-LD syntax validity, all 10 cards | `json.loads()` on every block | 0 parse errors |
| Internal link integrity, all 10 cards + updated `/pattaya/` + `sitemap.xml` | Script resolving every `href="/..."` against the real file tree | 0 broken links |
| Render + console errors | Local server, screenshot on `/pattaya/` (new grid) and `/new-developments/seaspire-jomtien/` | Grid renders correctly with all 10 cards; card page renders breadcrumb/hero/gallery/stats correctly; 0 console errors |

### Photo-vs-render verification (see README "Pattaya batch 1" for full detail)

Every one of the 10 projects' visual-asset folders was opened and inspected before
writing gallery copy, not trusted from the sheet's "Photo"/"Рендеры" label alone.
All 10 turned out to be renders-only (3 explicitly labelled, 5 confirmed by
filename/metadata inspection after the label said "Photo", 1 confirmed by image
dimensions/file-size/content inconsistent with a real photo of an unfinished
building), and 1 (Grand Solaire Noble Pattaya) had no usable visual-asset link at
all. Every card's gallery says which case applies, honestly, rather than guessing.

### What was NOT tested / cannot be verified from this environment

Same categories as v1/v2 (PageSpeed/Lighthouse, Google Rich Results Test, real
cross-browser testing, professional proofing, accessibility-tool audit, independent
verification of brokerage-sourced figures) apply to these 10 pages too.

## Pattaya batch 2 — 10 more real cards (v7)

### What was tested (with evidence)

| Test | Method | Result |
|---|---|---|
| Title (45–60) / description (140–158) / single H1, all 10 cards | Python script against built files | First pass: 8 titles under 45 chars, 2 descriptions under 140 chars — fixed, re-verified; final pass 0 violations |
| Direct-answer paragraph: 40–60 words, has a number, no opening pronoun | Same script | First pass: 3 cards over 60 words (Grand Solaire Pattaya 64, Skypark Lucean 64, Seven Seas 69) — trimmed; final pass 0 violations |
| "real estate investment" keyword density (target 2–4/1000, ceiling 6) | Same script | First pass: 6 of 10 cards at only 2 occurrences — added a 3rd natural occurrence each; final pass 1.9–3.0/1000 on all 10 |
| FAQPage JSON-LD ↔ visible `<details>` match + 40–90 word answers, all 10 cards | Script diff + word count | First pass: 0 text mismatches, 18 answers across 9 cards under 40 words — each extended with a genuine clarifying clause, re-verified twice (one card needed a third pass); final pass 0 violations, 5/5 match on every card |
| Stray-markup check (`</meta>` and similar) | `grep` for `</meta>` across all 10 files | Found in 2 files (copy-paste artifact in the twitter:description tag) — fixed immediately on sight, confirmed clean on next script run |
| JSON-LD syntax validity, all 10 cards | `json.loads()` on every block | 0 parse errors |
| Internal link integrity, all 10 cards + updated `/pattaya/` + `sitemap.xml` | Script resolving every `href="/..."` against the real file tree | 0 broken links |
| Render + console errors | Local server, screenshot on `/new-developments/riviera-palm-beach-wongamat/` and `/new-developments/seven-seas-le-carnival/` | Both render correctly (hero, gallery, stats); Seven Seas' "2020 – ?" completion-gap stat displays as intended; 0 console errors |

### Source-quality checks specific to this batch

- **Grand Solaire naming clash**: the tracker names the row "Grand Soliar" but the
  linked brief consistently spells it "Grand Solaire Pattaya" — the brief's spelling
  was used for the page, with the tracker's row name recorded in Sources, and the
  slug kept distinct from the unrelated `grand-solaire-noble-pattaya` card (verified
  by re-reading both source docs side by side, not assumed from the names alone).
- **AI-artifact check**: Level Pratumnak's brief contained a visible
  `citeturn0search4turn0search7`-style citation fragment. Treated as a signal to
  use only the document's structured facts (counts, sizes, dates, named facilities)
  and skip any unsourced descriptive claims; disclosed in the page's own Sources
  section rather than silently relying on the whole document.
- **Seven Seas Le Carnival's data gaps were read directly from its own brief, not
  inferred**: the brief's own "Год сдачи" field literally reads "2020 – ????", and
  its tracker row's visual-asset cell was empty (checked directly, not assumed from
  a missing link elsewhere). Both are stated on the page instead of guessed at or
  omitted.

### What was NOT tested / cannot be verified from this environment

Same categories as the previous batches apply to these 10 pages too.

## Pattaya batch 3 — final 4 real cards, tracker exhausted (v8)

### What was tested (with evidence)

| Test | Method | Result |
|---|---|---|
| Full-sheet survey before starting | Scrolled and read every row of the Pattaya tracker (~53 rows), checking column E (О проекте link) and column J (price) for each unbuilt row | Only 4 rows had both; the rest were already built, sold out, "price on request" resale, or missing a brief — documented in README rather than assumed |
| Title (45–60) / description (140–158) / single H1, all 4 cards | Python script against built files | First pass: 2 titles and 2 descriptions under the minimum — fixed, re-verified; final pass 0 violations |
| Direct-answer paragraph: ≤60 words, has a number, no opening pronoun | Same script | First pass: 2 cards over 60 words (LOVE IT Wongamat 63, SEA SKY 64) — trimmed; final pass 0 violations |
| FAQPage JSON-LD ↔ visible `<details>` match + 40–90 word answers, all 4 cards | Script diff + word count | First pass: 1 text mismatch found (SEA SKY Pratumnak — single quote in JSON-LD vs. straight double quote in visible text around "Quiet Luxury"), 10 answers across 4 cards under 40 words; mismatch fixed by correcting the JSON-LD string, answers extended; final pass 0 violations, 5/5 match on every card |
| JSON-LD syntax validity, all 4 cards | `json.loads()` on every block | 0 parse errors |
| Internal link integrity, all 4 cards + updated `/pattaya/` + `sitemap.xml` | Script resolving every `href="/..."` against the real file tree | 0 broken links |
| Render + console errors | Local server, screenshot on `/new-developments/sea-sky-pratumnak/`, grid check on `/pattaya/` | Card renders correctly including the corrected quote mark; all 24 Pattaya cards linked from the grid; 0 console errors |

### What was NOT tested / cannot be verified from this environment

Same categories as the previous batches apply to these 4 pages too.
