Skip to content

Link previews for the homepage and every reading - #16

Merged
EliezerIsrael merged 2 commits into
mainfrom
claude/share-previews
Oct 12, 2026
Merged

EliezerIsrael merged 2 commits into
mainfrom
claude/share-previews

Conversation

@EliezerIsrael

Copy link
Copy Markdown
Member

Summary

Link-preview crawlers (Slack, iMessage, WhatsApp, social sites) read <meta> tags from the HTML they fetch and don't run JavaScript, so every index.html?sheet=… link previewed as the same page. Now:

  • Each reading has a share page at read/<slug>/. It's a generated copy of index.html with <base href="../../">, data-sheet="<slug>" on <html> (the engine reads it when there's no ?sheet=; a one-line change), and its own Open Graph/Twitter tags. Deep links work: read/bereshit/?path=creation/cr-1963. Branch clicks and Back keep the /read/… address.
  • Preview images (1200×630, about 50 KB each) in assets/previews/. Each shows the title, author and description beside the reading's opening text with its first highlight lit, like the engine does. The homepage image uses the hero poster.
  • The homepage links to read/<slug>/, so copied URLs carry the right preview. ?sheet= links keep working but preview as the homepage.
  • Retired readings get share pages too, since old links to them still circulate.

Keeping it current

python3 scripts/build_previews.py regenerates the pages and tags; --images <slug> re-renders an image. Re-run it after any index.html change, including ?v= bumps. tests/test_data.py fails while generated pages are stale (verified: a version bump trips it). CLAUDE.md, the conversion guide and both sheet-building skills now include the step.

Tests

  • test_share_pages_are_current: the generated pages match the generator, and every image exists
  • test_share_page_runs_the_reader: every read/<slug>/ loads its reading with no 404s or console errors
  • The homepage test accepts read/<slug>/ links

Built on #15 (favicon). Once that merges, this diff shows only the preview work.

Test plan

  • tests/test_data.py passes; homepage and share-page render tests pass
  • Checked by hand: read/bereshit/?path=creation/cr-1963 opens the 1963 leaf; a branch click keeps /read/bereshit/; Back works; tags are absolute URLs
  • After deploy: paste a read/… link into Slack or opengraph.xyz to confirm the image renders

🤖 Generated with Claude Code

EliezerIsrael and others added 2 commits October 12, 2026 17:43
A brown tile with lines of text, right-aligned like Hebrew: one word lit, the
rest dimmed, the engine's own highlight. SVG for modern browsers, a 32px PNG
fallback, and a full-bleed 180px apple-touch-icon (iOS rounds the corners
itself and would fill transparent ones with black).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Link-preview crawlers read <meta> from the HTML they fetch and don't run JS,
so index.html?sheet=... could only ever preview as one page. Each reading now
has a generated share page at read/<slug>/: a copy of index.html with
<base href="../../">, data-sheet="<slug>" (the engine reads it when there's no
?sheet=), and its own Open Graph/Twitter tags and 1200x630 image. The
homepage's tags live in a generated block in index.html.

- scripts/build_previews.py writes the pages and tags; --images re-renders
  assets/previews/*.jpg: the title, author and description beside the
  reading's opening text with its first highlight lit. The homepage image
  uses the hero poster.
- The homepage links to read/<slug>/ so copied URLs carry the right preview.
  ?sheet= links keep working.
- test_data: generated pages must match the generator (a ?v= bump without
  re-running fails) and every image must exist. test_render: every share page
  loads its reading with no 404s or console errors.
- CLAUDE.md, conversion guide and both sheet-building skills say to run it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gitvelocity-reviewer

Copy link
Copy Markdown

📊 Code Quality Score: 49/100

S 14 + A 9 + I 8 + R 7 + Q 9 + P 2 = 49 base, x 1.0 ESF (902 effective lines, 20 files) = 49

Category Score Factors
🔭 Scope 14/20 20 files: the new scripts/build_previews.py, 12 generated read/<slug>/index.html pages, 12 JPGs in assets/previews/, favicon assets, index.html, js/engine.js, js/home.js, two tests, CLAUDE.md and two SKILL.md files. The homepage card and demo links change URL form for every sheet.
🏗️ Architecture 9/20 Introduces a build step that copies index.html into one page per slug, using <base href="../../"> and a data-sheet attribute that engine.js reads when ?sheet= is absent. Generated output is committed and guarded by test_share_pages_are_current. No new runtime dependency; Playwright is imported lazily and only for --images.
⚙️ Implementation 8/20 with_share swaps the block between share:start and share:end markers, and render_sheet_page rewrites <html>, <head> and <title> by string replace. opening_text picks the first plain-text panel, its language and the first highlighted phrases, and mark wraps them in escaped <mark> tags. render_images drives Chromium over HTML card templates at 1200x630.
⚠️ Risk 7/20 Every homepage card now links to read/<slug>/, so a missing generated page or a relative-URL problem under <base> breaks sheet loading for visitors. ?sheet= links still work. sheet_description joins the description and the author tail with no punctuation, which is visible in 7 live og:description values.
✅ Quality 9/15 test_share_pages_are_current compares every generated page and checks each preview JPG exists. test_share_page_runs_the_reader loads read/<slug>/ for each slug on desktop and checks that sections render. test_index_page_lists_every_sheet is updated for the new hrefs. build_previews.py has a module docstring and CLAUDE.md documents the workflow. No test covers opening_text, mark, card_html or render_images, and none follows a branch click on a share page.
🔒 Perf / Security 2/5 esc runs html.escape(quote=True) on every value written into meta attributes and card HTML. The preview images are 1200x630 JPGs at quality 88, served as static files. No performance measurement accompanies the change.

Was this score accurate? 👍 Yes · 👎 No

How this was scored →

Scored by GitVelocity · How are scores calculated?

@EliezerIsrael
EliezerIsrael merged commit 99c2f01 into main Oct 12, 2026
1 check passed
@gitvelocity-reviewer

Copy link
Copy Markdown

📊 Code Quality Score: 54/100

Base Score 54 (S14 + A9 + I11 + R7 + Q11 + P2) × ESF 1.0 (902 effective lines, Extra Large tier; 20 files, Large file tier, no bump) = 54

Category Score Factors
🔭 Scope 14/20 20 files change. One new script (scripts/build_previews.py), 11 generated read/<slug>/index.html pages, 11 preview JPEGs plus 3 icon files, and edits to index.html, js/engine.js and js/home.js. tests/test_data.py and tests/test_render.py are modified, and CLAUDE.md and two SKILL.md files are updated.
🏗️ Architecture 9/20 Introduces a build-time generation step. Each page is a copy of index.html with <base href="../../">, data-sheet on <html> and a replaced share block between the share:start and share:end markers. engine.js now falls back to document.documentElement.dataset.sheet when ?sheet= is absent. The homepage cards link to read/<slug>/ instead of ?sheet=.
⚙️ Implementation 11/20 build_previews.py rewrites the share block with with_share, builds the tags with share_block (HTML-escaped via esc), and substitutes the <title> with a regex. opening_text finds the first non-image, non-comparison panel, picks the language, and collects the words the first highlight step lights so mark can wrap them. card_html and home_html build the card markup and render_images screenshots it at 1200×630 through Playwright, optionally filtered by slug.
⚠️ Risk 7/20 Changes the URL form the homepage links to, so home.js hero and demo links now point at read/bereshit/?path=…. ?sheet= still resolves, so existing links keep working. If the generated pages go stale, share links show wrong tags, which is the case test_share_pages_are_current checks for. The change touches static GitHub Pages output only, with no server or data migration.
✅ Quality 11/15 test_share_pages_are_current compares every path from expected_pages() with what is on disk and checks that each assets/previews/<name>.jpg exists. test_share_page_runs_the_reader is parametrized over sheet_slugs() and loads read/<slug>/, waits for ScrollTrigger and asserts .cr-section renders. The test_index_page_lists_every_sheet extractor is updated for the new href shape. The module docstring and CLAUDE.md document the regeneration workflow. No test exercises render_images, and the description text format is not asserted.
🔒 Perf / Security 2/5 All tag values go through html.escape(quote=True) in esc before reaching attributes or the title, including titles and descriptions from data/index.json. Pages are static files, so crawlers get tags without running JavaScript. No benchmark or size limit accompanies the JPEG output.

Was this score accurate? 👍 Yes · 👎 No

How this was scored →

Scored by GitVelocity · How are scores calculated?

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