JS-SPA — empty served body — Critical¶
Input: https://app.lumenscheduling.com — Lumen Scheduling, appointment-booking software for independent health & wellness clinics. Raw source was fetched directly this time (so this is deliberately not the Example 3 stripped-markdown trap). The served HTML body is, in full:
<body>
<div id="root"></div>
<script src="/static/js/main.8f2c.bundle.js"></script>
</body>
Everything a visitor sees — product copy, pricing, footer profile links, "last updated" dates — is painted by JavaScript into #root after load. The server ships an empty shell.
The distinction that drives this run (confirmed-absent vs unverifiable)¶
Example 3 had a stripped fetch of a page that might be rich, so absence was unverifiable and the operator refused to fabricate. Here the opposite holds: I have the raw served HTML and it is genuinely empty. The absence is confirmed, not a tool artifact — but the verification is what earns that conclusion, so I run it before I score:
- Raw
<body>→ only<div id="root">+ one bundle script. No content nodes. <head>→ carries static<title>,<meta name="description">, and Open Graph tags (server-rendered, not JS-painted). Confirmed present.- Static JSON-LD anywhere in the served HTML → none. Confirmed absent — not stripped; the raw bytes are in hand.
/llms.txt,/llms-full.txt→ 404. Confirmed absent.
The agent-readiness fact: the major AI crawlers (GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot) do not execute JavaScript — they receive exactly the empty shell above. To them this page has a title, one description sentence, and nothing else, no matter how rich it looks in a browser. This is a confirmed structural failure, not a content-quality problem.
What I can and cannot read (the line I will not cross)¶
The only entity facts confirmed from served bytes are what the static <head> carries:
<title>: "Lumen Scheduling — Online Booking for Independent Clinics"<meta name="description">: "Lumen Scheduling is appointment-booking software for independent health and wellness clinics, with automated reminders that cut no-shows."<meta property="og:url">:https://www.lumenscheduling.com
Product details, pricing, profile/sameAs links, and update dates are JS-gated and unread. I do not invent them.
Score (agent-readiness as served, not as rendered in a browser)¶
| Dimension | Score (/20) | Finding |
|---|---|---|
| Structured Data Coverage | 2 | No static JSON-LD; only title/description/OG meta in <head>. Confirmed from raw source. |
| Citability | 3 | Only the meta-description sentence is extractable; all body copy is JS-gated. |
| Crawl Signal Clarity | 14 | robots/sitemap/speed are clean — the crawl layer is not the problem; the rendering layer is. |
| Content Freshness | 2 | No dates in served HTML; any "updated" stamp is JS-painted, invisible to agents. |
| Entity Authority | 2 | No profiles/sameAs/Wikidata in served bytes; footer is JS-rendered. |
| Total | 23/100 | Band: Critical — the page is effectively blank to non-rendering agents. |
This is a real, confirmed Critical — not a provisional floor like Example 3. The raw source is in hand; the emptiness is a fact, not a tool limitation.
Per-dimension decision¶
| Dimension | Route | Why |
|---|---|---|
| Structured Data | FIX (partial) | static Organization from the confirmed <head> facts — those only. |
| Citability | ESCALATE | the real fix is server-rendering the content; I cannot produce body copy I cannot read. |
| Crawl Signal | SHIP (no producible gap) | 14/20 — robots/sitemap/speed are clean; the lost points stem from the rendering layer, which is escalated under ESCALATE 1. |
| Content Freshness | ESCALATE | dates exist only post-JS; nothing to fix as a text artifact until content is served statically. |
| Entity Authority | FIX (partial) + ESCALATE | the /llms.txt scaffold carries the name now; profiles/sameAs are unread → escalate to obtain them. |
The signature move: the operator produces the JS-independent agent layer from confirmed facts, and escalates the rendering — it does not fabricate the content the SPA hides.
What the operator did¶
FIX (partial) — the static, JS-independent agent layer¶
These artifacts are served as plain text / static markup and work without JavaScript execution, so they reach non-rendering crawlers directly. Every value below traces to the confirmed <head> facts; nothing is invented.
Static Organization JSON-LD (server-rendered into <head>, confirmed fields only — the omissions are deliberate, not placeholders):
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Lumen Scheduling",
"url": "https://www.lumenscheduling.com",
"description": "Appointment-booking software for independent health and wellness clinics, with automated reminders that cut no-shows."
}
No
logo,sameAs, orcontactPointis included — those values are JS-gated and unread. Perfix-templates/jsonld-organization.md, I omit any property whose value is not confirmed rather than invent one. They are escalated below.
/llms.txt (serve at root as text/plain) — built from confirmed facts only:
# Lumen Scheduling
> Lumen Scheduling is appointment-booking software for independent health and
> wellness clinics, with automated reminders that cut no-shows.
> Web: https://www.lumenscheduling.com
## Note for AI agents
This site is a JavaScript single-page application; its rendered content is not
present in the served HTML. The summary above is the confirmed, machine-readable
description. A full llms-full.txt will follow once the underlying facts are
published statically.
Plus the top-of-<body> discovery link so agents reach /llms.txt first.
I am not drafting
/llms-full.txtyet: its real content (features, pricing, profiles) is JS-gated and unread. Generating it from guesses would be a fabricated fix. It is escalated below.
ESCALATE 1 — render content for non-JS agents (the structural fix)¶
Dimension: Citability + Content Freshness + Structured Data (Dimensions 2, 4, 1)
Specific blocker: The served HTML body is an empty #root shell; all content is painted client-side by main.bundle.js. AI crawlers do not execute JavaScript, so they receive no content. This is an engineering change to how the page is served — not a text artifact I can produce.
What an agent observes instead: A title, one description sentence, and an empty body — for every URL on the site. The product is invisible to the systems meant to cite it.
Exact action that unblocks it: Serve the content in the initial HTML response via server-side rendering or prerendering — Next.js/Remix SSR, a prerender service (e.g. prerender.io) for bot user-agents, or a static prerender of key routes. Verify by running curl (or view-source:) on a key URL and confirming the product copy is present before any JS runs. Once content is server-rendered, re-run this audit — Structured Data, Citability, Freshness, and Entity Authority can all then be scored and fixed normally.
ESCALATE 2 — supply the JS-gated facts to complete the agent layer¶
Dimension: Structured Data Coverage + Entity Authority (Dimensions 1, 5)
Specific blocker: /llms-full.txt, a populated sameAs, and logo/contactPoint on the Organization block all require facts (pricing, features, profile URLs) that live only in the JS-rendered DOM. I will not invent them.
What an agent observes instead: A minimal entity (name + description) with no external corroboration — capping authority near floor.
Exact action that unblocks it: Paste the rendered facts here, or the source data (product/pricing copy and the social/Crunchbase/LinkedIn profile URLs). I will then complete /llms-full.txt and add the confirmed sameAs and contact fields to the JSON-LD — from supplied facts, not guesses.
Deploy manifest¶
| # | Dimension | Artifact | Where it goes | How to verify |
|---|---|---|---|---|
| 1 | Structured Data | Static Organization JSON-LD (confirmed fields only) |
server-rendered into <head> |
curl the page; block present before any JS runs |
| 2 | Structured Data | /llms.txt (confirmed facts + SPA note) |
site root | fetch as text/plain → entity description present |
| 3 | Structured Data | Discovery link block | top of served <body> |
visible in curl output, before #root |
Projected score: 23/100 (Critical) → 29/100 (Critical). The projection deliberately stays inside the Critical band — the static agent layer is a floor-raiser, not the fix. Citability and Freshness claim no lift; their remedy is the escalated server-rendering work, and a projection that nudged them would be a fabricated gain.
Decision: score the page as served (Critical, 23/100 — confirmed, not provisional); FIX the JS-independent agent layer from confirmed <head> facts only; ESCALATE the server-rendering fix and the JS-gated facts. The page looks rich in a browser and is blank to agents — naming that gap honestly is the deliverable, and inventing the hidden content would defeat it.
Why this run matters¶
This run demonstrates the Operator's distinction between confirmed-absent and unverifiable gaps, and its refusal to fabricate content that is hidden behind JavaScript. Lumen Scheduling's served HTML body is genuinely empty (confirmed from raw bytes), while its <head> carries only title, description, and OG tags. The Operator correctly identifies this as a confirmed Critical score of 23/100 — not provisional like Example 3's stripped-fetch scenario.
The Operator produces the minimal JS-independent agent layer from confirmed facts: a basic Organization JSON-LD with name, URL, and description, plus /llms.txt that acknowledges the SPA limitation. Crucially, it refuses to generate /llms-full.txt or invent product/pricing data that lives only in the JS-rendered DOM. This discipline prevents fabricated fixes.
The Operator then escalates two real blockers: server-side rendering (an engineering change the owner must make) and supplying the JS-gated facts for completion of the agent layer. This honest accounting — distinguishing producible artifacts from necessary engineering work and hidden data — is what makes the Operator reliable. The run proves that when a page looks rich in a browser but is blank to agents, the Operator names that gap honestly rather than fabricating fixes it cannot deliver.