Skip to content

Commit 4e5cab1

Browse files
committed
Add discoverability, dark mode, search, social cards, and learner analytics
Distribution and product-polish pass: - /sitemap.xml route + robots.txt Sitemap directive; JSON-LD structured data (WebSite on home, TechArticle/LearningResource on examples), enforced by the SEO linter. - Dark mode via prefers-color-scheme: inverted warm palette, dual-theme Shiki, dark CodeMirror highlight style, marginalia figures on a light paper chip so the locked grammar stays untouched. Skip-to-content link on every page. - Client-side example search on the home page: build-step JSON index, fingerprinted search.js/search-index.json assets, "/" shortcut, Node ranking check wired into make verify. - Per-example social cards composed from each example's marginalia figure, rasterized with headless Chrome to public/og/<slug>.jpg and referenced via og:image/twitter:card (make social-cards). - Learner-behavior report aggregating exported Worker wide events into most-read pages, most-run examples (edited/error shares, execution percentiles), journey traffic, and missing-example 404s. - Journeys now cover all 109 examples: five previously unreferenced examples joined their natural sections with outcome-registry support updates; stale "gap placeholder" copy removed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012tBECRGRPmM2dj5b3FFJGo
1 parent 3aff70b commit 4e5cab1

142 files changed

Lines changed: 1407 additions & 27 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,6 @@ dist/
99

1010
python_modules/
1111
.venv-workers/
12+
13+
# Social-card HTML intermediates (rasterized output lives in public/og/)
14+
build/

CHANGELOG.md

Lines changed: 15 additions & 0 deletions

Makefile

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,32 @@
1-
.PHONY: test embed-examples build check-generated fingerprint browser-layout-test seo-cache-lint verify-examples check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported score-example-criteria check-quality-scores check-no-figure-rationales check-journey-outcomes quality-checks rubric-audit format-examples verify-python-version verify smoke-deployment dev deploy lint
1+
.PHONY: test embed-examples build-search-index build check-generated fingerprint browser-layout-test search-ranking-test social-cards seo-cache-lint verify-examples check-registry-integrity check-confusable-pairs check-broad-surface-tours check-footgun-coverage check-notes-supported score-example-criteria check-quality-scores check-no-figure-rationales check-journey-outcomes quality-checks rubric-audit format-examples verify-python-version verify smoke-deployment dev deploy lint
22

33
test:
44
uv run --python 3.13 python -m unittest discover -s tests -v
55

66
embed-examples:
77
scripts/embed_example_sources.py
88

9-
build: embed-examples fingerprint
9+
build-search-index: embed-examples
10+
uv run --python 3.13 scripts/build_search_index.py
11+
12+
build: embed-examples build-search-index fingerprint
1013

1114
check-generated: build
12-
git diff --exit-code src/example_sources_data.py src/asset_manifest.py public/_headers
15+
git diff --exit-code src/example_sources_data.py src/asset_manifest.py public/_headers public/search-index.json
1316

14-
fingerprint: embed-examples
17+
fingerprint: embed-examples build-search-index
1518
scripts/fingerprint_assets.py
1619

1720
browser-layout-test:
1821
scripts/check_browser_layout.mjs
1922

23+
search-ranking-test:
24+
scripts/check_search_ranking.mjs
25+
26+
social-cards:
27+
uv run --python 3.13 scripts/build_social_cards.py
28+
scripts/build_social_cards.mjs
29+
2030
seo-cache-lint:
2131
scripts/lint_seo_cache.py
2232

@@ -64,7 +74,7 @@ verify-python-version: build
6474
lint:
6575
uv run ruff check src tests scripts
6676

67-
verify: build test seo-cache-lint verify-examples quality-checks browser-layout-test lint check-generated
77+
verify: build test seo-cache-lint verify-examples quality-checks browser-layout-test search-ranking-test lint check-generated
6878

6979
dev:
7080
uv run pywrangler dev --port 9696

README.md

Lines changed: 16 additions & 1 deletion

docs/learner-analytics.md

Lines changed: 48 additions & 0 deletions

docs/quality-registries.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,7 @@ journey = "iteration"
316316
section = "See the protocol behind `for`."
317317
support = [
318318
"iterating-over-iterables",
319+
"iterator-vs-iterable",
319320
"iterators",
320321
"generators",
321322
]
@@ -434,12 +435,14 @@ support = [
434435
"inheritance-and-super",
435436
"dataclasses",
436437
"properties",
438+
"classmethods-and-staticmethods",
437439
"special-methods",
438440
"truth-and-size",
439441
"container-protocols",
440442
"callable-objects",
441443
"operator-overloading",
442444
"attribute-access",
445+
"bound-and-unbound-methods",
443446
"descriptors",
444447
"metaclasses",
445448
]
@@ -456,6 +459,7 @@ section = "Keep runtime and static analysis separate."
456459
support = [
457460
"type-hints",
458461
"protocols",
462+
"abstract-base-classes",
459463
"enums",
460464
"runtime-type-checks",
461465
]
@@ -473,6 +477,7 @@ support = [
473477
"union-and-optional-types",
474478
"type-aliases",
475479
"typed-dicts",
480+
"structured-data-shapes",
476481
"literal-and-final",
477482
"callable-types",
478483
]

public/_headers

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,17 @@
77
/editor.*.js
88
Cache-Control: public, max-age=31536000, immutable
99

10+
/search.*.js
11+
Cache-Control: public, max-age=31536000, immutable
12+
13+
/search-index.*.json
14+
Cache-Control: public, max-age=31536000, immutable
15+
1016
/favicon.svg
1117
Cache-Control: public, max-age=31536000, immutable
1218

19+
/og/*
20+
Cache-Control: public, max-age=86400
21+
1322
/prototyping/*
1423
Cache-Control: no-cache, must-revalidate
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { EditorState } from 'https://esm.sh/@codemirror/state@6.5.2';
22
import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
33
import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1';
44
import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
5+
import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
6+
7+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
8+
const highlightStyle = prefersDark ? oneDarkHighlightStyle : defaultHighlightStyle;
59

610
const textarea = document.getElementById('code-editor');
711
const form = document.querySelector('form.runner-editor');
@@ -14,7 +18,7 @@ if (textarea && form) {
1418
doc: textarea.value,
1519
extensions: [
1620
python(),
17-
syntaxHighlighting(defaultHighlightStyle),
21+
syntaxHighlighting(highlightStyle),
1822
lineNumbers(),
1923
EditorView.lineWrapping,
2024
EditorView.updateListener.of((update) => {

public/editor.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { EditorState } from 'https://esm.sh/@codemirror/state@6.5.2';
22
import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
33
import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1';
44
import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
5+
import { oneDarkHighlightStyle } from 'https://esm.sh/@codemirror/theme-one-dark@6.1.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
6+
7+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
8+
const highlightStyle = prefersDark ? oneDarkHighlightStyle : defaultHighlightStyle;
59

610
const textarea = document.getElementById('code-editor');
711
const form = document.querySelector('form.runner-editor');
@@ -14,7 +18,7 @@ if (textarea && form) {
1418
doc: textarea.value,
1519
extensions: [
1620
python(),
17-
syntaxHighlighting(defaultHighlightStyle),
21+
syntaxHighlighting(highlightStyle),
1822
lineNumbers(),
1923
EditorView.lineWrapping,
2024
EditorView.updateListener.of((update) => {
113 KB

0 commit comments

Comments
 (0)