fix(public): guard the live contact page against the heading-hierarchy finding (closes 2026-08-09 dogfood item) - #119
Conversation
…y finding The 2026-08-08 dogfood finding "Heading hierarchy needs cleanup on /contact" (finding 52753880dfc7) was repaired in source by PR #18 (card headings promoted from H3 to H2, outline H1 -> H2s -> H3s with no jumps), and scripts/test-public-heading-hierarchy.mjs guards the worktree HTML. But the live site still serves the June-20 bundle: the deployed /contact/ page jumps H1 -> H3 (live outline [1,3,3,3,2,3,3,3]) and the deployed stylesheet keeps the old .info-card h3-only rule, so the finding silently stays open against tinystudio.in. Land the live guard following the fleet's live-guard pattern: - scripts/test-public-live-contact-heading-hierarchy.mjs (new): fetches the deployed /contact/ and /styles.css, then re-asserts the repaired outline (exactly one H1 first, three H2 card titles inside .info-card, flat H2 band before footer H3s, no heading-level jump greater than one) and the shared .info-card :is(h2, h3) rule at the former card scale. - Wired as npm run site:check-live-contact-heading-hierarchy and into the nightly live-site-check.yml staleness alarm. Deliberately NOT part of npm run test / npm run ci - blocking chains stay green on repo state alone, same convention as the soft-404 guard. - scripts/check-public-live-deploy.mjs: the deploy lane's post-deploy acceptance now also asserts H2-after-H1 on /contact/, so the moment the Pages token is provisioned and the lane publishes, the live repair is verified as part of deploy acceptance. - Network-tolerant: skips when the site is unreachable; fails loudly when the deployment serves stale pages. verify: node scripts/test-public-heading-hierarchy.mjs green; guard fails loudly on the current stale deployment; SKIP_LIVE_CHECKS=1 exit 0; git diff --check clean
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
📝 WalkthroughWalkthroughThe pull request adds a live ChangesContact heading validation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant NightlyWorkflow
participant NpmScript
participant ContactHeadingCheck
participant LiveContactPage
participant LiveContactStylesheet
NightlyWorkflow->>NpmScript: run site:check-live-contact-heading-hierarchy
NpmScript->>ContactHeadingCheck: execute test script
ContactHeadingCheck->>LiveContactPage: fetch /contact/
LiveContactPage-->>ContactHeadingCheck: return HTML
ContactHeadingCheck->>ContactHeadingCheck: validate heading hierarchy
ContactHeadingCheck->>LiveContactStylesheet: fetch stylesheet
LiveContactStylesheet-->>ContactHeadingCheck: return CSS
ContactHeadingCheck->>ContactHeadingCheck: validate card-heading CSS
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/test-public-live-contact-heading-hierarchy.mjs`:
- Around line 77-81: Update the response handling around the fetch in the live
endpoint test so non-2xx responses record a failed assertion instead of logging
a skip and returning null. Keep skips limited to fetch errors and timeouts,
while preserving normal validation for successful responses.
- Around line 95-104: Update the heading validation loop in
scripts/test-public-live-contact-heading-hierarchy.mjs to track whether the
first H3 has been seen and count any subsequent H2 as a failure. Add an
assertion that no H2 appears after the first H3, while preserving the existing
jump and H2-count checks.
- Around line 132-137: Update the heading-hierarchy guard in the test script to
track failures separately for each target, so stale-stylesheet messaging only
reflects the stylesheet check itself rather than earlier page assertions. Use
the final target result to choose the concluding message, reporting the finding
as open only when the relevant live deployment check fails and reporting success
when all checks pass.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8cf20cc6-e98a-4a97-9aab-efb569f78427
📒 Files selected for processing (4)
.github/workflows/live-site-check.ymlpackage.jsonscripts/check-public-live-deploy.mjsscripts/test-public-live-contact-heading-hierarchy.mjs
| const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }) | ||
| if (!res.ok) { | ||
| console.log(` ok skipped: ${url} answered ${res.status}, deployment not reachable - no assertions run for it`) | ||
| return null | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Fail when the live endpoint returns an HTTP error.
An HTTP response proves that the endpoint is reachable. Lines 78-80 convert a 404 or 500 response into a successful skip. The guard then exits successfully without validating a missing or failed deployment. Reserve skips for fetch errors and timeouts. Record a failed assertion for non-2xx responses.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/test-public-live-contact-heading-hierarchy.mjs` around lines 77 - 81,
Update the response handling around the fetch in the live endpoint test so
non-2xx responses record a failed assertion instead of logging a skip and
returning null. Keep skips limited to fetch errors and timeouts, while
preserving normal validation for successful responses.
| const cardH2s = levels.filter((l) => l === 2).length | ||
| ok(cardH2s >= 4, `live ${name} page keeps the flat H2 band (card H2s plus the footer H2) before the footer H3s`) | ||
| let jumps = 0 | ||
| for (let i = 1; i < levels.length; i++) { | ||
| if (levels[i] - levels[i - 1] > 1) { | ||
| jumps++ | ||
| console.error(` bad transition H${levels[i - 1]} -> H${levels[i]} on ${name}`) | ||
| } | ||
| } | ||
| ok(jumps === 0, `live ${name} page has no heading-level jump greater than one (no H1 -> H3 skip)`) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Verify that no H2 follows the first H3.
The current checks accept H1 -> H2 -> H3 -> H2. That sequence has no upward level jump and still meets the H2 count. It violates the required flat H2 band before the footer H3 headings. After the first H3, fail if a later H2 exists.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/test-public-live-contact-heading-hierarchy.mjs` around lines 95 -
104, Update the heading validation loop in
scripts/test-public-live-contact-heading-hierarchy.mjs to track whether the
first H3 has been seen and count any subsequent H2 as a failure. Add an
assertion that no H2 appears after the first H3, while preserving the existing
jump and H2-count checks.
| if (failures > 0) { | ||
| console.error(" the deployed stylesheet is stale: it misses the card-heading pairing that public/styles.css already has. Refresh the live deployment from origin/main.") | ||
| } | ||
| } | ||
|
|
||
| console.log("\nLive /contact/ heading-hierarchy guard result: the finding stays open against tinystudio.in until a refresh of the live deployment lands on origin/main.") |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Report the result for the correct target.
The stylesheet message reports stale CSS when an earlier page assertion incremented global failures. Line 137 also reports that the finding remains open when all checks pass. Track failures per target, and select the final message from the final result.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/test-public-live-contact-heading-hierarchy.mjs` around lines 132 -
137, Update the heading-hierarchy guard in the test script to track failures
separately for each target, so stale-stylesheet messaging only reflects the
stylesheet check itself rather than earlier page assertions. Use the final
target result to choose the concluding message, reporting the finding as open
only when the relevant live deployment check fails and reporting success when
all checks pass.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: aefca50a33
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) }) | ||
| if (!res.ok) { | ||
| console.log(` ok skipped: ${url} answered ${res.status}, deployment not reachable - no assertions run for it`) | ||
| return null |
There was a problem hiding this comment.
Fail closed when the monitored route is not its own 200 response
In the nightly live-contact-heading-hierarchy job, a reachable /contact/ that returns 404/500 is logged as an “ok skipped” result, while the default redirect-following behavior also accepts another page’s response. For example, a redirect to /promptly/ would pass because that page has the same one-H1, three-card-H2 outline, so a broken contact route can leave this job green. Require a 200 response from the expected final URL; reserve skipping only for the intended network-unavailable case.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c2aa7c007c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| { | ||
| const { status, body } = await get("/contact/") | ||
| ok(status === 200, `/contact/ returns 200 (got ${status})`) | ||
| ok(h2AfterFirstH1(body), "H2 follows the first H1 before any H3") |
There was a problem hiding this comment.
Verify every contact card heading after deployment
In the deploy-public-site.yml publish path, this is the new post-upload acceptance assertion, but h2AfterFirstH1 only proves that some H2 precedes the first H3. A partially stale /contact/—for example, one with an earlier managed-service H2 or only the first card promoted while later card titles remain H3—passes this check even though the heading-hierarchy repair is not deployed, causing the release lane to report the publish as verified. Apply the three-card and no-jump assertions used by the nightly guard here as well.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7e287054c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| // Count <h2> used as .info-card card titles (inside <article class="info-card">). | ||
| const infoCardTitleCount = (html) => | ||
| (html.match(/<article class="info-card[^"]*"[^>]*>[\s\S]*?<h2\b/gi) || []).length |
There was a problem hiding this comment.
Bound each card-heading match to its article
In the nightly live-contact-heading-hierarchy job, this regex can cross the closing </article> while searching for an H2. If the third contact card regresses to H3, its match consumes the footer H2 instead, so infoCardTitleCount still returns 3; the current page also retains enough other H2s, and the H2→H3→H2 sequence creates no upward level jump, allowing every outline assertion to pass. Restrict each match to the contents of its individual .info-card article so a partially regressed deployment fails the guard.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
nish3451 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fad66f9cf6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const cardH2s = levels.filter((l) => l === 2).length | ||
| ok(cardH2s >= 4, `live ${name} page keeps the flat H2 band (card H2s plus the footer H2) before the footer H3s`) |
There was a problem hiding this comment.
Scope the footer H2 check to the footer
When the unfiltered contact page is deployed—or after the snoozed sections return—this count no longer verifies the claimed footer H2: the current source contains two additional non-card H2s, so changing the footer heading from H2 to H3 still leaves at least four H2s, while the no-jump check also passes. Inspect the footer heading directly so the nightly guard actually enforces the documented H2 footer -> H3 columns outline.
Useful? React with 👍 / 👎.
Why
The 2026-08-08 dogfood finding "Heading hierarchy needs cleanup on /contact" (finding 52753880dfc7) was repaired in source by PR #18 (card headings promoted from H3 to H2, outline H1 -> H2s -> H3s with no jumps), and
scripts/test-public-heading-hierarchy.mjsguards the worktree HTML. But the live site still serves the June-20 bundle: the deployed /contact/ page jumps H1 -> H3 (live outline [1,3,3,3,2,3,3,3]) and the deployed stylesheet keeps the old.info-card h3-only rule — verified live on 2026-08-12. The finding silently stays open against tinystudio.in until the deployment refreshes.This lands the live guard so the staleness is loud, following the fleet's live-guard pattern (like the soft-404 guard). Supersedes the stale, conflicting PR #78 (same intent; its branch never got past a conflict with the merged live-site-check.yml).
What
scripts/test-public-live-contact-heading-hierarchy.mjs(new): fetches the deployed/contact/and/styles.css, then re-asserts the repaired outline (exactly one H1 first, three H2 card titles inside.info-card, flat H2 band before footer H3s, no heading-level jump greater than one) and the shared.info-card :is(h2, h3)rule at the former card scale.npm run site:check-live-contact-heading-hierarchyand into the nightlylive-site-check.ymlstaleness alarm (alongside the soft-404 check). Deliberately NOT part ofnpm run test/npm run ci— blocking chains stay green on repo state alone, same convention as the soft-404 guard.scripts/check-public-live-deploy.mjs: the deploy lane's post-deploy acceptance now also asserts H2-after-H1 on /contact/, so the moment the Pages token is provisioned and the lane publishes, the live repair is verified as part of deploy acceptance.Current status
The guard intentionally fails against the live site right now (11 checks, 8 failures — all stale-deployment detections), keeping this item visibly open until the production deploy path is repaired (dormant release lane, PR #111) and main is published. Once the deployment refreshes, this guard goes green and the finding stays closed.
Verify
node scripts/test-public-heading-hierarchy.mjs→ 62 checks, 0 failures (worktree HTML stays repaired)node scripts/test-public-live-contact-heading-hierarchy.mjs→ fails loudly on the stale live deployment (exit 1)SKIP_LIVE_CHECKS=1 node scripts/test-public-live-contact-heading-hierarchy.mjs→ exit 0node scripts/check-public-live-deploy.mjswithSKIP_LIVE_CHECKS=1→ exit 0find scripts -name '*.mjs' | xargs node --check→ clean;git diff --check→ cleannpm testexit 1 is the pre-existing environment-specific retention-automation preflight (identical on clean origin/main, unrelated to this change)Summary by CodeRabbit
Bug Fixes
Chores