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 "'" 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:
co-authored by
Claude Opus 5
parent
e968e4ccf0
commit
c55efb20fe
@@ -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 `'` 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 {
|
||||
|
||||
Reference in New Issue
Block a user