@gurupanguji

Homepage Day Timeline Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Change the homepage feed from the latest 5 posts to the latest 5 distinct publishing dates with posts, rendered as a grouped left-rail timeline.

Architecture: Keep the change local to index.html. Add one small verification script in scripts/verify_homepage_day_timeline.py that proves both the grouping logic and the built homepage output, then replace the flat feed markup and styles in the homepage with grouped Liquid and timeline CSS. No archive work, no shared include extraction, and no JavaScript rendering changes in this pass.

Tech Stack: Jekyll, Liquid, HTML, CSS, Python 3 stdlib


File Map

Task 1: Add a Failing Homepage Timeline Verifier

Files:

#!/usr/bin/env python3
from __future__ import annotations

from pathlib import Path
import re
import sys


POST_PATTERN = re.compile(r"(\d{4})-(\d{2})-(\d{2})-(.+)\.md$")
GROUP_PATTERN = re.compile(r'class="[^"]*\bfeed-day-group\b[^"]*"')
LABEL_PATTERN = re.compile(r'<div class="feed-day-label">\s*([^<]+?)\s*</div>')


def slug_to_url(name: str) -> tuple[str, str]:
    match = POST_PATTERN.match(name)
    if not match:
        raise ValueError(f"Unsupported post filename: {name}")
    year, month, day, slug = match.groups()
    return f"{year}-{month}-{day}", f"/blog/{year}/{month}/{day}/{slug}/"


def group_posts_by_day(rows: list[tuple[str, str]], max_days: int = 5) -> list[dict[str, list[str] | str]]:
    groups: list[dict[str, list[str] | str]] = []
    current_day = None
    for day, url in rows:
        if day != current_day:
            if len(groups) == max_days:
                break
            groups.append({"day": day, "urls": []})
            current_day = day
        groups[-1]["urls"].append(url)
    return groups


def fixture_self_test() -> None:
    rows = [
        ("2026-04-13", "/blog/2026/04/13/a/"),
        ("2026-04-12", "/blog/2026/04/12/b/"),
        ("2026-04-12", "/blog/2026/04/12/c/"),
        ("2026-04-11", "/blog/2026/04/11/d/"),
        ("2026-04-10", "/blog/2026/04/10/e/"),
        ("2026-04-09", "/blog/2026/04/09/f/"),
        ("2026-04-08", "/blog/2026/04/08/g/"),
    ]
    groups = group_posts_by_day(rows, max_days=5)
    assert [group["day"] for group in groups] == [
        "2026-04-13",
        "2026-04-12",
        "2026-04-11",
        "2026-04-10",
        "2026-04-09",
    ]
    assert groups[1]["urls"] == [
        "/blog/2026/04/12/b/",
        "/blog/2026/04/12/c/",
    ]
    assert all(group["day"] != "2026-04-08" for group in groups)


def expected_homepage_groups(posts_dir: Path) -> list[dict[str, list[str] | str]]:
    rows: list[tuple[str, str]] = []
    for path in sorted(posts_dir.iterdir(), reverse=True):
        if not path.name.endswith(".md"):
            continue
        day, url = slug_to_url(path.name)
        rows.append((day, url))
    return group_posts_by_day(rows, max_days=5)


def day_label(day: str) -> str:
    year, month, date = day.split("-")
    month_name = {
        "01": "JAN",
        "02": "FEB",
        "03": "MAR",
        "04": "APR",
        "05": "MAY",
        "06": "JUN",
        "07": "JUL",
        "08": "AUG",
        "09": "SEP",
        "10": "OCT",
        "11": "NOV",
        "12": "DEC",
    }[month]
    return f"{month_name} {date}"


def verify_rendered_homepage(site_index: Path, groups: list[dict[str, list[str] | str]]) -> None:
    html = site_index.read_text(encoding="utf-8")

    group_count = len(GROUP_PATTERN.findall(html))
    if group_count != 5:
        raise AssertionError(f"Expected 5 timeline groups, found {group_count}")

    labels = LABEL_PATTERN.findall(html)
    expected_labels = [day_label(group["day"]) for group in groups]
    if labels[:5] != expected_labels:
        raise AssertionError(f"Expected labels {expected_labels}, found {labels[:5]}")

    for group in groups:
        for url in group["urls"]:
            if url not in html:
                raise AssertionError(f"Missing expected post URL in homepage output: {url}")

    all_expected_urls = [url for group in groups for url in group["urls"]]
    outside_group_urls = []
    posts_dir = Path("_posts")
    for path in sorted(posts_dir.iterdir(), reverse=True):
        if not path.name.endswith(".md"):
            continue
        _, url = slug_to_url(path.name)
        if url in all_expected_urls:
            continue
        outside_group_urls.append(url)
    if outside_group_urls and outside_group_urls[0] in html:
        raise AssertionError(f"Found a post from the 6th day or later in homepage output: {outside_group_urls[0]}")


def main() -> int:
    fixture_self_test()
    groups = expected_homepage_groups(Path("_posts"))
    verify_rendered_homepage(Path("_site/index.html"), groups)
    print(f"Verified homepage timeline for {len(groups)} distinct publish dates.")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except AssertionError as error:
        print(f"FAIL: {error}", file=sys.stderr)
        raise SystemExit(1)

Run:

bundle exec jekyll build
python3 scripts/verify_homepage_day_timeline.py

Expected:

FAIL: Expected 5 timeline groups, found 0
git add scripts/verify_homepage_day_timeline.py
git commit -m "test: add homepage day timeline verifier"

Task 2: Replace the Homepage Feed With Grouped Timeline Markup

Files:

In index.html, replace the existing .feed-item, .feed-item.loaded, .feed-item:hover, .feed-item:focus-visible, .feed-item-title, and .feed-item-date rules with this block:

      .feed-timeline {
        display: flex;
        flex-direction: column;
        gap: 28px;
      }

      .feed-day-group {
        display: grid;
        grid-template-columns: 84px minmax(0, 1fr);
        column-gap: 22px;
        align-items: start;
      }

      .feed-day-rail {
        display: flex;
        flex-direction: column;
        align-items: flex-start;
        min-height: 100%;
      }

      .feed-day-label {
        font-family: var(--font-ui);
        font-size: 0.58rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 3px;
        color: var(--text-muted);
        white-space: nowrap;
      }

      .feed-day-thread {
        width: 1px;
        min-height: 52px;
        flex: 1;
        margin-top: 10px;
        margin-left: 10px;
        background: linear-gradient(
          to bottom,
          var(--border-subtle) 0%,
          rgba(0, 0, 0, 0) 100%
        );
      }

      html[data-theme="black"] .feed-day-thread {
        background: linear-gradient(
          to bottom,
          var(--border-subtle) 0%,
          rgba(255, 255, 255, 0) 100%
        );
      }

      .feed-day-posts {
        display: flex;
        flex-direction: column;
      }

      .feed-post-link {
        display: block;
        text-decoration: none;
        color: var(--text-primary);
        padding: 12px 0;
        border-bottom: 1px solid var(--border-subtle);
        transition: color 0.3s ease, transform 0.3s ease, padding-left 0.3s ease;
      }

      .feed-post-link:hover {
        padding-left: 10px;
        color: var(--interaction-emphasis);
      }

      .feed-post-link:focus-visible {
        padding-left: 10px;
        color: var(--interaction-emphasis);
        outline: 2px solid var(--interaction-emphasis);
        outline-offset: 4px;
      }

      .feed-post-title {
        display: block;
        font-family: var(--font-ui);
        font-size: 0.75rem;
        font-weight: 600;
        text-transform: uppercase;
        letter-spacing: 1px;
        line-height: 1.25;
      }

In the existing @media (max-width: 768px) block in index.html, add these rules before the closing brace:

        .blog-feed {
          max-width: 560px;
        }

        .feed-timeline {
          gap: 24px;
        }

        .feed-day-group {
          grid-template-columns: 1fr;
          row-gap: 10px;
        }

        .feed-day-thread {
          width: 48px;
          min-height: 1px;
          height: 1px;
          margin-top: 8px;
          margin-left: 0;
        }

In index.html, replace the current contents of #feed-container with this grouped timeline markup:

        <div id="feed-container" class="feed-timeline">
          
          
          
          
            
            
              

              
              

              <section class="feed-day-group">
                <div class="feed-day-rail">
                  <div class="feed-day-label">APR 18</div>
                  <div class="feed-day-thread" aria-hidden="true"></div>
                </div>
                <div class="feed-day-posts">
              
              
            

            <a href="/blog/2026/04/18/mark-of-the-mature-man/" class="feed-post-link">
              <span class="feed-post-title">the mark of the mature man</span>
            </a>
          
            
            
              
                </div>
              </section>
              

              
              

              <section class="feed-day-group">
                <div class="feed-day-rail">
                  <div class="feed-day-label">APR 17</div>
                  <div class="feed-day-thread" aria-hidden="true"></div>
                </div>
                <div class="feed-day-posts">
              
              
            

            <a href="/blog/2026/04/17/john-price-quotes/" class="feed-post-link">
              <span class="feed-post-title">john price quotes</span>
            </a>
          
            
            
              
                </div>
              </section>
              

              
              

              <section class="feed-day-group">
                <div class="feed-day-rail">
                  <div class="feed-day-label">APR 16</div>
                  <div class="feed-day-thread" aria-hidden="true"></div>
                </div>
                <div class="feed-day-posts">
              
              
            

            <a href="/blog/2026/04/16/the-resonant-computing-manifesto/" class="feed-post-link">
              <span class="feed-post-title">πŸ”— The Resonant Computing Manifesto</span>
            </a>
          
            
            

            <a href="/blog/2026/04/16/we-found-a-ticking-time-bomb-in-macos-tcp-networking/" class="feed-post-link">
              <span class="feed-post-title">we found a ticking time bomb in macos tcp networking</span>
            </a>
          
            
            
              
                </div>
              </section>
              

              
              

              <section class="feed-day-group">
                <div class="feed-day-rail">
                  <div class="feed-day-label">APR 15</div>
                  <div class="feed-day-thread" aria-hidden="true"></div>
                </div>
                <div class="feed-day-posts">
              
              
            

            <a href="/blog/2026/04/15/so-you-want-to-live-to-be-81/" class="feed-post-link">
              <span class="feed-post-title">πŸ”— So You Want To Live To Be 81?</span>
            </a>
          
            
            

            <a href="/blog/2026/04/15/pi-monopackagescoding-agentchangelogmd-at-main/" class="feed-post-link">
              <span class="feed-post-title">πŸ”— pi-mono/packages/coding-agent/CHANGELOG.md at main</span>
            </a>
          
            
            

            <a href="/blog/2026/04/15/banksy-satoshi-the-unmasking-impulse/" class="feed-post-link">
              <span class="feed-post-title">banksy, satoshi & the unmasking impulse</span>
            </a>
          
            
            
              
                </div>
              </section>
              

              
              

              <section class="feed-day-group">
                <div class="feed-day-rail">
                  <div class="feed-day-label">APR 14</div>
                  <div class="feed-day-thread" aria-hidden="true"></div>
                </div>
                <div class="feed-day-posts">
              
              
            

            <a href="/blog/2026/04/14/dense-discovery-383-why-we-defend-whats-failing-us/" class="feed-post-link">
              <span class="feed-post-title">πŸ”— Dense Discovery #383: Why we defend what’s failing us</span>
            </a>
          
            
            

            <a href="/blog/2026/04/14/if-you-thought-the-speed-of-writing-code-was-your-problem-you-have-bigger-problems/" class="feed-post-link">
              <span class="feed-post-title">if you thought the speed of writing code was your problem - you have bigger problems</span>
            </a>
          
            
            
              
                </div>
              </section>
              

              
              
                

          
            </div>
          </section>
          
        </div>

Run:

bundle exec jekyll build
python3 scripts/verify_homepage_day_timeline.py

Expected:

Verified homepage timeline for 5 distinct publish dates.
git add index.html scripts/verify_homepage_day_timeline.py
git commit -m "feat: group homepage posts by day"

Task 3: Run Manual QA For Layout And Accessibility

Files:

Run:

bundle exec jekyll serve --host 127.0.0.1 --port 4000

Expected:

Server address: http://127.0.0.1:4000/

Verify:

At roughly 390px wide, verify:

Using Tab, verify:

Run:

bundle exec jekyll build
python3 scripts/verify_homepage_day_timeline.py

Expected:

Verified homepage timeline for 5 distinct publish dates.
git add index.html scripts/verify_homepage_day_timeline.py
git commit -m "fix: polish homepage day timeline layout"

Self-Review

Spec Coverage

Placeholder Scan

Type Consistency

GitHub Issue / PR Fallback

If gh auth is still broken in the execution environment, create the issue and later the PR manually with these commands after re-auth:

gh auth login -h github.com
gh issue create \
  --title "Homepage timeline groups latest posts by day" \
  --body "Implement the approved homepage-only day timeline design from docs/superpowers/specs/2026-04-06-homepage-day-timeline-design.md."

Execution Handoff

Plan complete and saved to docs/superpowers/plans/2026-04-06-homepage-day-timeline-implementation-plan.md. Two execution options:

1. Subagent-Driven (recommended) - I dispatch a fresh subagent per task, review between tasks, fast iteration

2. Inline Execution - Execute tasks in this session using executing-plans, batch execution with checkpoints

Which approach?