Colophon

Read this in the style of Walt Whitman

This is rommy.blog. A personal blog.

Pages are plain HTML, generated by a Node.js build script at deploy time — no framework, no bundler, no runtime dependencies. Content lives in a Cloudflare D1 database: Writing posts, drafts, Reading logs, Sharing links, and Thinking notes. The build script reads D1 on each deploy and writes static HTML to Cloudflare Pages. Generated pages aren't committed to Git — only source, styles, and hand-authored pages live in the repo. Saving from the admin triggers a Pages rebuild; so does pushing to main.

Editing happens through a password-protected admin UI, behind Cloudflare Access. A Cloudflare Worker handles the API: writing to D1, uploading media to R2, serving photos, audio, and video from rommy.blog/media (same-origin, edge-cached, with byte-range support for streaming and iOS), and cross-posting to Micro.blog (via Micropub), Bluesky, and Mastodon. Writing and Reading cross-posts include the post or book title before the link. Writing, Reading, and Sharing cross-posts to Bluesky include an explicit link card — title, description, and thumbnail fetched at post time. Thinking photo posts syndicate the full text and image(s) natively; audio and video also syndicate as native, playable attachments on Micro.blog and Mastodon, and on Bluesky, where video (MP4 or iPhone MOV) is sent through Bluesky's own video-processing service so it transcodes and plays natively there too. When a native attachment is included, the Thinking permalink is appended to the syndicated text so readers can still reach the canonical post (Bluesky's link-card embed already points there for audio and any video that falls back to a card). Falls back to a link-only post — with an Audio: or Video: prefix — when a file is over a platform's size limit or unavailable. Bluesky truncates at 300 characters, and the admin shows a soft guide while typing. Homepage section headings have hover tooltips that Claude Fable refreshes after content changes; Reading tab intro copy is redrafted the same way when books are added or removed. Deleting a Thinking note removes it from all platforms where it was syndicated.

Writing uses Quill. Images go inline from the toolbar — type in the same paragraph to wrap text, or click a photo to reposition or delete it. On save, image-only paragraphs are merged with the following paragraph so floats render correctly. Drafts are stored in D1; published posts keep a version history.

Photos are stored in R2 and served from rommy.blog/media. Uploads up to 25 MB are compressed to 5 MB or smaller on save (GIFs must already be under 5 MB). rommy.blog and Micro.blog get the compressed file; Bluesky receives a further JPEG when it still exceeds 2 MB. On Writing posts, portrait-oriented photos default to half width; click once to enlarge, again to shrink.

Thinking notes live in D1, with optional photo(s), audio (Apple Voice Memos M4A), or video (MP4 or iPhone MOV, up to 100 MB) — one kind of medium per note. Up to four photos can be attached; two or more render as an equal-size, cropped 2×2 grid at the width of the blog, and clicking any tile opens a full-viewport lightbox with previous/next controls, keyboard arrows, and swipe-to-navigate on mobile — wrapping at both ends, and rendering edge-to-edge on phones, including landscape orientation. Text is optional when media is attached. Video uploads go directly from the browser to R2 via presigned URLs; photos and audio pass through the Worker. Audio and video play on rommy.blog only, served from R2 through the Worker's edge cache. Permalink pages prefetch video for faster playback; the archive and homepage load metadata only. Permalink slugs are generated from US Eastern time. Bare URLs become clickable links; #hashtags link to the matching tag page on Bluesky. There are no preview cards on the site itself, with two exceptions: YouTube links get a native, playable embed beneath the note, and Spotify links (tracks, albums, playlists, episodes, shows, or artists) get a compact native embed the same way, so both play right there on rommy.blog. Otherwise, Bluesky, Micro.blog, and Mastodon unfurl links in their own clients, and Bluesky turns hashtags into clickable tags.

The Thinking archive at /thinking/ offers List and Grid views, switched with two small icon buttons under the heading; the choice is remembered in localStorage. Six type-filter buttons sit beside them — photo, video, audio, YouTube, Spotify, and text — and by default every post is visible in whichever view you're in; click one filter to solo-select that type (click again to clear). Filters apply to both list and grid. Links to the archive from elsewhere always land on /thinking/ with no filter active; choosing a filter updates the URL to a shareable path — /thinking/images/, /thinking/videos/, /thinking/audio/, /thinking/youtube/, /thinking/music/ for Spotify, /thinking/text/ — so a filtered view can be bookmarked or linked directly. Grid groups notes by month and shows one square thumbnail per post. Photo tiles use the note's image, resized to 252 px squares at the edge via the Worker's /media/thumb/ route. Native video tiles use a JPEG poster captured at upload (or backfilled from the first frame). YouTube tiles pull the public preview image; Spotify tiles pull album art when available. Text-only notes show a clipped snippet in a card, with a small icon badge (camera, microphone, YouTube mark, Spotify mark, or “Tt” for plain text) in the corner when the note has no photo. Grid images load lazily — deferred until grid view is active and the tile nears the viewport, with a small concurrency cap so the page stays snappy on long archives.

Reading at /reading/latest/ (with a curated Must Reads list) is a book log linked to Bookshop.org, sorted by the month you read each title (ym), not the day you added it. The archive uses path-based tabs — /reading/latest/ and /reading/must-reads/ — with the URL updating when you switch; each tab opens with a short taste summary, redrafted by Claude Fable when books are added or removed. The archive has the same list/grid toggle as Thinking (also remembered in localStorage): list shows month and title; grid is a storefront-style catalog grouped by month — three columns on desktop, two on mobile — with cover art under each title. When you add a book in the admin, an optional author field helps disambiguate cover search; the picker queries Open Library, Apple Books, and Google Books, and you can swap covers later from the archive list. Chosen covers are stored in D1 and can be replaced with custom images uploaded to R2; several Must Reads titles use custom cover art hosted at rommy.blog/media/reading/covers/. At build time, any entry still without a cover gets an automatic Open Library lookup (ISBN-first from the Bookshop URL, then a guarded title search).

Under Elsewhere on the homepage, Socials is a collapsible row — a small arrow points right until you click, then down, revealing indented links to Bluesky, Mastodon, and micro.blog.

When a rommy.blog URL is shared elsewhere, the build emits Open Graph and Twitter meta tags. Writing posts use the summary and first in-post image; Thinking permalinks use a text snippet and the note's first image, with a favicon fallback.

Writing post pages support Webmentions. Incoming mentions are received by webmention.io and displayed via client-side fetch — likes and reposts as avatars, replies as comments. Bridgy bridges Mastodon activity back to webmention.io, so Mastodon replies and boosts appear as webmentions. Thinking has no comments.

The design is minimal: warm off-white, a single typeface (Inter), photos where they belong. Dark mode is toggled in the footer and persisted between visits. There is no client-side framework beyond the theme toggle, archive view toggles, type filters, the Socials drawer, archive pagination, and photo expand/collapse.

Sharing is a linklog. The Changelog is generated from Git commit history. Dates display in US Eastern time.

Visitor analytics use Google Analytics 4. The contact form is handled by Formspree.

Source code is MIT licensed. Built and maintained with Cursor and Claude Code.