Development
Prerequisites
- Ruby 3.x with Bundler
- Python 3.11+ for maintenance scripts
- Optional: a virtual environment for Python dependencies in
scripts/requirements.txt
Local Setup
bundle install
bundle exec jekyll serve --livereload
Jekyll will print the local URL, usually http://127.0.0.1:4000.
Core Validation Commands
Run these before opening a PR:
bundle exec jekyll build
bundle exec htmlproofer ./_site --disable-external --ignore-empty-alt --ignore-urls "/localhost/,/127.0.0.1/" --enforce-https
ruby scripts/validate_content.rb
CI Parity
The CI workflow in .github/workflows/code_quality.yml runs the same three checks:
bundle exec jekyll buildbundle exec htmlproofer ./_site --disable-external --ignore-empty-alt --ignore-urls "/localhost/,/127.0.0.1/" --enforce-httpsruby scripts/validate_content.rb
If CI fails, reproduce locally with the same commands first.
Content Update Workflow
Blog posts
- Create a dated Markdown file in
_posts/. - Add the required front matter defined in
docs/content-model.md. - Add or generate an OG image if needed.
- If the post should participate in translation, run the translation script after the English source is stable.
- Run the validation commands.
Books
- Add or update the collection entry in
_books/. - Use the required front matter defined in
docs/content-model.md. - Run the validation commands.
Data-driven pages
- Homepage and work page content comes from
_data/about.yaml. - Stats and homepage view counts come from
_data/view_count.json. - Translation UI labels come from
_data/i18n.yml.
Whenever _data/ changes, rebuild the site and verify the affected page manually.
Python Maintenance Scripts
Install dependencies only when needed:
python3 -m venv .venv
source .venv/bin/activate
pip install -r scripts/requirements.txt
Analytics refresh
scripts/fetch_analytics.py writes _data/view_count.json. Run it only when you intend to refresh tracked analytics data and have the required environment variables.
Required environment variables:
GA_PROPERTY_IDGA_CREDENTIALS
Translation generation
scripts/translate_posts.py writes JSON files under assets/translations/.
Recommended safety workflow:
- Run with
--dry-runfirst. - Limit scope with
--postor--langwhile iterating. - Review generated JSON before committing.
OG image generation
scripts/generate_og_images.py writes PNGs into assets/images/.
Use --post or --force deliberately so you do not regenerate unrelated assets by accident.
Page Asset Conventions
Top-level pages can load page-specific assets using front matter arrays:
page_stylesheets:
- /assets/css/components/content-cards.css
- /assets/css/pages/search.css
page_scripts:
- /assets/js/components/discovery.js
- /assets/js/pages/search.js
Use these only for page-owned behavior and styles. Shared global behavior still belongs in layouts, includes, css/main.css, or shared assets under assets/css/components and assets/js/components.
Guardrails
- Do not add inline
<style>blocks or inline page-owned<script>blocks back toindex.html,blog.md,books.md,work.md, orstats.md. - Do not introduce a JS bundler or CSS preprocessor as part of routine page changes.
- Keep shared top-level page UI inside
_includes/components/once a repeated pattern exists in more than one page.