Skip to content

feat(demo): a link preview card for the hosted demo - #178

Merged
nadeem4 merged 1 commit into
mainfrom
feat/demo-link-preview-card
Sep 23, 2026
Merged

nadeem4 merged 1 commit into
mainfrom
feat/demo-link-preview-card

Conversation

@nadeem4

@nadeem4 nadeem4 commented Sep 23, 2026

Copy link
Copy Markdown
Owner

Pasting either link showed nothing today. The app's own page
(https://nadeem4nk-nl2sql-demo.hf.space) had no Open Graph tags, and the
Space's page had a short_description but no thumbnail, so its card had no
image. This gives both a card.

The card

The link preview card

1200x630, in the playground's own language: mist paper, graphite ink, one
verdigris accent, Schibsted Grotesk and Fragment Mono, and the playground's own
mark. The words carry it -- the product name, the line "Ask a database in
plain English, the model plans, the code writes the SQL"
at 52px over two
lines, and the quieter "Bring your own API key, Sample data, Open source".
Under a rule, one restrained cue: a fragment of the plan the model writes
turning into the SQL the code writes from it. No screenshot, nothing small
enough to disappear in a thumbnail.

It is generated, not drawn by hand into a binary:
scripts/social_card.html is the source, and
python scripts/render_social_card.py inlines the playground's bundled fonts
and shoots it with headless Chrome. The script writes the same 61 KB to both
places that need it -- docs/assets/social-card.png, which the Space's
thumbnail reads over raw GitHub, and
packages/nl2sql/src/nl2sql/cli/demo/playground/assets/social-card.png, which
the app serves and the wheel carries -- and a test holds the two
byte-identical, so a regeneration cannot go out half-changed.

The app's tags

preview.py adds them to the built page's <head> as each request goes out,
rather than baking them into the bundle or having React set them:

  • a crawler runs no JavaScript, so a tag React adds on mount is a tag nobody
    sees;
  • og:image and og:url have to be absolute, and no single absolute URL is
    right -- the same page is the Space, a container, and
    http://127.0.0.1:8000.

So the host comes off the request: X-Forwarded-Proto and X-Forwarded-Host
when a proxy set them, which is what the Space does; the Host header
otherwise; and the URL the app itself saw if neither is a host, which also
means a forged header cannot write a URL into the page. Served over HTTP behind
the Space's headers:

<meta property="og:url" content="https://nadeem4nk-nl2sql-demo.hf.space/" />
<meta property="og:image" content="https://nadeem4nk-nl2sql-demo.hf.space/social-card.png" />

The full set is description, og:type, og:site_name, og:title,
og:description, og:url, og:image, og:image:width, og:image:height,
og:image:alt, twitter:card=summary_large_image, twitter:title,
twitter:description and twitter:image. The page's old static
<meta name="description"> is gone from web/playground/index.html, so there
is exactly one and it says what the card says. The card is served at
GET /social-card.png as image/png.

Checked, and not checked

  • pytest -m "not integration" -q: 1589 passed, 5 skipped (1578 before;
    the 5 skips are langchain_anthropic missing locally). The hosted-mode tests
    that prove no key reaches disk or logs stay green -- the route-enumeration
    test in test_playground_settings.py lists the new route, so the new
    response is checked for the key too.
  • npm test in web/playground: 106 passed. The bundle was rebuilt from
    source with npm ci && npm run build, never hand-edited.
  • mkdocs build --strict: clean.
  • Over a real socket, not only through TestClient: GET / returns
    text/html; charset=utf-8 with the tags in the head, the proxied request
    returns the hf.space URLs above, and GET /social-card.png returns
    image/png, 62327 bytes, PNG magic intact.
  • Not checked: a card validator needs a public URL, so nobody can run one
    against a branch. Run X's validator or https://opengraph.dev against the
    Space once this is deployed.

docs/deployment/hosted-demo.md gains a The link preview section covering
both cards, how the absolute URL is derived, and how to regenerate the image.

Pasting either link showed nothing: the app's own page had no Open Graph
tags, and the Space's card had no thumbnail.

- `scripts/social_card.html` is a 1200x630 card in the playground's own
  language -- mist paper, one verdigris accent, Schibsted Grotesk and
  Fragment Mono -- carried by the words, with a fragment of a plan turning
  into SQL underneath. `python scripts/render_social_card.py` renders it
  with headless Chrome into the two places that need it, and a test holds
  those two byte-identical.
- The playground adds the Open Graph and Twitter tags to the built page's
  head as each request goes out (`preview.py`) rather than baking them in:
  a crawler runs no JavaScript, and `og:image` and `og:url` have to be
  absolute, so the host comes off the request -- the forwarded headers when
  a proxy set them, which is what the Space does. The card is served at
  `/social-card.png` and ships in the wheel.
- The Space's front-matter gains `thumbnail`, read over raw GitHub so the
  card works before the Space has built.
@nadeem4
nadeem4 merged commit 927baf1 into main Sep 23, 2026
6 checks passed
@nadeem4
nadeem4 deleted the feat/demo-link-preview-card branch September 23, 2026 21:34
@github-actions github-actions Bot mentioned this pull request Sep 23, 2026
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