Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ updates:
update-types:
- minor
- patch
# The gems only run in CI (build, html-proofer), but html-proofer pulls in
# nokogiri, which gets security releases.
# The gems only run at build time, but nokogiri parses post bodies there and
# gets security releases.
- package-ecosystem: bundler
directory: /
schedule:
Expand Down
24 changes: 7 additions & 17 deletions .github/workflows/jekyll.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,19 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# No apt step. The packages it installed are already in the ubuntu-24.04
# image — a past run's log showed libyaml-dev/zlib1g-dev/libffi-dev
# "already the newest version" and only 2 incidental upgrades — and the
# -dev headers were only ever needed to compile Ruby itself, which
# ruby/setup-ruby avoids by fetching a prebuilt build. The native gems
# (sassc, eventmachine) need g++/make, which the image ships as well.
#
# What it did cost was `apt-get update` on every deploy, a network-bound
# step slower and less reliable than the rest of the pipeline put
# together. If this assumption is ever wrong the build job fails and
# `deploy` (needs: build) publishes nothing.
# No apt step: setup-ruby fetches a prebuilt Ruby, and the native gems
# (sassc, eventmachine, nokogiri) need only the g++/make the image ships.
# Should that stop holding, the build fails and nothing deploys.
- name: Setup Ruby
uses: ruby/setup-ruby@b3aaceaef7e0ee231b499e7b5f49c03fc1d32199 # v1.338.0
# The version comes from .ruby-version (3.3+: html-proofer's
# async→console dependency requires Ruby >= 3.3).
with:
bundler-cache: true
- name: Unit-test the Jekyll plugins
# Plain `ruby`, not `bundle exec`: the plugins guard their Jekyll/Liquid
# registrations behind `defined?`, so their logic loads standalone, and
# minitest ships with Ruby. Runs before the build — these plugins decide
# every page's description and read time.
run: ruby test/run_all.rb
# Before the build: these plugins decide every page's description, order
# and read time.
run: bundle exec ruby test/run_all.rb
- name: Lint the post sources
# Front matter against _data/post_conventions.yml, plus table, math and
# heading rules the built HTML no longer shows.
Expand Down Expand Up @@ -97,7 +87,7 @@ jobs:
BASE_PATH: ${{ steps.pages.outputs.base_path }}
# check-plugin-output.rb also holds the build to the live sitemap, so a
# post merged without --record cannot silently lose its URL.
PUBLISHED_SITEMAP: https://bits-bytes-nn.github.io/sitemap.xml
CHECK_LIVE_SITEMAP: "1"
- name: Upload artifact
# Nothing to publish from a PR run; the gates above are the whole point.
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
Expand Down
6 changes: 6 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,11 @@ gem 'bigdecimal'
gem 'logger'
gem 'webrick'

# _plugins/safe_post_html.rb parses each post body with its HTML5 parser.
gem 'nokogiri', '>= 1.12'

# CI quality gate: validates internal links, images, and HTML structure of _site/
gem 'html-proofer', '~> 5.2'

# test/run_all.rb, run under the bundle like the build.
gem 'minitest', '~> 5.25', group: :test
3 changes: 3 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ GEM
logger (1.7.0)
mercenary (0.4.0)
mini_portile2 (2.8.9)
minitest (5.27.0)
nokogiri (1.19.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
Expand Down Expand Up @@ -171,6 +172,8 @@ DEPENDENCIES
jekyll-sitemap
kramdown-parser-gfm
logger
minitest (~> 5.25)
nokogiri (>= 1.12)
webrick

RUBY VERSION
Expand Down
12 changes: 3 additions & 9 deletions README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Bits, Bytes and Neural Networks

**AI/ML 논문 리뷰·요약과 기술 가이드를 블로그로 발행합니다.**
**AI/ML 논문과 기술을 깊이 읽고 정리하는 블로그입니다.**

[bits-bytes-nn.github.io](https://bits-bytes-nn.github.io) · GitHub Pages에서 서빙하는 Jekyll 정적 사이트

Expand Down Expand Up @@ -31,14 +31,8 @@ Summaries, Paper Surveys, Tech Guides, Insights 다섯 개입니다. 논문 글
2. **의존성 설치:** `bundle install`
3. **개발 서버**(저장하면 다시 빌드, `http://localhost:4000`):
`bundle exec jekyll serve`. `_config.yml`을 바꾸면 다시 시작합니다.
4. **CI와 같은 검사:**

```bash
ruby test/run_all.rb && ruby script/lint-posts.rb
bundle exec jekyll build --strict-front-matter --destination /tmp/site-verify
bundle exec htmlproofer /tmp/site-verify --disable-external --allow-hash-href --no-ignore-empty-alt
script/validate-site.sh /tmp/site-verify
```
4. **CI와 같은 검사:** 명령은
[docs/operations.md](docs/operations.md#checks-before-pushing)에 있습니다.

## 문서

Expand Down
12 changes: 3 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Bits, Bytes and Neural Networks

**In-depth AI/ML paper reviews, summaries, and tech guides — published as a blog.**
**In-depth notes on AI/ML papers and technology.**

[bits-bytes-nn.github.io](https://bits-bytes-nn.github.io) · a Jekyll static site on GitHub Pages

Expand Down Expand Up @@ -33,14 +33,8 @@ merge deploys.
2. **Install the gems:** `bundle install`
3. **Serve with live reload** at `http://localhost:4000`:
`bundle exec jekyll serve`. Restart it after changing `_config.yml`.
4. **Check a change the way CI does:**

```bash
ruby test/run_all.rb && ruby script/lint-posts.rb
bundle exec jekyll build --strict-front-matter --destination /tmp/site-verify
bundle exec htmlproofer /tmp/site-verify --disable-external --allow-hash-href --no-ignore-empty-alt
script/validate-site.sh /tmp/site-verify
```
4. **Check a change the way CI does:** the commands are in
[docs/operations.md](docs/operations.md#checks-before-pushing).

## Documentation

Expand Down
9 changes: 6 additions & 3 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ date_format: "%B %-d, %Y"
# which then leaks into meta content="" attributes and the feed <subtitle>.
# The home page's description too, and the feed <subtitle> and footer: one text.
description: >-
AI/ML 논문을 설계 결정과 트레이드오프까지 파고들어 읽고, 에이전틱 AI·데이터 아키텍처 현장에서
겪은 문제를 정리하는 기술 블로그입니다.
In-depth notes on AI/ML papers and technology.
# The language of the site's own words (navigation, labels, the title and
# description above), which differs from `lang`, the posts'. The layouts mark
# that text with it, so a screen reader does not read it in a Korean voice.
ui_lang: en
baseurl: ''
url: https://bits-bytes-nn.github.io
# Post URLs embed :year/:month/:day, and front-matter dates carry no offset, so
Expand Down Expand Up @@ -106,7 +109,7 @@ social:
icon: github
username: bits-bytes-nn
url: https://github.com/bits-bytes-nn
desc: Fork me on GitHub
desc: bits-bytes-nn on GitHub
share: false

# Share only (no url = not shown in Contact)
Expand Down
6 changes: 3 additions & 3 deletions _includes/footer.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<footer class="site-footer">
<footer class="site-footer" lang="{{ site.ui_lang }}">

<div class="wrapper">

Expand Down Expand Up @@ -41,8 +41,8 @@ <h2 class="footer-heading">{{ site.title }}</h2>
</div>

<div class="site-signature">
<p class="rss-subscribe text"><strong>Subscribe <a href="{{ "/feed.xml" | prepend: site.baseurl }}">via RSS</a></strong> · <a href="{{ '/about/#privacy' | prepend: site.baseurl }}">Privacy</a></p>
<p class="text" lang="{{ site.lang }}">{{ site.description }}</p>
<p class="rss-subscribe text"><strong><a href="{{ "/feed.xml" | prepend: site.baseurl }}">Subscribe via RSS</a></strong> · <a href="{{ '/about/#privacy' | prepend: site.baseurl }}">Privacy</a></p>
<p class="text">{{ site.description }}</p>
</div>

</div>
Expand Down
24 changes: 10 additions & 14 deletions _includes/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@

<!-- Site styles -->
<link rel="stylesheet" href="{{ '/css/main.css' | prepend: site.baseurl }}">
{% if page.stylesheet %}<link rel="stylesheet" href="{{ page.stylesheet | prepend: site.baseurl }}">{% endif %}
<!-- Without JS the phone menu stays open (main.js closes it): let it scroll
away with the page rather than cover two thirds of the screen. -->
<noscript><style>.navigation { position: absolute; }</style></noscript>
<!-- Without JS the phone menu has no toggle: show it open, without the
button, and let it scroll away with the page rather than cover two
thirds of the screen. -->
<noscript><style>.navigation { position: absolute; } .navigation ul.navigation-menu { display: block; } .navigation .navigation-menu-button { display: none; }</style></noscript>
{%- comment -%}
A page kept out of the sitemap (search, 404) is one with nothing to rank, and
Google had indexed the empty search shell. noindex says so; a canonical
Expand Down Expand Up @@ -203,18 +203,14 @@
</script>
{% endif %}

<!-- hreflang alternates for translated posts (paired by translation_id).
x-default, for readers in neither language, is the English one: they
are likelier to read English than Korean, and English queries brought
most of the search clicks (Search Console). -->
{% if page.translation_id %}
<link rel="alternate" hreflang="{{ page.lang }}" href="{{ page.url | prepend: site.baseurl | prepend: site.url }}" />
{% if page.lang == 'en' %}<link rel="alternate" hreflang="x-default" href="{{ page.url | prepend: site.baseurl | prepend: site.url }}" />{% endif %}
{% for post in site.posts %}
{% if post.translation_id == page.translation_id and post.url != page.url %}
<!-- hreflang alternates for a translated post (page.translation,
_plugins/translations.rb). x-default, for readers in neither language,
is the English one: they are likelier to read English than Korean. -->
{% if page.translation %}
{% assign pair = "" | split: "" | push: page | push: page.translation %}
{% for post in pair %}
<link rel="alternate" hreflang="{{ post.lang }}" href="{{ post.url | prepend: site.baseurl | prepend: site.url }}" />
{% if post.lang == 'en' %}<link rel="alternate" hreflang="x-default" href="{{ post.url | prepend: site.baseurl | prepend: site.url }}" />{% endif %}
{% endif %}
{% endfor %}
{% endif %}

Expand Down
8 changes: 4 additions & 4 deletions _includes/header.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<header class="navigation" role="banner">
<header class="navigation" role="banner" lang="{{ site.ui_lang }}">
<div class="navigation-wrapper">
<a href="{{ site.baseurl }}/" class="logo">
{% if site.logo %}
Expand All @@ -16,15 +16,15 @@
{% endif %}
</a>
<button type="button" id="js-mobile-menu" class="navigation-menu-button"
aria-label="Toggle navigation menu" aria-expanded="false" aria-controls="js-navigation-menu">
aria-label="Menu" aria-expanded="false" aria-controls="js-navigation-menu">
{% include icon.html name="bars" %}
</button>
<nav role="navigation" aria-label="Main">
<ul id="js-navigation-menu" class="navigation-menu show">
<ul id="js-navigation-menu" class="navigation-menu">
{% include nav_links.html %}
<li class="nav-link theme-toggle-item">
<button type="button" id="theme-toggle" class="theme-toggle"
aria-label="Toggle dark mode" title="Toggle dark mode">
aria-label="Dark mode" title="Dark mode">
<span class="theme-icon-light">{% include icon.html name="moon" %}</span><span class="theme-icon-dark">{% include icon.html name="sun" %}</span>
</button>
</li>
Expand Down
43 changes: 11 additions & 32 deletions _includes/language_switcher.html
Original file line number Diff line number Diff line change
@@ -1,34 +1,13 @@
{%- comment -%}
Renders a ko/en language switcher for posts that have a translation.
Pairs are matched by a shared `translation_id` front-matter key; each post
also sets `lang: ko|en`. Outputs nothing for posts without a counterpart.
A link to the post's translation (page.translation, _plugins/translations.rb),
between the meta panel and the table of contents. It is an action, unlike the
date or category in the meta panel, and labels itself in the language it leads
to; `lang` makes a screen reader say it in that language.
{%- endcomment -%}
{% if page.translation_id %}
{% assign counterpart = nil %}
{% for post in site.posts %}
{% if post.translation_id == page.translation_id and post.url != page.url %}
{% assign counterpart = post %}
{% break %}
{% endif %}
{% endfor %}
{% if counterpart %}
{% if page.lang == 'ko' %}{% assign other_label = 'English' %}{% else %}{% assign other_label = '한국어' %}{% endif %}
{%- comment -%}
A standalone button between the meta panel and the table of contents, not a
meta item inside .post-header-info. "Read this in English" is an action the
reader takes, unlike the date or category next to it, and it carries its own
label in the link text — so it needs neither a Language: caption nor a
column in that row.
{%- endcomment -%}
<div class="language-switcher">
{%- comment -%}
`lang` marks the words that are in the other language, for screen readers:
on a Korean page only "English" (the rest is Korean), on an English page
the whole Korean label.
{%- endcomment -%}
<a href="{{ counterpart.url | prepend: site.baseurl }}" rel="alternate" hreflang="{{ counterpart.lang }}"{% if page.lang != 'ko' %} lang="ko"{% endif %}>
{% if page.lang == 'ko' %}<span lang="{{ counterpart.lang }}">{{ other_label }}</span>{% else %}{{ other_label }}{% endif %}로 읽기 →
</a>
</div>
{% endif %}
{% endif %}
{%- assign twin = page.translation -%}
{%- if twin -%}
{%- if twin.lang == 'en' -%}{%- assign label = 'Read in English' -%}{%- else -%}{%- assign label = '한국어로 읽기' -%}{%- endif -%}
<div class="language-switcher">
<a href="{{ twin.url | prepend: site.baseurl }}" rel="alternate" hreflang="{{ twin.lang }}" lang="{{ twin.lang }}">{{ label }} <span aria-hidden="true">→</span></a>
</div>
{%- endif -%}
7 changes: 6 additions & 1 deletion _includes/nav_links.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
{%- comment -%}
`nav_title`: a shorter tab name than the page's title. The bar is 48em wide,
and three tabs that each began with "Paper" wrapped it to two rows. The
footer's site map has the room, so it passes full=true for the titles.
footer's site map has the room, so it passes full=true for the titles, and
adds the pages marked `footer_nav` (the indexes the bar has no room for).
{%- endcomment -%}
{% if include.full %}
{% assign nav_pages = site.pages | where_exp: "p", "p.main_nav or p.footer_nav" | sort: "nav_order" %}
{% else %}
{% assign nav_pages = site.pages | where: "main_nav", true | sort: "nav_order" %}
{% endif %}
{% for page in nav_pages %}
<li class="nav-link">
<a href="{{ page.url | prepend: site.baseurl }}">{% if include.full %}{{ page.title | escape }}{% else %}{{ page.nav_title | default: page.title | escape }}{% endif %}</a>
Expand Down
8 changes: 2 additions & 6 deletions _includes/related_posts.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,8 @@
{%- endcomment -%}
{% if page.related and page.related.size > 0 %}
<nav class="related-posts" aria-labelledby="related-posts-heading">
{%- comment -%}
English, like every other UI string on this site ("Published", "Read time",
"Contents", "Site Map"). The chrome is one language by design; only content is
bilingual, so this heading does not branch on page.lang.
{%- endcomment -%}
<h2 id="related-posts-heading" class="related-posts-heading">Related reading</h2>
{%- comment -%} In site.ui_lang, like every label; only content is bilingual. {%- endcomment -%}
<h2 id="related-posts-heading" class="related-posts-heading" lang="{{ site.ui_lang }}">Related reading</h2>
<ul>
{% for other in page.related %}
<li>
Expand Down
2 changes: 1 addition & 1 deletion _layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
button, the nav links, theme toggle), and a long post then leads with a
copy button per code block. Screen readers can jump by landmark; sighted
keyboard users had no shortcut. -->
<a class="skip-link" href="#main-content">Skip to content</a>
<a class="skip-link" href="#main-content" lang="{{ site.ui_lang }}">Skip to content</a>

{% include header.html %}
<!-- The bar's height before first paint, so a bar that wraps to two rows does
Expand Down
20 changes: 8 additions & 12 deletions _layouts/post.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ <h1 class="title">{{ page.title | escape }}</h1>
post. feed.xml names site.author in each entry's <author>: this
post's author is always the site's.
{%- endcomment -%}
{% if page.paper_author %}<p class="info">Paper by <strong>{{ page.paper_author | escape }}</strong></p>{% endif %}
{% if page.paper_author %}<p class="info" lang="{{ site.ui_lang }}">Paper by <strong>{{ page.paper_author | escape }}</strong></p>{% endif %}
</header>
</div>
</div>

<div class="wrapper">
{%- comment -%} The cover's CC BY 4.0 credit, which must be visible (_data/covers.yml). {%- endcomment -%}
{%- assign cover_source = site.data.covers[page.cover] -%}
{% if cover_source %}<p class="cover-credit">Cover: <a href="{{ cover_source.url }}">{{ cover_source.title | escape }}</a>, {{ cover_source.credit | escape }} (<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>)</p>{% endif %}
<section class="post-meta">
{% if cover_source %}<p class="cover-credit" lang="{{ site.ui_lang }}">Cover: <a href="{{ cover_source.url }}">{{ cover_source.title | escape }}</a>, {{ cover_source.credit | escape }} (<a href="https://creativecommons.org/licenses/by/4.0/">CC BY 4.0</a>)</p>{% endif %}
<section class="post-meta" lang="{{ site.ui_lang }}">
<div class="post-header-info">
<div class="post-date">
<span class="meta-label">Published</span>
Expand Down Expand Up @@ -73,7 +73,7 @@ <h1 class="title">{{ page.title | escape }}</h1>
{%- assign toc = content | post_toc -%}
{% if toc.size >= 3 %}
<details id="post-toc" class="post-toc" open>
<summary>
<summary lang="{{ site.ui_lang }}">
<span class="post-toc-title">Contents</span>
<span class="post-toc-count">{{ toc.size }}</span>
<span class="post-toc-chevron" aria-hidden="true"></span>
Expand All @@ -99,27 +99,23 @@ <h1 class="title">{{ page.title | escape }}</h1>
Korean post would be that same article in English.
{%- endcomment -%}
{% if page.adjacent_older or page.adjacent_newer %}
<nav class="post-nav" aria-label="Adjacent posts">
<nav class="post-nav" aria-label="Adjacent posts" lang="{{ site.ui_lang }}">
{% if page.adjacent_older %}
<a class="post-nav-link prev" href="{{ page.adjacent_older.url | prepend: site.baseurl }}" rel="prev">
<span class="post-nav-dir">← Previous</span>
<span class="post-nav-dir"><span aria-hidden="true">←</span> Previous</span>
<span class="post-nav-title" lang="{{ page.adjacent_older.lang | default: site.lang }}">{{ page.adjacent_older.title | escape }}</span>
</a>
{% else %}<span></span>{% endif %}
{% if page.adjacent_newer %}
<a class="post-nav-link next" href="{{ page.adjacent_newer.url | prepend: site.baseurl }}" rel="next">
<span class="post-nav-dir">Next →</span>
<span class="post-nav-dir">Next <span aria-hidden="true">→</span></span>
<span class="post-nav-title" lang="{{ page.adjacent_newer.lang | default: site.lang }}">{{ page.adjacent_newer.title | escape }}</span>
</a>
{% else %}<span></span>{% endif %}
</nav>
{% endif %}

<section class="rss">
<p class="rss-subscribe text"><strong>Subscribe <a href="{{ "/feed.xml" | prepend: site.baseurl }}">via RSS</a></strong></p>
</section>

<section class="share">
<section class="share" lang="{{ site.ui_lang }}">
<span>Share: </span>
{% for social in site.social %}
{% if social.share and social.share_url %}
Expand Down
Loading