From 613f691b7c458651151d0ce84860c4367a2eebf6 Mon Sep 17 00:00:00 2001 From: nish3451 <257724087+nish3451@users.noreply.github.com> Date: Sat, 8 Aug 2026 15:09:33 +0530 Subject: [PATCH] feat: add truthful public page structured data Adds source-bound JSON-LD for the contact and Promptly privacy pages, with focused regression coverage and existing test wiring. Co-Authored-By: Claude --- package.json | 4 +- public/contact/index.html | 26 +++++ public/promptly/privacy/index.html | 26 +++++ scripts/test-public-structured-data.mjs | 120 ++++++++++++++++++++++++ 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 scripts/test-public-structured-data.mjs diff --git a/package.json b/package.json index 3b7a1e54..181f2ada 100644 --- a/package.json +++ b/package.json @@ -85,9 +85,9 @@ "product:truth": "node scripts/check-product-truth.mjs", "mobbin:run": "node scripts/run-design-system-proving-lab.mjs", "mobbin:check": "node scripts/run-design-system-proving-lab.mjs --dry-run", - "ci": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-heading-hierarchy.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check", + "ci": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-heading-hierarchy.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check", "check": "npm test", - "test": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-heading-hierarchy.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check" + "test": "node scripts/test-service-engine.mjs && node scripts/test-sales-intake-contract.mjs && node scripts/test-active-offer-projection.mjs && node scripts/test-active-operator-surfaces.mjs && node scripts/test-direction-proof-gate.mjs && node scripts/test-client-readiness-contract.mjs && node scripts/test-validated-service-client.mjs && node scripts/test-client-acceptance-gates.mjs && node scripts/check-product-truth.mjs && node scripts/check-human-service-kit.mjs && node scripts/test-design-system-proving-lab.mjs && node scripts/check-retention-automation.mjs && node scripts/test-retention-automation.mjs && node scripts/check-agency-defaults.mjs && node scripts/check-outbound-claim-safety.mjs && node scripts/check-outbound-send-readiness.mjs && node scripts/test-outbound-send-readiness.mjs && node scripts/test-public-conversion-signal.mjs && node scripts/test-public-structured-data.mjs && node scripts/test-public-heading-hierarchy.mjs && find scripts -name '*.mjs' -print0 | xargs -0 -n1 node --check" }, "engines": { "node": ">=20" diff --git a/public/contact/index.html b/public/contact/index.html index b454fc5a..658236b3 100644 --- a/public/contact/index.html +++ b/public/contact/index.html @@ -27,6 +27,32 @@ + diff --git a/public/promptly/privacy/index.html b/public/promptly/privacy/index.html index a9a39fb3..db63ed04 100644 --- a/public/promptly/privacy/index.html +++ b/public/promptly/privacy/index.html @@ -19,6 +19,32 @@ + diff --git a/scripts/test-public-structured-data.mjs b/scripts/test-public-structured-data.mjs new file mode 100644 index 00000000..04ee11dc --- /dev/null +++ b/scripts/test-public-structured-data.mjs @@ -0,0 +1,120 @@ +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}`) + } +} + +const PAGES = [ + { + file: "public/contact/index.html", + pageType: "ContactPage", + label: "contact page" + }, + { + file: "public/promptly/privacy/index.html", + pageType: "WebPage", + label: "Promptly privacy policy page" + } +] + +const ORG_ID = "https://tinystudio.in/#organization" +const ORG_NAME = "Tiny Studio" + +const titleOf = (html) => { + const m = html.match(/([^<]*)<\/title>/i) + return m ? m[1].trim() : "" +} +const descriptionOf = (html) => { + const m = html.match(/<meta\b[^>]*name="description"[^>]*content="([^"]*)"[^>]*>/i) + return m ? m[1] : "" +} +const canonicalOf = (html) => { + const m = html.match(/<link\s+rel="canonical"\s+href="([^"]*)">/i) + return m ? m[1] : "" +} +const jsonLdBlocksOf = (html) => + [...html.matchAll(/<script\s+type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi)].map( + (m) => m[1] + ) + +console.log("test-public-structured-data: JSON-LD structured data on public pages") + +for (const { file, pageType, label } of PAGES) { + console.log(`A. ${file}`) + const html = read(file) + const blocks = jsonLdBlocksOf(html) + + ok(blocks.length === 1, `${label} has exactly one application/ld+json block`) + if (blocks.length !== 1) continue + + let graph + try { + graph = JSON.parse(blocks[0]) + ok(true, `${label} JSON-LD block parses as valid JSON`) + } catch (err) { + ok(false, `${label} JSON-LD block parses as valid JSON (${err.message})`) + continue + } + + ok(graph["@context"] === "https://schema.org", `${label} uses the schema.org context`) + ok(Array.isArray(graph["@graph"]), `${label} JSON-LD uses an @graph array`) + + const canonical = canonicalOf(html) + const title = titleOf(html) + const description = descriptionOf(html) + ok(canonical.length > 0, `${label} page declares a canonical URL`) + ok(title.length > 0, `${label} page declares a title`) + ok(description.length > 0, `${label} page declares a description`) + + const org = (graph["@graph"] || []).find( + (node) => node["@type"] === "Organization" && node["@id"] === ORG_ID + ) + ok( + org && org.name === ORG_NAME && org.url === "https://tinystudio.in/", + `${label} JSON-LD carries the stable Tiny Studio organization reference` + ) + + const page = (graph["@graph"] || []).find((node) => node["@type"] === pageType) + ok(page !== undefined, `${label} JSON-LD declares the page as ${pageType}`) + if (!page) continue + + ok(page.url === canonical, `${label} JSON-LD url matches the page canonical URL`) + ok(page.name === title, `${label} JSON-LD name matches the page title`) + ok(page.description === description, `${label} JSON-LD description matches the meta description`) + ok( + page["isPartOf"] && page["isPartOf"]["@id"] === "https://tinystudio.in/#website", + `${label} JSON-LD page is part of the Tiny Studio website` + ) + ok( + page["about"] && page["about"]["@id"] === ORG_ID, + `${label} JSON-LD page is about the Tiny Studio organization` + ) +} + +console.log("B. npm test/ci wiring") +const pkg = JSON.parse(read("package.json")) +const wired = pkg.scripts.test.includes("test-public-structured-data.mjs") +ok(wired, "npm test runs the public structured data test") +ok( + pkg.scripts.check === "npm test", + "npm run check delegates to npm test" +) +ok( + pkg.scripts.ci.includes("test-public-structured-data.mjs"), + "npm run ci runs the public structured data test" +) + +console.log(`\n${checks} checks, ${failures} failures`) +process.exit(failures === 0 ? 0 : 1)