Full OG regeneration (2,350 images) OOM-kills when the Eleventy watcher
is running (~1.8 GB RSS), leaving only ~1.2 GB headroom in the 3 GB
container. WASM native memory from Satori/Resvg grows beyond what GC
can reclaim within a single process.
Solution: spawn og-cli in batches of 100 images. Each invocation exits
after its batch, fully releasing all WASM native memory. Exit code 2
signals "more work remains" and the spawner re-loops. Peak memory per
batch stays under ~500 MB regardless of total image count.
Also seed newManifest from existing manifest so unscanned entries survive
batch writes.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Full OG regeneration (2,350 images) was OOM-killed because WASM native
memory from Satori/Resvg accumulated between GC cycles. The previous
GC interval of 50 images allowed ~2.5-5 GB of native allocations before
reclamation. Reduce to every 5 images to keep peak RSS under ~400 MB.
Also reduce --max-old-space-size from 768 to 512 MB (V8 heap only uses
~22 MB) and add peak RSS tracking to the completion log.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Satori (Yoga WASM) and Resvg (Rust WASM) allocate native memory that
V8 doesn't track against the heap limit. Without periodic GC, the JS
wrappers accumulate and native RSS grows to ~2 GB during OG image
generation for 3400+ posts.
- Add --expose-gc to og-cli spawn
- Call global.gc() every 50 images to reclaim native memory
- Log final RSS/heap for monitoring
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
After GC, logs heap space breakdown (old_space, large_object_space, etc.)
to help identify memory consumers. Supports HEAP_SNAPSHOT=1 env var to
write a heap snapshot to /tmp for detailed analysis.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The previous commit fixed the Tier 2 default hero avatar (home.njk),
but production uses the homepage builder (Tier 1) which renders
hero.njk instead. Same issue: HTML width/height 96x96 but CSS sets
sm:w-32/h-32 (128px) on desktop, causing CLS on resize.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Three root causes identified via PageSpeed layout shift culprits:
1. Grid mismatch (CLS 0.495): Critical CSS used `2fr 1fr` but Tailwind
compiles to `repeat(3, minmax(0, 1fr))` with `grid-column: span 2`.
Updated critical CSS to match Tailwind's exact output.
2. Font swap FOUT (CLS 0.074): @font-face declarations were only in the
deferred stylesheet. Moved to critical CSS with font-display:optional
and added <link rel="preload"> for weights 400/600/700. Changed all
font-display from swap to optional in tailwind.css source.
3. Avatar resize: HTML width/height was 96x96 but CSS sets sm:w-32/h-32
(128px) on desktop. Updated attributes to 128x128.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The skeleton-to-content swap was the root cause of extreme CLS scores.
Critical CSS already provides correct first-paint layout, making the
skeleton unnecessary. Removes html.loading class, skeleton div,
page-content wrapper, and all skeleton CSS rules.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Add global.gc() call in eleventy.after handler to release unused heap
pages back to the OS. Without this, V8 keeps ~2 GB of build-time
allocations resident in watch mode because there's no allocation
pressure to trigger GC naturally. Requires --expose-gc in NODE_OPTIONS
(set in start.sh for the watcher process).
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The @keydown.tab handler in fediverse-modal.njk contained complex
inline JS with arrow functions, querySelector strings with escaped
quotes, and comparison operators — all of which confused
html-minifier-terser's HTML parser, causing parse errors on every
page that includes the modal (i.e., nearly every page).
Moved the focus-trap logic to a trapFocus() method on the Alpine
component where it belongs.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The accessibility change from <span> to <ul> caused categories to stack
vertically. Add flex utilities to maintain horizontal flow while keeping
the semantic list markup and ARIA attributes.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
- Add skip-to-main-content link and main content ID target
- Add prefers-reduced-motion media queries for all animations
- Enhance visible focus indicators (2px offset, high-contrast ring)
- Replace ~160 text-surface-500 instances with text-surface-600/dark:text-surface-400
for 4.5:1+ contrast ratio compliance
- Add aria-hidden="true" to ~30+ decorative SVG icons across sidebars/widgets
- Convert facepile containers from div to semantic ul/li with role="list"
- Add aria-label to icon-only buttons (share, sort controls)
- Add sr-only labels to form inputs (webmention, search)
- Add aria-live="polite" to dynamically loaded webmentions
- Add aria-label with relative+absolute date to time-difference component
- Add keyboard handlers (Enter/Space) to custom interactive elements
- Add aria-label to nav landmarks (table of contents)
- Fix modal focus trap and dialog accessibility
- Fix lightbox keyboard navigation and screen reader announcements
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Articles, notes, and bookmarks were all sharing amber, which
defeats the purpose of per-type color identity.
New complete color map (7 unique colors):
- Articles: indigo (long-form writing)
- Notes: teal (short posts)
- Bookmarks: amber (saved links)
- Likes: red (heart)
- Replies: sky (conversation)
- Reposts: green (sharing)
- Photos: purple (visual)
Updated across collection pages, blog.njk mixed view,
recent-posts widget (now shows type-specific icons and colors
for all 7 types), and design system documentation.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Each post type now has a unique, consistent color applied across
sparkline, card border, icon, label, hover state, and permalink:
- Articles/Notes/Bookmarks: amber
- Likes: red (was rose)
- Replies: sky (was rose)
- Reposts: green (was rose)
- Photos: purple
Replaces generic accent/rose colors with type-specific colors in
blog.njk mixed-type view and all individual collection pages.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Match sparkline wrapper colors to the post type icon colors used in the
recent posts widget: replies=sky, reposts=green, likes=red, photos=purple.
Articles, notes, bookmarks, and blog keep amber (writing domain).
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
SVGs with only a viewBox and no width/height attributes use intrinsic
sizing that can override CSS width:100%. Adding width="100%" height="100%"
and preserveAspectRatio="none" on the SVG element itself ensures the
sparkline fills its container div.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
- YouTube embeds now use lite-youtube facade (loads iframe on click,
~800 KiB savings per page with embedded videos)
- Avatar resized from 1000x1000 to 400x400 (152 KiB → 39 KiB)
- lite-yt-embed.css max-width changed to 100% for responsive layout
- Removed unused Tailwind primary color palette from CSS bundle
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The sparkline SVG containers were using <span> elements as flex items.
While a span's outer display is blockified in flex context, its inner
display remains inline, causing SVG width:100% to resolve against the
inline content width (~22px) instead of the flex-allocated width (~670px).
Switching to <div> provides block inner display, allowing the SVG to
fill the available space correctly.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Replace unstyled content flash with pulsing gray placeholder shapes
while the deferred Tailwind stylesheet loads. Uses a 'loading' class
on <html> that critical CSS uses to show skeleton / hide content,
removed by the stylesheet's onload handler.
Includes noscript fallback to bypass skeleton when JS is disabled.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
- Sparkline: change from fixed 120/180px to fluid width (flex-1 min-w-0)
filling the content area next to page titles across all 8 post type pages
- Blog filter: fix dark mode active pill contrast (dark:bg-accent-700)
- Interactions: replace wrong share icon with correct ActivityPub logo
- Interactions: add IndieWeb webmention provenance badge (globe icon)
- Interactions: improve platform detection (Bridgy Fed, more Fediverse instances)
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Add critical CSS rules to constrain images (max-width:100%) and inline
SVGs (default 1.25rem) before Tailwind loads. Add width/height HTML
attributes to avatar images in hero sections and h-card sidebar widget.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
- Replace broken SVG icon (mixed checkmark + radio paths) with proper
microphone icon matching the empty state icon
- Add font-mono to date elements: last synced span, episode <time>
- Add font-mono to stat numbers: podcast count (header + sidebar)
- Add shadow-sm to episode article cards (card depth pattern)
- Add focus:ring-2 to all buttons: refresh, try again, load more
- Focus rings use domain color (orange-500) consistently
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
- Add font-mono text-sm to all <time> elements (4 dates across 3 views)
- Add font-mono to stat numbers (feeds count, items count, pagination)
- Add shadow-sm to list view and expanded view article cards
- Add border + shadow-sm to stats bar (card depth pattern)
- Add focus:outline-none focus:ring-2 focus:ring-accent-500 to all buttons
(view mode toggles, pagination prev/next, refresh)
- Fix card view hover to use domain color (accent-400) instead of surface-400
- Fix pagination bug: loadPage() now passes feedId when filter is active,
preventing feed filter from resetting on page change
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Widget: add noLabels to prevent month abbreviations from squishing
together in the narrow sidebar. Both widget and section: use lighter
boxColorDark (#44403c / surface-700) so empty grid cells are visible
against the surface-800 sidebar/section backgrounds in dark mode.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
The ai-usage widget was empty on blog listing pages (/notes/, /photos/)
because sidebar.njk (the third sidebar dispatcher) was missing the
widget registration. Also adds the "zap" lightning bolt icon to icon.njk
which was referenced but never defined.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
1. Add font-mono to <time> elements (design system typography rule)
2. Add shadow-sm to inspiration box and empty-state box (depth rule)
3. Add h-entry + p-name/u-url microformats to all cards (RSS-ready)
4. Add border-l-4 visual differentiation: amber for Pages, emerald
for Activity Feeds, surface for Site Pages
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Replace the 22-item "/" dropdown with a curated "Pages" dropdown (Blogroll,
Podroll, News, All Pages). Add /now to header nav. Move CV and Digest to
footer only. Add auth-gated Dashboard to footer Navigate column. Remove
Interactions from footer Content column.
Refactor /slashes/ into a comprehensive site map with three sections: Pages
(dynamic CMS pages), Activity Feeds (plugin pages), and Site Pages (theme
.njk pages like /graph, /changelog, /featured, /starred, etc.).
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596
Compact widget showing AI transparency stats (total, AI-involved,
human-only, ratio), level breakdown pills, and a 1-year post-graph
highlighting AI-involved posts. Links to /ai/ for full report.
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
- Replace broken client-side type filter on /blog/ with navigation
pill links to dedicated collection pages (with post counts)
- Replace Load More with proper prev/next/page-number pagination
on Interactions inbound tab (20 per page, filter resets page)
- Add auto-unfurl transform for standalone external links in notes
- Exclude Digest and Categories pages from Pagefind search index
- Add Pagefind search filters for post type, year, and category
- Add Pagefind filter metadata to page.njk layout
Confab-Link: http://localhost:8080/sessions/956f4251-b4a9-4bc9-b214-53402ad1fe63
- Replace network-diagram SVG with official ActivityPub logo in post footer
- Replace person-plus SVG with AP logo in fediverse follow widget
- Replace Mastodon icon with AP logo in share widget, use #a730b8 purple
- AP icon brand color: #f1027e → #a730b8 in social-icon macro
- Detect Mastodon syndication URLs (/@username pattern) for proper logo pill
Confab-Link: http://localhost:8080/sessions/c5b1471e-b046-44d9-b94f-ab5e68fae7cc
- Add posting-activity.njk section template with configurable years/limit
- Register posting-activity in homepage-section.njk dispatcher
- Move hardcoded post-graph into Tier 2 block (homepage builder controls its own)
- Add "View full history" link to /graph/ on both Tier 2 and section template
- Create /graph/ page showing all years with no limit
Confab-Link: http://localhost:8080/sessions/edb1b7b0-da66-4486-bd9c-d1cfa7553b88
Adds a markdown-it inline rule that transforms #tag text into
links to /categories/tag/ on-site. Syndication targets (Bluesky,
Mastodon, Bridgy) continue to receive raw #tag text, which their
native facet/hashtag detection handles automatically.
Edge cases handled: headings, hex colors, URL fragments, code
blocks, pure numbers are all excluded from conversion.
Confab-Link: http://localhost:8080/sessions/0ec83454-d346-4329-8aaf-6b12139bf596