Skip to content
This repository was archived by the owner on Oct 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 3 additions & 3 deletions public/drishti/support/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ <h1>Drishti support is in place before launch day.</h1>
<section class="card-grid">
<article class="info-card reveal delay-1">
<p class="eyebrow">Email route</p>
<h3>Ready for real support traffic.</h3>
<h2>Ready for real support traffic.</h2>
<p>
Drishti support routes through Tiny Studio’s support inbox with an
app-specific subject line so the public support flow is clean and
Expand All @@ -86,7 +86,7 @@ <h3>Ready for real support traffic.</h3>

<article class="info-card reveal delay-2">
<p class="eyebrow">Related pages</p>
<h3>Everything a launch page needs nearby.</h3>
<h2>Everything a launch page needs nearby.</h2>
<p>
Drishti’s support page is paired with an app privacy page, the
studio contact page, and the general Tiny Studio support hub.
Expand All @@ -95,7 +95,7 @@ <h3>Everything a launch page needs nearby.</h3>

<article class="info-card reveal delay-3">
<p class="eyebrow">Public role</p>
<h3>Stable now, useful later.</h3>
<h2>Stable now, useful later.</h2>
<p>
This support page already works for submission and will continue
to be the right public support destination after launch.
Expand Down
6 changes: 3 additions & 3 deletions public/privacy-choices/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ <h1>Privacy questions and data requests have a clear route.</h1>
<section class="card-grid">
<article class="info-card reveal delay-1">
<p class="eyebrow">Access</p>
<h3>Ask what information may be involved.</h3>
<h2>Ask what information may be involved.</h2>
<p>
If you need help understanding what information may have been
shared through support or connected to your use of an app, this is
Expand All @@ -86,7 +86,7 @@ <h3>Ask what information may be involved.</h3>

<article class="info-card reveal delay-2">
<p class="eyebrow">Correction or deletion</p>
<h3>Use the same privacy inbox.</h3>
<h2>Use the same privacy inbox.</h2>
<p>
Correction and deletion requests can be sent through the privacy
email path so they stay connected to the rest of the studio’s
Expand All @@ -96,7 +96,7 @@ <h3>Use the same privacy inbox.</h3>

<article class="info-card reveal delay-3">
<p class="eyebrow">Current setup</p>
<h3>The website itself is lightweight.</h3>
<h2>The website itself is lightweight.</h2>
<p>
Tinystudio.in does not currently offer user account sign-in or
payment processing, so privacy questions will usually relate to an
Expand Down
8 changes: 7 additions & 1 deletion public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -700,8 +700,14 @@ h4 {
box-shadow: var(--shadow);
}

.info-card h3 {
/* Card headings keep the former h3 card-title scale and margin even when
semantic markup uses h2; inside cards the global h2 max-width cap (12ch)
is removed so card titles can wrap naturally. Global h2 styling is
intentionally untouched. */
.info-card :is(h2, h3) {
margin-top: 12px;
font-size: clamp(1.65rem, 2vw, 2.35rem);
max-width: none;
}

.info-card p {
Expand Down
46 changes: 41 additions & 5 deletions scripts/test-public-heading-hierarchy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,41 @@ const AFFECTED_PAGES = [
"public/contact/index.html",
"public/promptly/index.html",
"public/promptly/privacy/index.html",
"public/drishti/index.html"
"public/drishti/index.html",
"public/drishti/support/index.html",
"public/privacy-choices/index.html"
]

// Heading levels in document order, e.g. [1, 2, 2, 3].
// 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]))

console.log("test-public-heading-hierarchy: rendered heading outline on the four finding pages")
// 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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the H2 match inside each article

This regex can continue past the matching </article> until it finds any later H2, so it does not establish the containment asserted by the test. For example, moving the third card title immediately after its closing article still produces a count of three, retains four total H2s and creates no level jump, causing every new hierarchy check to pass even though that card has no H2. Limit each match to the article body or inspect each article independently.

Useful? React with 👍 / 👎.

Comment on lines +33 to +35

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope each card match to its closing </article>.

The current pattern is not bounded by an article end tag. If a card regresses to H3, the match can consume the next card’s or footer’s H2 and still count that card. The test can therefore pass with fewer than three card H2 headings.

Match each article body separately and count articles that contain exactly one H2.

Proposed fix
 const infoCardTitleCount = (html) =>
-  (html.match(/<article class="info-card[^"]*"[^>]*>[\s\S]*?<h2\b/gi) || []).length
+  [...html.matchAll(
+    /<article\b[^>]*class="[^"]*\binfo-card\b[^"]*"[^>]*>([\s\S]*?)<\/article>/gi
+  )].filter(([, body]) => (body.match(/<h2\b/gi) || []).length === 1).length

Also applies to: 55-55

🤖 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-heading-hierarchy.mjs` around lines 33 - 35, Update
infoCardTitleCount to bound each match from an `<article class="info-card">`
opening tag through its corresponding closing `</article>`, then count only
article bodies containing exactly one H2 heading. Ensure H2 elements outside the
matched article, including later cards or the footer, cannot satisfy the count.


// The CSS rule that keeps card headings at the former h3 card-title scale
// regardless of h2 semantics, and lifts the global h2 12ch cap inside cards.
const CARD_HEADING_RULE = ".info-card :is(h2, h3) {"
const CARD_RULE_DECLARATIONS = [
"margin-top: 12px", // former .info-card h3 margin
"font-size: clamp(1.65rem, 2vw, 2.35rem)", // former h3 card-title scale
"max-width: none" // no 12ch cap inside cards
]

console.log("test-public-heading-hierarchy: card headings are semantic H2s with the former card scale")

for (const page of AFFECTED_PAGES) {
const levels = headingLevelsOf(read(page))
const html = read(page)
const levels = headingLevelsOf(html)
console.log(`A. ${page}`)
ok(levels.length > 0, "page contains at least one heading")
ok(levels.filter((l) => l === 1).length === 1, "page has exactly one H1")
ok(levels[0] === 1, "the H1 is the first heading in the outline")
ok(infoCardTitleCount(html) === 3, "the three card headings are H2s inside .info-card articles")
// Outline: H1 -> three H2 cards -> footer headings (footer H2 then H3s).
const cardH2s = levels.filter((l) => l === 2).length
ok(cardH2s >= 4, "card H2s plus the footer H2 keep a flat H2 band before the footer H3s")
let jumps = 0
for (let i = 1; i < levels.length; i++) {
if (levels[i] - levels[i - 1] > 1) {
Expand All @@ -46,7 +66,23 @@ for (const page of AFFECTED_PAGES) {
ok(jumps === 0, "no heading-level jump greater than one (no H1 -> H3 skip)")
}

console.log("B. npm test/ci wiring")
console.log("B. card-heading CSS pairing")
const css = read("public/styles.css")
const ruleStart = css.indexOf(CARD_HEADING_RULE)
ok(ruleStart !== -1, `styles.css defines ${CARD_HEADING_RULE}`)
const ruleEnd = css.indexOf("}", ruleStart)
const ruleBody = ruleStart === -1 ? "" : css.slice(ruleStart, ruleEnd)
for (const decl of CARD_RULE_DECLARATIONS) {
ok(ruleBody.includes(decl), `card rule keeps ${decl}`)
}
const globalH2 = css.match(/h2\s*{[^}]*}/)?.[0] ?? ""
ok(globalH2.includes("max-width: 12ch"), "global h2 styling (12ch cap) is untouched")
Comment on lines +78 to +79

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Anchor the global H2 assertion to the global selector.

css.match(/h2\s*{[^}]*}/) searches for the substring h2 {. It can match a more specific rule such as .info-card h2 { or a comment. The assertion can pass even when the standalone global H2 rule is missing.

Anchor the match to a rule boundary or parse the selector list.

Proposed fix
-const globalH2 = css.match(/h2\s*{[^}]*}/)?.[0] ?? ""
+const globalH2 =
+  css.match(/(?:^|[}\n])\s*h2\s*{[^}]*}/)?.[0] ?? ""
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const globalH2 = css.match(/h2\s*{[^}]*}/)?.[0] ?? ""
ok(globalH2.includes("max-width: 12ch"), "global h2 styling (12ch cap) is untouched")
const globalH2 =
css.match(/(?:^|[}\n])\s*h2\s*{[^}]*}/)?.[0] ?? ""
ok(globalH2.includes("max-width: 12ch"), "global h2 styling (12ch cap) is untouched")
🤖 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-heading-hierarchy.mjs` around lines 78 - 79, Update the
globalH2 extraction in the heading hierarchy test to match only a standalone
global h2 selector at a CSS rule boundary, rather than any substring ending in
“h2 {”. Preserve the existing max-width assertion while preventing matches from
more specific selectors or comments.

ok(
!/\.info-card\s+h3\s*{/.test(css),
"the old .info-card h3-only rule is replaced by the shared :is(h2, h3) rule"
)

console.log("C. npm test/ci wiring")
const pkg = JSON.parse(read("package.json"))
ok(
pkg.scripts.test.includes("test-public-heading-hierarchy.mjs"),
Expand Down