This repository was archived by the owner on Oct 12, 2026. It is now read-only.
Repository navigation
fix(public): restore the Promptly support heading hierarchy in production (fresh live guard, supersedes stale PR #77) #128
Closed
nish3451
wants to merge
5
commits into
main
from
fix/lane1-promptly-support-heading-live-restore-20260812
Closed
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
14c397d
fix(public): guard the live Promptly support page against the skipped…
nish3451 9e79318
Merge branch 'main' into fix/lane1-promptly-support-heading-live-rest…
nish3451 e862943
Merge branch 'main' into fix/lane1-promptly-support-heading-live-rest…
nish3451 da68ceb
Merge main into fix/lane1-promptly-support-heading-live-restore-20260…
nish3451 7aef97a
Merge branch 'main' into fix/lane1-promptly-support-heading-live-rest…
nish3451 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) }) | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When 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) | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
/promptly/support/is redirected to/promptly/,fetchfollows the redirect by default and this guard still passes: the Promptly landing page also has exactly one H1, three.info-cardH2 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 withredirect: "manual"or verify that the final response URL still identifies/promptly/support/before asserting its body.Useful? React with 👍 / 👎.