Skip to content

Latest commit

Β 

History

233 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🧠 Bits, Bytes and Neural Networks

In-depth AI/ML paper reviews, summaries, and tech guides β€” published as a blog.

A Jekyll static site, deployed to GitHub Pages.

Deploy Ruby Jekyll GitHub Pages

πŸ‡°πŸ‡· ν•œκ΅­μ–΄ README

Bits, Bytes and Neural Networks


Quick start

If you've never run a Jekyll site before, here's the whole loop.

1. Install Ruby 3.3+ and Bundler. Check what you have:

ruby --version     # need 3.3 or newer (see .ruby-version)
bundle --version   # ships with Ruby; if missing: gem install bundler

On macOS the system Ruby is old β€” use rbenv or asdf to install 3.3+. (The repo pins the version in .ruby-version, so a version manager will pick it up automatically.)

2. Install the project's gems (Jekyll, plugins, html-proofer):

bundle install

3. Run the dev server. It rebuilds on save and serves at http://localhost:4000:

bundle exec jekyll serve

Edit a file under _posts/, _sass/, or _includes/, save, and refresh β€” most changes appear immediately. (Changes to _config.yml need a server restart.)

4. Build for production (what CI does) when you want the final output in _site/:

bundle exec jekyll build

Why plain jekyll and not github-pages? This site uses custom Ruby plugins in _plugins/, which the sandboxed github-pages gem disallows. So both local builds and CI run Jekyll directly.


Project structure

_posts/            Posts β€” YYYY-MM-DD-slug.md (Korean; English twin is -en.md)
_layouts/          Page templates: default β†’ post / page
_includes/         Reusable fragments: head, header, footer, nav_links,
                   page_divider, category-posts, language_switcher,
                   related_posts
_sass/             Styles: _layout, _post, _tags, _syntax (Rouge code theme),
                   _dark (dark mode), base/*
                   ⚠ bourbon/ and neat/ are vendored frameworks β€” don't edit
_plugins/          reading_time.rb      (KO/EN-aware read time)
                   lazy_images.rb       (adds loading="lazy" to <img>)
                   post_description.rb  (fills page.description for posts)
                   related_posts.rb     (page.related, and the prev/next links)
                   scrollable_tables.rb (wraps wide tables so they scroll)
                   search_index.rb      (plain_text filter for search.json)
css/               main.scss (Sass entry point) Β· search.css (search page only)
js/                main.js (theme toggle, code-copy, TOC, menu, image zoom…)
                   search.js (drives the search box)
assets/images/     Shared cover images, reused across posts by topic
assets/<slug>/     Per-post figures, one folder per post
search.json        Full-text search index (consumed by simple-jekyll-search)
test/              minitest unit tests for the _plugins/ logic
script/            validate-site.sh (post-build discoverability checks)
sitemap-index.xml  Sitemap index β€” the URL to submit to Search Console
.github/workflows/ CI: tests β†’ build β†’ html-proofer β†’ validate-site; deploys on push to main

Top-level pages: index.html (home), plus paper-reviews.md, paper-summaries.md, tech-guides.md, insights.md (the four section pages), categories.html, tags.html, search.md, and about.md.


Writing a post

The easiest path is the /write-post skill, which runs the whole research β†’ draft β†’ proofread workflow. To add one by hand, create _posts/YYYY-MM-DD-slug.md starting with this front matter:

---
layout: post
title: "<Post Title>"
subtitle: "<one-line pitch>"       # optional β€” shown under the title in the header
date: YYYY-MM-DD HH:MM:SS
author: "<Author>"                 # the paper's org; omit for Insights/opinion posts
description: >-                    # optional β€” see below
  <search-snippet, ~150 chars>
categories: ["<Type>", "<Topic>"]
tags: ["<Tag-1>", "<Tag-2>"]
cover: /assets/images/<topic>.(jpg|png)
use_math: true                     # ONLY if the post has equations (loads MathJax)
lang: ko                           # optional β€” with translation_id below…
translation_id: <shared-slug>      # …links a Korean post to its -en twin
---

Don't repeat the title as an H1 in the body. The layout already renders it, so a leading # Title produces two <h1>s and leaks into the search snippet. Put a tagline in subtitle: instead.

description: β€” the search snippet

description: is what Google shows under the link, what social cards quote, and what the RSS <summary> carries. If you omit it, _plugins/post_description.rb derives one from the post's first real prose paragraph, which is usually good enough. Write it by hand when the first paragraph opens on a pull quote or a disclosure note β€” that is, on most Insights posts.

Descriptions must be unique across the site; CI fails the build if two pages share one.

Categories drive the URL

Categories are two levels:

  • categories[0] β€” the type: Paper Reviews, Paper Summaries, Tech Guides, or Insights. This decides which nav tab the post appears under. A type with no posts yet keeps its tab and renders an empty-state line: a missing tab reads as a section that was removed, not one still filling up.
  • categories[1] β€” the topic: Language-Models, Multimodal-Learning, Finetuning, Retrieval-Augmented-Generation, Agentic-AI, … (add new ones freely).

Jekyll combines the two with the date to build the output path:

categories: ["Paper Reviews", "Language-Models"] + date: 2025-01-23
        ↓
_site/paper reviews/language-models/2025/01/23/<slug>.html

So changing the categories or date of a published post changes its URL, which breaks inbound links and search results. Set them once and leave them.

Tags: one topic tag on top of the specific ones

Tags are free-form and hyphenated, and a tag phrased as one paper's contribution (Fine-Grained-Expert-Segmentation) can only ever apply to that paper. Those make a precise index and connect nothing, and _plugins/related_posts.rb requires a shared tag β€” so a post tagged only that way ships with no "Related reading" block at all.

So also give each post at least one tag from the controlled topic layer:

Agentic-AI  Alignment  DeepSeek  Knowledge-Graph  Llama
Mixture-of-Experts  Multimodal-Models  Reasoning-Models
Retrieval-Augmented-Generation

Keep the specific tags β€” they say something the topic tag does not, and dropping them would move live /tags/ anchors. Add the topic tag, don't swap for it.

Before coining a new topic tag, check that nothing above already covers it. Three names for one idea (Agentic-Architecture, Agentic-Patterns, Agentic-Infrastructure) leave every post holding a tag no other post shares, which is the same as having no topic tag at all.

Math: always use $$…$$

Write $$…$$ for both inline and display math, and set use_math: true.

Never use single $…$. kramdown doesn't treat single $ as math, so its Markdown pass turns _/* inside the span into <em>/<strong> before MathJax runs β€” e.g. $a*b*c$ becomes $a<em>b</em>c$ and renders broken. With $$, kramdown emits verbatim \(…\) and leaves the contents alone. (Prose dollar signs like $10M are fine β€” they're not math.)

Validate before pushing

These are the four gates CI runs, in the same order. Build to a throwaway directory rather than _site/, for the reason in the note below:

ruby test/run_all.rb                                        # plugin logic still correct?
bundle exec jekyll build --strict-front-matter \
  --destination /tmp/site-verify                            # does it build clean?
bundle exec htmlproofer /tmp/site-verify --disable-external \
  --allow-hash-href --no-enforce-https                      # broken links, images, anchors?
script/validate-site.sh /tmp/site-verify                    # sitemap, feed, metadata, headings

If a check reports something impossible, look for a running jekyll serve first. It watches the tree and rewrites _site/ behind you, it overrides site.url with http://localhost:4000 (so every sitemap URL looks wrong), and it holds the _config.yml it started with β€” so exclude entries added since then don't apply. Building elsewhere sidesteps all three:

ps aux | grep '[j]ekyll serve'

test/ unit-tests the pure logic in _plugins/ β€” one file per plugin. Plain ruby, not bundle exec: each plugin guards its Jekyll/Liquid registration behind defined? so the logic loads standalone, and minitest ships with Ruby. Anything you change in _plugins/ changes every page on the site, so add a case before changing behaviour.


Deployment

.github/workflows/jekyll.yml runs on pull requests to main as well as pushes to it, so the gates below block a bad merge rather than merely reporting one after the fact. Steps 1–4 run on both events; step 5 is skipped for pull requests. In order, it:

  1. runs ruby test/run_all.rb (the _plugins/ unit tests),
  2. builds the site with JEKYLL_ENV=production,
  3. runs html-proofer over _site/ (internal links, images, anchors),
  4. runs script/validate-site.sh β€” sitemap/feed parse at byte 0, every sitemap URL under the configured url, a pinned build timezone, robots.txt not blocking, at least one rendered page, exactly one h1 per page, no heading-level skips, a description and a canonical on every page, every description longer than its own title, no duplicate description or title, and no authoring sources published β€” and
  5. deploys to GitHub Pages.

A failure is almost always step 3 or 4; the Actions log names the exact link, image, or page. There is no manual deploy step.

⚠ Don't add google*.html / naver*.html to _config.yml's exclude. They're Search Console / Naver ownership-verification tokens that must ship to the site root. Excluding them silently breaks ownership verification.

If Search Console says it can't fetch the sitemap

Check the file first β€” it is usually fine:

curl -sI  https://bits-bytes-nn.github.io/sitemap.xml   # expect 200, application/xml
curl -sS  https://bits-bytes-nn.github.io/sitemap.xml -o /tmp/s.xml && \
  ruby -rrexml/document -e 'REXML::Document.new(File.read("/tmp/s.xml")); puts "well-formed"'
curl -sS  https://bits-bytes-nn.github.io/robots.txt

If those pass, the failure is a cached Search Console verdict, not the site. Search Console keys a sitemap by URL and keeps the first result it recorded, so re-submitting the same path reuses the stale entry. Remove the entry and submit sitemap-index.xml instead β€” a URL it has not seen before β€” then use URL Inspection β†’ Request Indexing on a couple of posts to prompt a crawl.


License

MIT β€” see LICENSE. Built on the Centrarium Jekyll theme.

About

In-depth AI/ML paper reviews, tech guides, and insights on the latest research and engineering.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages