Personal daily newspaper for a monochrome ePaper display in my grandfather's living room. The system aggregates baseball, weather, local headlines, TV listings, golf results, and rotating family-specific content into static pages that refresh on a schedule.
Visit live newspaper →generate.js static renderergenerate-explore.js RSS magazine rackThe system has no runtime server. Scheduled GitHub Actions fetch every upstream source, render ePaper pages and mobile pages, validate the output, commit the generated files, and let Cloudflare Pages deploy the result. The display only knows a list of URLs and a refresh interval.
The ePaper edition and mobile hub are siblings. Both are generated from the same data at the same time. The ePaper rotation stays fixed-size and monochrome; the phone version becomes a swipe chain for family members.
The device needs a finished page, not an application shell. GitHub Actions performs the live data work, and the generated output is plain HTML and CSS.
validate.js checks ePaper dimensions, navigation integrity, URL safety, feed counts, and structural contracts before any generated page is pushed.
Each feed source has an explicit set of allowed hostnames. A headline link outside its expected host family is dropped from the generated page.
Daily quotes, famous Jims, cigar notes, and seasonal pages use a deterministic day index. There is no database state to maintain.
Scheduled Actions commits can overlap with local development pushes. The workflow uses a three-attempt rebase and retry loop, with generated data winning for generated files.
Thin italics vanish, gray text loses contrast, and overflow creates physical display artifacts. Templates enforce fixed dimensions, visible weights, and readable sizes.
Every data source is optional, every network call has a timeout, and every page has fallback copy for missing data. A failed source creates a quiet unavailable state.
generate.js is over 3,500 lines. TVmaze coverage varies by network. The Libby magazine catalog is still thin.