RSS Amplifier

Blog

Jerry Cheung

Jerry Cheung blog posts

jch.github.ioRSS feed ↗136 posts

Latest posts

Meshtastic meets Tomagotchi: Long Range Mesh Radio Network

Dennis (AD6DM) told me about Meshtastic before Defcon 34, so I bought 2 little radios from the merch area to try it out. If I'm being honest, what hooked me was the adorable 3d-printed tomagotchi 1 case and the custom firmware that gives you a pet to feed and take care of. The radio is cool too! Defcon was a great place to try it out because there were nodes everywhere, making it easy to trace how…

Makecode Arcade Games on Retropie

My son has been building a game called MONKEY MADNESS GHOULS GHOSTS (beats the initial working name of "w/ dad copy") on Makecode Arcade. If you’re not familiar with the platform, it’s a block based programming environment for building 8-bit pixel-y handheld games. The drag-and-drop blocks largely solves syntax error frustrations, but it has real programming challenges like events, loops, arrays,…

Apple Numbers vs Google Sheets

I'm a big fan of spreadsheets for tracking stuff. I keep a Google sheet for each car to track spending and maintenance. Here's a copy of my 2014 Mazda 6 sheet . I track date, odometer, cost, type, and description. Type lets me categorize spending into a pivot table in a 2nd sheet, and do some arithmetic to figure out price per mile, and price per month over time. I've used Google Sheets as a…

Serverless Offline Audiobook player

I built Audiobook Player to play local and iCloud audiobooks offline 1 without any syncing 2 or importing 3 . The HTML, CSS, and JS are all in that single file, and can be hosted anywhere. Try it with this small audiobook "The Smack in School" . Concept Browsers have good support for playing audio, so the idea was to build a player out of HTML/CSS and load local audiobooks into it: <!-- choose a…

Troubleshooting NetworkManager DHCP

After migrating from Debian Bookworm to Fedora Workstation 43, I getting ip addresses from my main network. After chasing some red herrings, the culprit was my Adtrans modem/router not responding to dhcp requests with a client id. The fix was to configure NetworkManager's internal dhcp client to not send this info: # /etc/NetworkManager/conf.d/99-adtran-compat.conf [connection]…

Campfire lessons: colors, icons, and inputs

Gather around folks, here's what I learned and applied to jch.app from 37signal's "Modern CSS Patterns in Campfire", and the source code from Campfire and Fizzy. I test drove these techniques on the Holdings page. I needed these changes to be incremental and opt-in to avoid breaking other pages. Colors Up until I shipped dark mode, I used tailwind's color utilities. Tailwind does come with a dark:…

Pico CSS notes

Introduction Modules Minimal reset Customization with CSS variables Low specificity defaults Responsiveness Icons Conclusion Introduction A minimalist and lightweight starter kit that prioritizes semantic syntax, making every HTML element responsive and elegant by default. Write HTML, Add Pico CSS, and Voilà! — picocss.com I've used this project for prototyping and some personal projects I haven't…

December 2025

What I’m learning and building Learning The Easy Way to Pick UI Colors . Sajid . Good refresher on Refactoring UI book concepts. Phoenix LiveView - Interactive Apps without Javascript - ElixirConf EU 2019 . The Road To LiveView 1.0 . Chris McCord. A change in programming model. RPC hiding HTTP request-response over the network. Hotwire Turbo works are a coarser granularity (partials, pages) than…

Reducing Tailwind asset size

I consolidated the form and typography styling I was using across jch.app and reduced the stylesheet bundle size 56% from 156kb to 68k. There's more room to trim from the default reset and utility classes, but this felt like a good stopping place. $ find public -name '*.css' | xargs du -sh | sort -n # Baseline 2058e695fa2cac93a48520e85f6ae6d4f2846345 4.0K…

Quiet, but discoverable

When a new iOS lands, a handful of features are highlighted after the phone restarts. The majority of changes are slowly discovered over several days; Frequently delightful like finding spare change in the couch, sometimes gross like finding stale cereal in the couch. The most common interactions do not justify additional explanations. Take the new liquid glass UI: I use it based on previous…

No CSS, No Javascript. Longevity on the web.

Remember CSS Zen Garden ? It started out as a site to allow designers to showcase how semantics (HTML) and visual design (CSS) can be separated. I remember being blown away by the early designs, but then being disappointed by the mess of relative and absolute positioning. Remember jQuery ? The interface was so simple, and it proliferated so many plugins that followed a similar design. These were…

SSL for local Rails development

Works with https://localhost:3000 or https://custom-hostname.local No insecure site warning because certificated is signed by a system trusted CA My goal was to test jch.app serviceworkers with different devices on the same network. While localhost is an exception allowed for serviceworkers, all other origins require a no-warning https connection. This meant the certificate must be signed with a…

Japan 2025

This was our first trip to Japan. Max was 10 years old and Stella was 5. August was hot and humid (26c/80f), but we were lucky and arrived a week after a heat advisory. Occasional showers helped with temperatures and our kids enjoyed splashing around. The highlight of the trip was having Big Max and Rin show us around Tokyo. Stella now calls them “our Japanese best friends”. I enjoyed all of the…

Fugit gem: defining recurring tasks for background jobs

Fugit parses cron syntax and various time and date strings. It is a dependency of solid_queue and good_job. I didn't notice it at first because I was using cron syntax directly for my side project . Personally, I don't mind cron syntax, but it turns out fugit also supports time zones. I have some tasks that run on Eastern time based on the stock market. Previously, I had two separate cron entries…

HENRY's Guide to Taxes, Loans, and Cars

I shared my personal experiences with these personal finance topics with r/HENRYfinance. This brings them all together and links to the comments. HENRY stands for High Earner Not Rich Yet. I also keep notes about personal finance in jch/personal-finance and build a investment and spending tracker at jch.app . HENRY's Car Buying Guide I love cars, and have seen a few posts and comments in this sub.…

CSS Fragmentation issues with multi column

After reading When and How to Use CSS Multi-Column Layout , I tried to use it for a masonry layout for the jch.app dashboard. While there is a grid-template-rows: masonry , it is not widely available in 2025. I wanted a single column of items on mobile, and 300px wide columns added as the viewport allowed. Items would have auto width and height, and allow the columns to determine width: #…

Monthly finance night

A tidy home is easier to clean. If there are legos all over the floor, I have to pick them up before I can vacuum (or get stabbed walking around). If there are dishes on the table, they need to be loaded in the dishwasher before that dried pesto stain can be wiped off. Similar to physically tiding up, I like to keep our finances tidy too. But unlike the necessary daily tidy in my home with two…

My computers 2025

The last time I was excited about hardware was 2008 , when I spent hard earned Starbucks money to buy a 12" iBook G4. Since then, I've been spoiled by company provided Apple laptops at each place I've worked. They've all been beautiful machines, each faster than the last. What's missing in each of those was a sense of excitement and pride in ownership. I stopped maintaining my own home server,…

Sourdough

During covid, I ran out of yeast. I tried to make my own sourdough starter from flour and water, but ended up with a hoochy moldy mess. Fast forward a couple years, and my friend Sarah was kind enough to share her starter, idiot-proof care instructions, and a recipe. I named the starter Lemmy, because it was lemon-y fresh. The first few loaves were edible, but definitely looked "homemade" in the…

Popovers and Anchor Positioning

Popover made it into baseline this year. I wanted to use it to replace the dialog modal sheet I was using because it comes with soft dismiss built-in. Initial testing was good, but iOS Safari hangs if there is an input within the popover. The fix is slated for 18.4 in April 2025, so I will stay with dialog until some time after that. popover:…

Ruby HTTP Streaming Revisited

This morning I read "You might not need Websockets". It reminded me of the rack-stream gem I wrote 13 years ago to explore the HTTP Streaming API. Here are changes since I last looked at this topic: Rail's ActionCable abstracts websocket connection management Turbo streaming can work through websockets or eventsource ActionController::Streaming can stream layouts earlier with some caveats around…

iOS Progressive Web App Dialog bug

This was a fun one. I'm adding a modal dialog to quickly add a number of stock shares. On iOS mobile safari 18.3.1, the form in the modal freezes up after the date picker makes a selection. It's as if the modal itself becomes inert . My hack workaround is to scroll the page by a pixel when the input loses focus onblur . This seems to unfreeze the page. Note this hack is only needed if a page is…

Dark mode and RSS

Bit of clean up for the blog. I've cleaned up the CSS to use browser defaults for the most part. Instead of using hex colors, I used named colors and chose colors that work well on a light and dark background. For dark mode, I used black for OLED screens instead of a dark gray. body { max-width : 80 ch ; padding : 1 em ; margin : auto; } . highlight pre { border-left : 2 px solid palegoldenrod;…

Environmental Hardware Upgrades: iPhone 16e

Apple announced the new iPhone 16e today. It's basically the same formula they used with their SE lineup for years. The latest processing power, in an older body style (XR?), with slightly worse camera to avoid cannablizing sales from the flagship phones, for a lower price. I've been a fan of the SE line up for years because I like how smaller phones fit in the hand, starting with my first iPhone…

Building an accessible search page with progressive enhancements

This article documents what I learned building a search page for stocks, ETFs, and other securities for https://jch.app . I start with built-in HTML elements and attributes for accessibility, add CSS for layout and visual design, and progressively enhance the user experience with Turbo, and one line of javascript. At the end, I'll talk about some experiments that led to dead ends and share…

Self hosting Rails on a Mac

https://jch.app now runs on a 2016 Macbook Pro with i7-6920HQ CPU @ 2.90GHz, 16GB DDR3 ram, and 2TB SSD. I feel warm and fuzzy keeping this 8 year old mac out of the e-waste bin. The app is low traffic, but it nonetheless strained the $7/month Digital Ocean VPS. While it's no Apple silicon or raspberry pi in efficiency, the laptop draws a modest 15-20W, translating to ~$3/month at my utility…

Progressive HTML Currency Input

When entering a money value, I like to see the number in my locale. For example, 1000000 is easier to read as $1,000,000 , but a German would expect 1.000.000 € . There are many different approaches spanning a decade+ of internet posts. This post talks about the difference approaches and tradeoffs, and what I implemented for my app https://jch.app . Context In my app, users can enter a financial…

Things I learned this week

View transitions and Turbo https://jch.github.io/turbo-cancel-view-transition/ . Better understanding of Turbo lifecycle events. cwebp to convert png to webp, decreased homepage carousel image sizes by 40%. https://developers.google.com/speed/webp/docs/cwebp Server timing API https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Server_timing Pwa asset generator for iOS splash screens.…

Customizing Turbo lifecycle events: iOS swipe view transitions

View transitions became available in Safari 18. Turbo had support for this, and the default experience is great in other browsers. There's a small hiccup that the default iOS and Safari browser back/forward swipe navigation animations cannot be disabled, leading to a double animation: The intuitive turbo lifecycle callback to fix this would be to customize turbo:before-render . However, that's too…

Bake-off: It's Performance Week!

tl;dr I spent a week to bring https://jch.app from 3s+ pages to ~150ms at the 99%tile Back again after my earlier rant about deployments this week. I run a personal project for tracking my investments at https://jch.app . Once I was happy with the functionality, the slow page loads became annoying. I hope this random collection of notes and search keywords helps another solo dev out there. puma: I…

Personal project deployment is both harder than easier than ever

Played with kamal for the first time today to deploy my app to a digital ocean droplet. No problems with the interface, but so many layers to the actual machine and a pain to debug at each layer. Ruby, docker, registry, vm, actual hardware. Nice that kamal and containers provide an abstraction to where you host. All this because I didn’t want to jump to the $25/mo render plan for my personal…

Saving $1,000 in interest using money market funds to pay bills

When interest rates were near 0% between 2008 through 2020, it made a negligible difference to pay bills from checkings or savings. But with savings rates hovering around 5%, it's worth about $1,000 per year for the $20k we keep in cash for paying bills. I made a simple system for paying bills that would follow interest rates, and reduces taxes on the earned interest. Use brokerage as a payment…

Ruby timezones and testing

I had a test in jch.app which fetched data by date, and kept being off by one day. The minimal failing test case became: Timecop . freeze ( "2024/03/15" ) do assert_equal Date . current , Date . today end Turns out, this was documented behavior that goes way back to 2011. From the timecop README: Sometimes Rails Date/Time methods don't play nicely with Ruby Date/Time methods . Be careful mixing…

Managing Rails client side navigation state

tl;dr: Over thought it. Session made it obvious and simple class HoldingsController < ApplicationController def index session [ :period ] = params [ :period ] end end # in the navigation partial <%= link_to "Holdings" , holdings_path ( period : session [ :period ] ) %> In my app, url path params maintains navigation state. For example, to see investment performance of holdings, there are links…

Skip tailwindcss build if already running

This speeds up tests and other rake tasks. Not a big savings (~1s on my tiny app), but takes out the extra tailwind build step if it's already running. # end of Rakefile if Rails . env . test? && IO . popen ( 'ps' ) . readlines . any? { | l | l . include? ( 'tailwindcss -i' ) } puts "Rakefile: tailwindcss:watch running, skipping tailwindcss:build" Rake :: Task [ "test:prepare" ] . prerequisites .…

Rails 5 Production Database Protection

Rails 5 prevents bad things™ from happening to production databases: $ RAILS_ENV=production rake db:drop rake aborted ! ActiveRecord::ProtectedEnvironmentError: You are attempting to run a destructive action against your ' production ' database If you are sure you want to continue, run the same command with the environment variable DISABLE_DATABASE_ENVIRONMENT_CHECK=1 Protected environments are…

How to sort GitHub notifications in Gmail

Too many GitHub notifications in Gmail? This post gives an example of how to use Google app script to automatically add labels like "Direct mention" and "Participating" to threads you care about. The script My script builds upon Lyzi Diamond's post "Manage GitHub notification messages in Gmail with Google Apps Scripts" . The full API reference for Gmail is available at…

How to have an effective one on one

I have half hour weekly one on one meetings with my manager, Dave. We usually talk about work, life, and career goals. Before these meetings, I felt uncomfortable to give or ask for feedback without implying something was wrong. These regular and short meetings also allow us to highlight achievements, and to bring up small concerns before they grow into large ones. Although we meet regularly, here…

Building reusable Ruby scripts

Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface. -Doug McIlroy, Inventor of Unix pipe This post illustrates some of the ideas behind the Unix philosophy by composing utilities together to convert Markdown to HTML. I chose Ruby because it's a language I'm familiar with, but also to show…

Protecting Resque::Server

Resque ships with a Sinatra app that can be mounted within your application for inspecting the status of your workers and jobs. By default, there's no authentication built in. But thanks to the fact that Sinatra is rack-able, you can put a middleware in front of it for handling authentication. For example, to add HTTP Basic auth, you can use Rack's built-in Rack::Auth::Basic . HTTP Basic…

Interview your libraries

I love working with John Barnette, even if we don't work on the same stuff regularly. The other day, he wrote this in a discussion: Every time we bring in an external library, we inherit its opinions, shortcomings, idioms, and taste. This isn't a reason to rewrite everything ourselves, but it's good for us to interview libraries like we interview new GitHubbers. I loved this. In two short…

Ruby Incompatible Encoding Errors

String encodings is like air. Completely necessary and never on your mind until something goes wrong. Encoding bugs are painful and often feel like the black arts. Follow the jump for an aggregation of past experiences for solving these tricky problems. Background The first time you think of encodings in Ruby is probably the first time you see an exception along the lines of: incompatible…

Ruby on Rack

Rack is a simple and flexible library for building Ruby web applications and web frameworks. It powers frameworks like Rails and Sinatra, and can be used to build custom reusable components called Rack middleware. If you develop web apps with Ruby, you're likely using Rack even if you're unaware of it. While it's typically used to build low-level abstractions close to HTTP, knowing how to use it…

Blog Reboot, Lessons Learned

It's been four years since I initially started this blog. I had no expectations or goals for what this blog would be. But now as I read some of my earlier posts, I find it very humbling to see how I've grown as a developer, and as a person. I want to highlight a few old posts that jumped out at me when I read them. Flaco Blog In the earliest days of this blog, I think I was more interested in…

And You Thought Render Farms Were Just For Pixar!

Rails views are typically rendered after some controller action is executed. But the code that powers Rails controllers is flexible and extensible enough to create custom rendering objects that can reuse views and helpers, but live outside of web request processing. In this post, I'll cover what a Rails controller is and what it's composed of. I'll also go over how to extend it to create your own…

Building Streaming REST APIs with Ruby

Twitter popularized the term "firehose API", to mean a realtime stream of data sent through a persistent connection. But even if you're not a realtime service, streaming APIs are great for pushing data from the backend to clients. They reduce resource usage because the server can decide when it's a good time to send a incremental chunk of data. They can also improve the responsiveness of your user…

Define Custom Callbacks for ActiveRecord and More

Rails ActiveRecord models have a lifecycle that developers are allowed to hook into. But while most of us know about before_save and after_update , there are a few lesser unknown callbacks that are good to know about before you reinvent them. In this post, I'll cover all of the available ActiveRecord lifecycle callbacks, and also show how you can define custom callbacks for normal ruby objects.…

Real Web Performance for Users, not Robots

Republished from Opperator blog If I told you that a page took 130 requests to load 170 KB of data? Would you call that a slow page? This is a trick question, and the numbers I gave are a double red herring. A page is slow when it feels slow to a user. Without having opened up the page for yourself in a browser, you can't have a real answer. But if your gut reaction was "yeah, that's damn slow", I…

Faraday: One HTTP Client to Rule Them All

Faraday is a Ruby HTTP client which allow developers to customize its behavior with middlewares. If you're familiar with [Rack]( http://rack.rubyforge.org/ ), then you'll love Faraday. Rather than re-implement yet another HTTP client, Faraday has adapters for popular libraries like Net::HTTP, excon, patron, and em-http. On top of having a consistent interface between different adapters, Faraday…

Writing a Custom Rails Cache Store

When you use Rails built-in helpers for page, action, and fragment caching, the cached data is stored into an instance of ActiveSupport::Cache::Store. But while the interface for using the cache stores are the same, each cache store implementation has different performance characteristics and are suited for different jobs. In this post, I'll cover what cache stores are available with Rails by…