Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 4 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
11 changes: 11 additions & 0 deletions .github/workflows/live-site-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ jobs:
cache: npm
- run: npm ci --ignore-scripts
- run: node scripts/check-public-live-soft-404.mjs
live-promptly-support-heading:
runs-on: [self-hosted, linux, x64, vps-verify]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 20
cache: npm
- run: npm ci --ignore-scripts
- run: node scripts/test-public-live-promptly-support-heading.mjs

live-contact-heading-hierarchy:
runs-on: [self-hosted, linux, x64, vps-verify]
steps:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"retention:automation-check": "node scripts/check-retention-automation.mjs",
"site:prepare": "node scripts/prepare-static-site-bundle.mjs",
"site:check-live": "node scripts/check-public-live-soft-404.mjs",
"site:check-live-promptly-support-heading": "node scripts/test-public-live-promptly-support-heading.mjs",
"site:check-live-contact-heading-hierarchy": "node scripts/test-public-live-contact-heading-hierarchy.mjs",
"claims:check": "node scripts/check-outbound-claim-safety.mjs",
"config:check": "node scripts/check-agency-defaults.mjs",
Expand Down
1 change: 1 addition & 0 deletions scripts/test-public-heading-hierarchy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const AFFECTED_PAGES = [
"public/contact/index.html",
"public/promptly/index.html",
"public/promptly/privacy/index.html",
"public/promptly/support/index.html",
"public/drishti/index.html",
"public/drishti/support/index.html",
"public/privacy-choices/index.html",
Expand Down
137 changes: 137 additions & 0 deletions scripts/test-public-live-promptly-support-heading.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Guard the LIVE Promptly support page against skipped heading levels.
//
// The 2026-08-08 scout item named /promptly/support/: it was repaired in
// source by PR #20 (card headings promoted from H3 to H2, outline
// H1 -> H2x3 -> footer H2 -> H3s with no jumps) and origin/main carries the
// fixed outline. This guard re-asserts the repaired outline against the
// page the live site actually serves, so a stale deployment (like the
// June-20 bundle still serving H3 cards) fails loudly instead of silently
// re-opening the finding.
//
// It runs standalone as `npm run site:check-live-promptly-support-heading`
// and from the nightly live-site-check.yml staleness net. It is
// deliberately NOT part of `npm test` / `npm run ci`: those blocking
// chains must stay green on repo state alone, while the live site is
// deployed by an external mechanism (Cloudflare Pages). Blocking CI on the
// live site would keep every pull request red whenever the deployment is
// stale, and would deadlock the deploy lane's pre-deploy `npm run check`
// gate. Network-tolerant: skips when the site is unreachable, fails loudly
// while it serves the stale bundle.
import { readFileSync } from "node:fs"
import { fileURLToPath } from "node:url"
import { dirname, join } from "node:path"

const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..")
const read = (p) => readFileSync(join(ROOT, p), "utf8")

let failures = 0
let checks = 0
const ok = (cond, msg) => {
checks++
if (cond) console.log(` ok ${msg}`)
else {
failures++
console.error(` FAIL ${msg}`)
}
}

// The deployed page must keep the repaired heading outline (H1 -> H2 cards
// -> H2 footer -> H3 footer columns, no skipped levels). The local suite
// (test-public-heading-hierarchy.mjs) asserts the same outline against the
// worktree HTML; this guard re-asserts it against the page the live site
// actually serves, so a stale deployment (like the June-20 bundle still
// serving H3 cards) fails loudly instead of silently re-opening the
// skipped-heading-level finding from the 2026-08-08 scout item.
const LIVE_PAGE = {
name: "Promptly support",
url: "https://tinystudio.in/promptly/support/",
source: "public/promptly/support/index.html"
}
const LIVE_CSS_URL = "https://tinystudio.in/styles.css"
const FETCH_TIMEOUT_MS = 10_000

// Heading levels in document order, e.g. [1, 2, 2, 2, 2, 3, 3, 3].
const headingLevelsOf = (html) =>
[...html.matchAll(/<h([1-6])\b[^>]*>/gi)].map((m) => Number(m[1]))

// 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

const fetchLive = async (url) => {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject redirects away from the support route

When /promptly/support/ is redirected to /promptly/, fetch follows the redirect by default and this guard still passes: the Promptly landing page also has exactly one H1, three .info-card H2 headings, at least four H2s, no heading-level jumps, and uses the same stylesheet. Consequently, a missing support destination can leave the nightly job green; fetch with redirect: "manual" or verify that the final response URL still identifies /promptly/support/ before asserting its body.

Useful? React with 👍 / 👎.

if (!res.ok) {
console.log(` ok skipped: ${url} answered ${res.status}, deployment not reachable - no assertions run for it`)
return null
Comment on lines +64 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fail the live guard on non-2xx responses

When /promptly/support/ or /styles.css responds with a 404 or 5xx, fetch succeeds but this branch returns null, so all assertions for that resource are skipped and the nightly job can exit successfully. In particular, the companion live-soft-404 job checks /promptly/ rather than /promptly/support/, so a missing support route can leave the entire workflow green; reserve the network-tolerant skip for actual connection failures and count non-2xx HTTP responses as failures.

Useful? React with 👍 / 👎.

}
return await res.text()
} catch (err) {
console.log(` ok skipped: ${url} unreachable (${err?.cause?.code ?? err?.name ?? "network error"}) - no assertions run for it`)
return null
}
}

const assertRepairedOutline = (name, source, html) => {
const levels = headingLevelsOf(html)
ok(levels.length > 0, `live ${name} page contains at least one heading`)
ok(levels.filter((l) => l === 1).length === 1, `live ${name} page has exactly one H1`)
ok(levels[0] === 1, `live ${name} page has the H1 as the first heading in the outline`)
ok(infoCardTitleCount(html) === 3, `live ${name} page has the three card headings as H2s inside .info-card articles`)
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)`)
if (failures > 0) {
console.error(` the deployed ${source} is stale: it misses the heading-hierarchy repair that the worktree copy of ${source} already has. Refresh the live deployment from origin/main.`)
}
}

console.log("test-public-live-promptly-support-heading: the deployed tinystudio.in Promptly support page keeps the repaired heading outline (no skipped levels)")

console.log("A. live page carries the repaired heading hierarchy")
const html = await fetchLive(LIVE_PAGE.url)
if (html !== null) assertRepairedOutline(LIVE_PAGE.name, LIVE_PAGE.source, html)

console.log("B. live stylesheet keeps the shared card-heading rule at the former card scale")
const css = await fetchLive(LIVE_CSS_URL)
if (css !== null) {
const ruleStart = css.indexOf(".info-card :is(h2, h3) {")
ok(ruleStart !== -1, "live styles.css defines .info-card :is(h2, h3) {")
const ruleEnd = ruleStart === -1 ? -1 : css.indexOf("}", ruleStart)
const ruleBody = ruleStart === -1 ? "" : css.slice(ruleStart, ruleEnd)
for (const decl of ["margin-top: 12px", "font-size: clamp(1.65rem, 2vw, 2.35rem)", "max-width: none"]) {
ok(ruleBody.includes(decl), `live card rule keeps ${decl}`)
}
ok(
!/\.info-card\s+h3\s*{/.test(css),
"live styles.css replaced the old .info-card h3-only rule with the shared :is(h2, h3) rule"
)
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("C. standalone script wiring (out of the blocking ci/test chain)")
const pkg = JSON.parse(read("package.json"))
ok(
pkg.scripts["site:check-live-promptly-support-heading"] === "node scripts/test-public-live-promptly-support-heading.mjs",
"npm run site:check-live-promptly-support-heading runs this guard on demand"
)
ok(
!pkg.scripts.ci.includes("test-public-live-promptly-support-heading.mjs"),
"npm run ci does not block on the live-site guard (blocking chain stays green on repo state alone)"
)
ok(
!pkg.scripts.test.includes("test-public-live-promptly-support-heading.mjs"),
"npm test does not block on the live-site guard"
)

console.log(`\n${checks} checks, ${failures} failures`)
process.exit(failures === 0 ? 0 : 1)
Loading