Skip to content

Fix broken API samples, bump DuckDB, and harden the page - #3

Open
melonamin wants to merge 3 commits into
mainfrom
fix/api-samples-and-hardening
Open

Fix broken API samples, bump DuckDB, and harden the page#3
melonamin wants to merge 3 commits into
mainfrom
fix/api-samples-and-hardening

Conversation

@melonamin

Copy link
Copy Markdown
Member

The code samples this page advertises used an API the widget does not have. Anyone copy-pasting the flagship example got a widget that silently ignored their setup SQL. This fixes that, bumps DuckDB, and adds the guardrails that stop it recurring.

The correctness bugs

Verified against the published pondpilot-widget@1.4.0 bundle and upstream docs/API.md, then confirmed live in a browser:

index.html said The widget actually has
initQuery: '…' initQueries: ['…'] — array, plural
resetQuery: '…' resetQueries: ['…'] — array, plural
widget.addEventListener(…) The event bubbles; use document.addEventListener
const { rows, columns } = e.detail e.detail is { data, elapsed, widget }
window.createDuckDB?.() No such global exists anywhere in the bundle

The wrong option keys were dropped silently by the config normalizer, and the optional chaining on createDuckDB?.() quietly made duckdbInstance undefined. Both failed in the worst possible way: without an error.

Running the results event live confirmed the detail keys are exactly ["data","elapsed","widget"].

DuckDB

Bumped @duckdb/duckdb-wasm from 1.31.1-dev1.0 to 1.33.1-dev57.0 (npm latest), verified in a browser before and after.

That package's version line is not the engine version. The page claimed "DuckDB 1.31.1", a release that has never existed. SELECT version() reports v1.4.1 on the old bundle and v1.5.4 on the new one, so the cards now say 1.5.4. The explicit duckdbVersion override was also identical to the widget's own default, which pinned the page to a stale runtime for no benefit.

The CSP nearly shipped a dead hero

Adding a CSP broke the hero demo: RuntimeError: table index is out of bounds. A wasm trap, not a network error, and no securitypolicyviolation event fires because worker violations don't propagate to the document.

Bisected across five variants — the plain SELECT in the theme cards worked, the remote-parquet query didn't, and connect-src * fixed it. Cause: DuckDB auto-loads its httpfs extension from extensions.duckdb.org the moment a query reads an https:// path. That host is now allowlisted, with a comment, because the symptom points nowhere near the cause.

Also in here

  • SRI + CSP. integrity on all three CDN resources. Inline scripts moved to assets/js/landing.js so script-src needs no 'unsafe-inline'. (Feature card six advertised the widget as "CSP-ready" while the page hosting the claim had neither.)
  • Version single-sourcing. 1.4.0 appeared 11 times. Every string now derives from the pinned <script src> — the one tag that can't be templated, since it carries the SRI hash. Literals stay in the markup so the page reads correctly without JS; CI asserts they agree.
  • LICENSE. The footer claimed MIT; the repo had no LICENSE file.
  • Social + SEO. Canonical, Open Graph, Twitter, JSON-LD, robots.txt, sitemap.xml, and a 1200×630 og.png rendered from the brand assets through headless Chromium so it uses the real Plus Jakarta Sans.
  • Accessibility. Font Awesome (a 2021 beta, render-blocking, for five icons) replaced with an inline SVG sprite. Skip link, aria-hidden on decorative glyphs, real nav list, prefers-reduced-motion guard.
  • Dark-only. The widget keyed its theme off prefers-color-scheme while the page has no light mode, so light-OS visitors got a light widget inside a dark hero. Pinned to dark.
  • CSS cleanup. Dropped 13 unused custom properties, 5 dead classes, three duplicated selectors, and a six-column grid whose every child spanned all six.
  • Corrected the "~22KB" claim. The bundle is 44KB raw, 13KB gzipped. Neither is 22KB.

CI

Four jobs on push, PR, and weekly — version drift and link rot happen without anyone touching the repo.

Job Guards
html-validate Structure and accessibility
linkinator Every outbound link still resolves
check-versions.sh Version literals agree with the pin; pin is still npm latest
check-sri.sh Recorded integrity hashes still match what the CDNs serve

All npx- or shell-based, so the repo stays build-free with no package.json. Both scripts were tested against injected drift, not just a clean tree: a stale version literal, a pin behind npm, and a corrupted hash each exit 1.

The link check immediately found a pre-existing 404 — the nav "Docs" link pointed at pondpilot.io/docs/widget. Repointed at the GitHub docs tree, matching the footer. This is the one change outside the reviewed scope, made because CI would otherwise be red on merge.

Verification

Driven in headless Chromium against the real page:

  • All 4 widgets execute — hero returns 3 rows from the remote parquet, theme cards 1 row each
  • 0 CSP violations, 0 console output, 0 page errors
  • Custom themes still apply per element (midnight-neon, sunset-sorbet, retro-terminal)
  • html-validate, linkinator, version drift, and SRI all green

Known gaps

  • No runtime test. By design (zero-build was the explicit call), so nothing in CI would catch a future widget release breaking the embed. The checks guard versions, links, hashes, and markup — not behavior.
  • og.png is 410KB. Palette reduction made it larger (dithering the gradient adds noise) and no pngquant/oxipng was available. Worth a proper optimizer pass.

🤖 Generated with Claude Code

melonamin and others added 3 commits July 10, 2026 08:53
The advertised code samples used an API the widget does not have. Verified
against the published pondpilot-widget@1.4.0 bundle and upstream docs/API.md:

  initQuery / resetQuery      -> initQueries / resetQueries (arrays)
  widget.addEventListener     -> document.addEventListener (the event bubbles)
  e.detail.{rows, columns}    -> e.detail.{data, elapsed}
  window.createDuckDB()       -> removed; no such global exists

The wrong option keys were dropped silently by the config normalizer, so a
reader copy-pasting the flagship example got a widget that ignored their setup
SQL and no error to explain it.

Bump @duckdb/duckdb-wasm 1.31.1-dev1.0 -> 1.33.1-dev57.0 (npm latest). That
package's version line is not the engine version: the page claimed "DuckDB
1.31.1", a release that has never existed. `SELECT version()` reports v1.5.4 on
the new bundle, so the cards now say that.

Add a CSP. connect-src must allow extensions.duckdb.org, because reading an
https:// path makes DuckDB auto-load its httpfs extension from there; blocking
it fails the hero query with a wasm trap rather than a network error. Add SRI
hashes to the CDN scripts, and move the inline scripts into assets/js/landing.js
so script-src needs no 'unsafe-inline'.

Derive every version string from the pinned <script src>, which carries the SRI
hash and is therefore the one place a bump has to happen. Pin the widget theme
to dark, since the page has no light mode.

Replace Font Awesome (a 2021 beta, render-blocking, for five icons) with an
inline SVG sprite. Add a skip link, aria-hidden on decorative glyphs, a real
nav list, and a prefers-reduced-motion guard.

Drop 13 unused custom properties, 5 dead classes, three duplicated selectors,
and a six-column grid whose every child spanned all six.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The footer claimed MIT but the repo had no LICENSE. Add it, matching upstream.

Add robots.txt and sitemap.xml to go with the canonical, Open Graph, Twitter,
and JSON-LD metadata the page now carries.

Add four CI jobs, run on push, PR, and weekly (version drift and link rot happen
without anyone touching the repo):

  html-validate       structure and accessibility
  linkinator          every outbound link still resolves
  check-versions.sh   version literals agree with the pin; pin is still npm latest
  check-sri.sh        recorded integrity hashes still match what the CDNs serve

All four are npx- or shell-based, so the repo stays build-free and has no
package.json. Both scripts were tested to fail on injected drift, not just to
pass on a clean tree.

Building the link check surfaced a pre-existing 404: the nav "Docs" link pointed
at pondpilot.io/docs/widget. That is repointed at the GitHub docs tree in the
preceding commit, which is what lets this job pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address five review findings, each verified with a negative test:

- Guard the DuckDB engine version. check-versions.sh enforced widget version
  literals but not the data-duckdb-version literals, so DUCKDB_ENGINE_VERSION in
  landing.js and the HTML could silently drift — the same hardcoded-version bug
  the page fixes elsewhere. Now asserts the literals equal the const.

- Make duckdb-wasm freshness advisory. Its npm `latest` is a -dev line that
  republishes often, and a bump also needs a manual browser re-check of the
  engine version, so a hard gate would turn the weekly build red on upstream's
  cadence rather than on a real problem. Warns instead of failing; the widget
  freshness gate stays hard.

- Document the stamp-before-Prism ordering in landing.js: rewriting the sample's
  textContent must precede tokenisation, which holds only because both scripts
  are deferred and this one is first.

- check-sri.sh now sends a User-Agent and retries transient fetch failures so a
  CDN hiccup does not fail the nightly run.

- Add <lastmod> to sitemap.xml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@melonamin

Copy link
Copy Markdown
Member Author

Addressed all five review findings in 43e3d70. Each fix has a negative test proving it fails on the condition it guards, not just that it passes clean.

# Finding Fix Verified
1 Engine-version literal unguarded check-versions.sh now asserts every data-duckdb-version literal equals DUCKDB_ENGINE_VERSION in landing.js Diverging either the HTML literal or the const → exit 1 with a clear message
2 Freshness pinned to a -dev dist-tag → recurring red builds duckdb-wasm drift is now advisory (warns, exit 0); the widget freshness gate stays a hard failure Stale wasm pin → WARNING + exit 0; stale widget pin → still exit 1
3 stampVersions() ordering dependency undocumented Added a comment explaining the stamp-must-precede-Prism invariant and why it holds (both deferred, this one first) node --check clean; comment-only change
4 check-sri.sh no UA / no retry Sends a User-Agent and retries transient fetch failures with backoff before reporting a finding Clean tree still verifies all 3 hashes; corrupt hash → exit 1
5 sitemap.xml missing <lastmod> Added Well-formed XML confirmed

Not changed, with reasoning:

  • No runtime test. Still the accepted zero-build tradeoff — flagged in the PR body. Finding [UI]: Add favicon, CSS Fixes #1's fix narrows the gap: engine drift is now caught statically even though behavior isn't exercised.
  • .htmlvalidate.json disabling no-raw-characters. Kept, to match the repo's existing formatting; the reviewer marked it acceptable.
  • og.png at 410KB. No lossless optimizer available in this environment; palette reduction made it larger. Left for a proper optimizer pass.

README's CI table and DuckDB-bump instructions updated to match the new engine-literal guard and advisory behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant