Structured data for the public pages (relay#48)

The last item on the SEO list still missing: search engines were given no
machine-readable identity for the site at all. A WebSite/Organization block
in the shared layout covers every page.

Two Leaf-specific traps, both silent, are why this is narrower than it
could be:

  * `#` opens a Leaf tag, so a JSON-LD "@id" written the usual way with a
    "#fragment" is read as a tag name. This block carries no fragments.

  * `#(title)` and `#(description)` are HTML-escaped on the way out, and a
    <script> body is not HTML-decoded -- so a title holding an apostrophe
    would arrive as "&#39;" mid-string and break the JSON. Only the origin
    is interpolated.

`testTheStructuredDataIsValidJSON` substitutes the Leaf tags and parses the
result, because nothing checks a <script> body: a stray comma ships a block
every crawler discards while the page still renders perfectly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-09-08 07:56:40 -04:00
co-authored by Claude Opus 5
parent e968e4ccf0
commit c55efb20fe
2 changed files with 48 additions and 0 deletions
+14
View File
@@ -17,6 +17,20 @@
<link rel="icon" type="image/png" sizes="16x16" href="/brand/favicon-16.png?v=#(assetVersion)">
<link rel="apple-touch-icon" href="/brand/apple-touch-icon.png?v=#(assetVersion)">
<link rel="stylesheet" href="/landing.css?v=#(assetVersion)">
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "#(origin)/",
"name": "Flamenet Messenger",
"publisher": {
"@type": "Organization",
"name": "Extant 2000 LLC",
"url": "#(origin)/",
"logo": "#(origin)/brand/mark-512.png"
}
}
</script>
</head>
<body>
@@ -306,6 +306,40 @@ final class PublicPagesTests: XCTestCase {
"/get advertises \(version) but the newest Android release is \(newest). "
+ "Somebody deciding whether to install is reading a stale number.")
}
/// The structured-data block has to be VALID JSON, and nothing checks a
/// `<script>` body for you — a stray comma ships a block every crawler
/// silently discards, and the page still renders perfectly.
///
/// 🔴 **Two Leaf-specific traps live in this block, and both are silent.**
///
/// `#` opens a Leaf tag, so a JSON-LD `"@id"` written the usual way with a
/// `#fragment` is read as a tag name and the render fails or drops it.
/// This block therefore carries no fragment identifiers at all.
///
/// And `#(title)` / `#(description)` are HTML-escaped on the way out. Inside
/// a `<script>` that is not decoded, so a title holding an apostrophe or a
/// quote would arrive as `&#39;` mid-string and break the JSON. Only values
/// we control the shape of — the origin — are interpolated here.
func testTheStructuredDataIsValidJSON() throws {
let layout = try String(contentsOf: views.appendingPathComponent("layout.leaf"),
encoding: .utf8)
guard let open = layout.range(of: "<script type=\"application/ld+json\">"),
let close = layout.range(of: "</script>", range: open.upperBound ..< layout.endIndex)
else { return XCTFail("no structured-data block in layout.leaf") }
let body = String(layout[open.upperBound ..< close.lowerBound])
XCTAssertFalse(body.contains("#(title)") || body.contains("#(description)"),
"an HTML-escaped value is interpolated into JSON")
// Substituted with a value of the shape Leaf will actually supply.
let filled = body.replacingOccurrences(of: "#(origin)",
with: "https://flamenetmessenger.com")
XCTAssertFalse(filled.contains("#("), "an un-substituted Leaf tag is left in the JSON")
let object = try JSONSerialization.jsonObject(
with: Data(filled.utf8)) as? [String: Any]
XCTAssertEqual(object?["@context"] as? String, "https://schema.org")
XCTAssertNotNil(object?["@type"], "the block declares no type")
}
func testTheSafetyPageCarriesAReachableContact() throws {