Repository navigation
fix(pixel): show schema.org event and offer data from a page's markup in the shared reader - #6733
Open
Lightheartdevs wants to merge 1 commit into
Open
Lightheartdevs wants to merge 1 commit into
Lightheartdevs wants to merge 1 commit into
Conversation
… in the shared reader Event and shop pages often build their visible text with JavaScript, which the shared public-page reader does not run, while the served HTML already carries the event's date and venue or an offer's price and stock state as schema.org JSON-LD or microdata. structured-data.mjs reads that data from the HTML the reader already fetched, without running anything: JSON-LD is parsed as JSON (comments and the content of other script, style, noscript, template and textarea elements are skipped), and microdata comes from tag attributes and text. Only whitelisted fields are kept: - Event and its subtypes: name, start, end, status (cancelled, postponed, rescheduled, moved online; scheduled is not shown), location (place name and postal address), organizer, url; - Product: name, sku, gtin, and up to three offers; - Offer and AggregateOffer: price or low-high, currency, availability, condition, seller, priceValidUntil; - Organization: name, postal address, url, only with an address and never when it is credited as a publisher, author, brand and similar. Values are single-line plain text with entities decoded and markup, control, zero-width and bidi characters removed; URLs are http(s) only, without credentials or utm_ tags. At most eight events, products or offers and two organizations, 2,400 characters. On an item's own page (its url is the page), related items are left out. ISO times are written as "2026-10-02 20:00 UTC-0400", so a zone offset cannot read as a time of day. The reader puts the block, labelled as markup data and not visible text, before the visible text of an HTML page, after bot-challenge detection. It is part of the page text, so pixel_ods_web_extract returns it inside the same untrusted-content boundary and host citation verification checks the same text: an event whose date is only in the markup can be verified, and a cancelled one is not. The tool description is unchanged. Scanning is linear in the (already bounded) response and bounded in scripts, JSON characters, nodes, tags and output; hostile 1 MB inputs finish in under 25 ms locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Many event and shop pages build their visible text with JavaScript, which Pixel's page reader does not run. The HTML they serve often already contains the facts as schema.org data: JSON-LD scripts, or microdata attributes. This PR reads that data, without running anything, and shows a short, fixed set of fields at the top of the page text.
Honest scope first. On the fleet corpus this rarely supplies a fact that was missing. It does not touch the main causes of unread sources:
Those need the render work or model-side changes. See the replay below.
What changes
plugin/structured-data.mjs(new) is called from the shared reader,createPublicPageReaderinweb-extract.mjs. The reader change is one import and one line. The block is added:The block is part of
text, so every reader consumer sees the same thing:pixel_ods_web_extractreturns it inside the existing untrusted-content markers;pixel_ods_search_readgets it too (checked in a local composition, see below).The tool description is unchanged, so the prompt does not change.
A real block (The Met Philadelphia shows page, read from tower2). Main's reader gets 56 characters from this page, the title only:
Only these fields are read
Nothing else is read: no descriptions, reviews, ratings, images or performer bios. A test checks that a prompt-injection line in
descriptionnever appears.Enumerations are shown as fixed words ("in stock", "new", "cancelled"). Any other value is dropped.
ISO times are written as
2026-10-02 20:00 UTC-0400. The zone has no colon, so the citation verifier cannot read an offset such as-04:00as the time 4:00.Bounds and safety
JSON.parse. Comments are skipped, and so is the content of other script, style, noscript, template, textarea and iframe elements, so a<script type="application/ld+json">written inside a JavaScript string or an HTML comment is ignored. Microdata is read from tag attributes and text by a simplified tag walk.@contextor full schema.org type IRIs. For microdata it means a schema.orgitemtype.utm_tags removed, at most 300 characters.urlis the page), related items are left out, so an event page does not look like a listing.[, 15k nesteditemscope, unclosed quotes, stray end tags) each finish in under 25 ms locally. The test fails above 1 s.Evidence: replay on tower2
This is a live replay from tower2 (OpenClaw 2026.6.33 SDK, the guarded fetch, the commit's reader). It covers two sets of URLs:
The needed-fact rules are the scout's (
facts.mjs, per journey). For retail pages I also counted "549.99 USD" with a stock word as a USD price with stock state; it changed nothing.For each page read, the same response was also passed through main's reader offline. Main's text was identical to this branch's visible text on 171/171 HTML pages.
Set A has no gain. Of its 79 unread URLs, JSON-LD cannot reach any, because nothing is read:
The JavaScript-built pages that are read carry no schema.org fact:
This matches the scout's raw-HTML probe: JSON-LD had the fact on 0 of 25 JavaScript-built pages.
Where the block does appear (35 pages, almost all event pages), 27 already restate the needed fact in a compact form: date, time, venue address and the event's own URL. That makes answers easier to cite and host verification easier to pass. It is not a coverage gain.
Added latency is the block's own CPU time on the fetched body, measured on tower2 (median of 5 runs per page, 171 HTML pages):
A run before the final fixups gave the same counts: 0 in A, and Met Philly as the only new page in B.
Tests
tests/structured_data.test.mjs, 13 tests:@idreferences; status words; end dropped on the same day;utm_tags and a differently encoded query), and the listing cap with an "N more" line;contentattribute, nested items, link text as a name, li siblings, other vocabularies);pixel_ods_web_extractin overview and query mode (the block only inside the markers);node --test tests/*.test.mjs):8ed4d293a2583ed3d01f0158f2d)A test-name diff against main shows only the 13 new tests.
Coordination
git merge-treeis clean against feat(pixel): search and read public pages in one call (pixel_ods_search_read) #6698 (1f0158f2d) and fix(pixel): read PDF text in the shared page reader; binary bodies are never page reads #6724 (d7268e0a9).pixel_ods_web_extractbullet that both PRs edit.completion-assurance.mjs, independently of this PR.pixel_ods_search_read(urls mode, focus "date venue") earns a receipt: matchfacts, with the start time and venue inside the boundary. The same page without JSON-LD is "opened, nothing relevant found; not citable".Risks
pixel_ods_web_extractoverview mode shows 6,000 characters, so the block takes some of that space. Query mode searches the whole text.search_read:search_readstrips bare URLs from excerpts, so a block line ends inurl:there.Replay artifacts are on tower2 in
/mnt/bulk/claude-tests/reader-jsonld/:replay.jsonl,replay-run1.jsonl,blocks/,replay.mjs. That is a throwaway dir; noods-*container was touched.🤖 Generated with Claude Code