SwiftTip 1.5 is now available on the App Store for free.
What’s new in SwiftTip 1.5
Customize up to seven quick tip percentages, or enter any custom tip from 0% to 100%.
Choose Exact, Round Tip, or Round Total with clearer controls.
Split bills between up to 20 people while keeping the full tip and total visible.
See refreshed themes, stronger contrast, and a redesigned Settings experience.
Use improved support for larger text and VoiceOver, along with more reliable currency calculations.
I originally created SwiftTip in 2015 for two reasons—and both are in its name.
First, I wanted a quick, free way to calculate a tip. Most tip calculators felt clunky and slow, so I built one designed for just a few taps and swipes. The goal was to calculate a tip swiftly and get back to your meal.
Second, SwiftTip was one of my first chances to build something with Apple’s new Swift programming language. It was a practical way to learn the language while making an app I would actually use.
Over the years, SwiftTip has remained both a useful utility and a small experiment. I’ve updated it to try new Apple APIs, Swift language features, operating-system capabilities, and evolving iOS design patterns.
This release continues that dual purpose: a modern, fast tip calculator—and a chance to keep exploring what’s new in Swift and iOS.
Me 3.1 is now available for free. This release makes it easier to keep your contact details close at hand—and more importantly, to share the right version of them with the right person.
What’s new in Me 3.1
Saved sharing profiles let you create reusable choices such as Personal, Work, or Networking.
QR contact cards let anyone scan your selected details, including people using Android.
Send to Myself adds a Share Sheet extension for quickly moving files into a new email to yourself.
Widgets, Siri, and Shortcuts put your preferred contact details one tap—or one voice request—away.
A refreshed sharing flow, onboarding experience, and new app icon round out the update.
A small app with a long history
I first created Me in 2009. At the time, its purpose was simple: I wanted a fast way to pull up my own contact information whenever I needed it. No digging through Contacts to find my phone number, email address, or mailing address.
Over time, Me became more useful as a way to share my contact information. My contact card has a lot of data, and I do not always want to give all of it to every person I meet. Me made it possible to share only the specific fields I wanted: perhaps a work email address and phone number, but not a home address or other personal details.
Apple has since brought selective contact sharing into iOS, which is genuinely useful. In a sense, Apple “Sherlocked” one of Me’s best features. But sharing is still contextual, and Me 3.1 improves on that idea with saved sharing profiles. I can now set up a Personal profile, a Work profile, or any other combination of contact details, then choose the appropriate one without rebuilding the selection each time.
Beyond AirDrop
AirDrop is a great way to share a contact between Apple devices, but it does not help when the person standing in front of you uses Android. Me 3.1 adds QR contact cards so you can share your selected details with anyone who has a QR code reader. They scan the code, add the contact, and you stay in control of what was included.
The underlying idea has not changed since 2009: your contact information should be easy to reach and easy to share. Me 3.1 simply gives that idea more useful, more private, and more flexible ways to work.
One of the small things I started noticing while traveling around Mongolia was that vehicle license plates came in several different colors. Most were white, but we also passed cars and buses with yellow, dark blue, red, green, and black plates.
The colors are not decorative. In Mongolia, the background color tells you what kind of vehicle you are looking at—or who owns it.
The quick guide
Plate color
What it means
⚪ White
Regular vehicles
🟡 Yellow
Public passenger transportation, including buses and taxis
🔵 Dark blue
Vehicles owned by the national or local government
🔴 Red
Diplomatic vehicles
🟢 Green
Qualifying electric vehicles
⚫ Black
Specialized technological-purpose vehicles
White: regular vehicles
White plates with black characters are the standard plates seen on most cars, motorcycles, and trailers. Calling these “private” plates is close, but “regular” is more accurate: the category covers ordinary vehicles that do not belong to one of the special groups.
A typical plate contains four numbers followed by three Cyrillic letters. On the plates we saw, the first two letters identified the registration area. Several combinations—including УБ, УН, УА, УЕ, and УК—were used for Ulaanbaatar. БӨ identified Bayan-Ölgii, ХӨ Khövsgöl, and ӨМ Ömnögovi.
Modern plates also carry two small national identifiers: Mongolia’s red Soyombo symbol on the left and an oval MNG mark between the numbers and letters.
Yellow: buses and taxis
Yellow plates with black characters identify vehicles used for public passenger transportation. That includes city and intercity buses as well as licensed taxis.
The distinction is narrower than simply “commercial vehicle.” A business-owned car or an ordinary cargo truck does not automatically receive a yellow plate merely because it is used commercially.
Mongolia’s vehicle-registration rules even account for a vehicle entering or leaving public passenger service: it can keep the same number and letter sequence while exchanging the yellow plate for a regular one.
Dark blue: government-owned vehicles
Dark blue plates with white characters identify vehicles registered as property of the national or a local government.
Mongolia began changing these vehicles to dark blue plates in 2023. The stated idea was to make publicly owned vehicles easier for everyone to recognize, discourage their use for personal errands, and improve public oversight.
This makes the color more than an administrative classification. It lets anyone standing on a street see when a vehicle belongs to the government.
Red: diplomatic vehicles
Red plates with white characters belong to diplomatic missions and qualifying diplomatic personnel. These plates use the Cyrillic prefix ДК, followed by four numbers.
The first pair of numbers can identify the country or organization. For example, published lists have used 19 for the United States and 32 for the European Union.
Green: electric vehicles
Green plates with white characters identify qualifying electric vehicles.
This category was particularly timely during our trip. In May 2026, Mongolia’s Ministry of Road and Transport clarified that green plates would be issued to battery-electric vehicles and range-extended electric vehicles. Conventional hybrids and plug-in hybrids do not qualify under that policy. The ministry specifically named common Japanese imports such as the Toyota Prius and Aqua as vehicles that would not receive green plates merely for being hybrids.
Qualifying green-plate vehicles are also exempt from Ulaanbaatar’s license-plate-based driving restrictions, giving drivers another reason to choose an electric vehicle.
Black: technological-purpose vehicles
Black plates with white characters are used for specialized technological-purpose vehicles and machinery.
Some informal explanations describe every black Mongolian plate as military, but that is not accurate. Mongolia’s civilian plate standard assigns black to the technological category, while vehicles used by the armed forces, border troops, and internal troops are governed separately.
A small system hiding in plain sight
Before learning what the colors meant, I saw them as just another unfamiliar detail. Once I understood the system, traffic became a little more readable. A yellow plate was carrying passengers. A dark blue plate belonged to the government. A red one was connected to a diplomatic mission. A green one was in the qualifying electric-vehicle category—not just one of Ulaanbaatar’s many imported Priuses.
It is a simple visual language, and once you know it is there, you start reading it everywhere you go.
If you’ve been using DEVONthink Pro for a while, chances are you’ve accumulated thousands of documents with a naming convention you’d now like to change. I recently found myself in this exact situation, and DEVONthink’s built-in Batch Process tool saved me hours of manual renaming.
My Use Case: Switching to ISO Date Format
For years, I named all my notes and documents using the format YYYYMMdd-title, like 20250101-notes. It worked fine, but I eventually decided to switch to the ISO 8601 standard format: YYYY-MM-dd-title (e.g., 2025-01-01-notes). The hyphens between date components make it much easier to read at a glance.
The problem? I had thousands of documents in the old format.
Why Not Use Name Mangler?
On macOS, I typically use Name Mangler for batch renaming files in Finder. It’s a fantastic tool for filesystem operations. However, DEVONthink manages its own database, and renaming files through Finder doesn’t work the same way. I needed a solution that worked directly within DEVONthink.
Enter Batch Process.
What is Batch Process?
Batch Process is a powerful built-in tool in DEVONthink Pro that lets you apply a sequence of actions to multiple selected items. You can create reusable configurations for tasks you perform frequently, including renaming files using regular expressions.
Creating a Batch Process Configuration
Here’s how I set up a configuration to convert my date format:
Step 1: Select Files and Open Batch Process
Select one or more files in DEVONthink that you want to rename
From the menu, choose Tools > Batch Process > Batch Process…
Step 2: Create a New Configuration
In the Batch Process dialog, click the + button at the bottom left to create a new configuration
Give it a descriptive name (I called mine “ISO Date”)
Step 3: Configure the Actions
You’ll need two actions to perform the rename:
Action 1 – Scan the filename: – Set the first dropdown to Scan Name – Set the second dropdown to Regular Expression – Enter the regex pattern: ^(\d{4})(\d{2})(\d{2})(.*)$
Action 2 – Change the filename: – Set the dropdown to Change Name – In the “to” field, enter: \1-\2-\3\4
Understanding the Regular Expression
Let me break down how this regex works:
Pattern
Meaning
^
Start of the filename
(\d{4})
Capture group 1: exactly 4 digits (year)
(\d{2})
Capture group 2: exactly 2 digits (month)
(\d{2})
Capture group 3: exactly 2 digits (day)
(.*)
Capture group 4: everything else (the title)
$
End of the filename
The replacement pattern \1-\2-\3\4 then reconstructs the filename: – \1 = the year (2025) – - = literal hyphen – \2 = the month (01) – - = literal hyphen – \3 = the day (01) – \4 = the rest of the filename (-notes)
So 20250101-notes becomes 2025-01-01-notes.
Running the Batch Process
Once your configuration is saved, using it is simple:
Step 1: Select Files to Rename
Select the files you want to convert. You can select multiple files, an entire folder’s contents, or use DEVONthink’s smart groups to find all files matching your old naming pattern.
Step 2: Apply the Configuration
From the menu, select Tools > Batch Process > ISO Date (or whatever you named your configuration).
DEVONthink will process all selected files instantly.
Tips for Using Batch Process
Test first: Always test your configuration on a few files before running it on your entire library
Use Smart Groups: Create a smart group that matches files with the old naming pattern to easily find all files that need conversion
Backup: While DEVONthink’s undo usually works, having a backup before major batch operations is always a good idea
Reuse configurations: Your configurations are saved and appear in the Tools > Batch Process menu for quick access
Conclusion
DEVONthink’s Batch Process tool is incredibly powerful for bulk operations. What would have taken hours of manual renaming was accomplished in seconds. If you’re managing a large document library and need to make systematic changes, Batch Process is worth learning.
The combination of regular expression matching and sequential actions makes it flexible enough to handle complex renaming scenarios while remaining accessible through a graphical interface.
I recently put together a Python script to sync my reading history between Book Tracker and Goodreads.
I use Book Tracker as my primary reading tracker. It’s a great app for cataloging books and tracking progress. But I also like to keep my Goodreads profile up to date. Books I read on my Kindle automatically show up in Goodreads, but audiobooks I listen to through Libby don’t. Since Goodreads doesn’t have a public API, the only way to sync is through CSV exports.
This script compares exports from both apps, figures out which books are missing from each, and generates properly formatted import files. It handles the tricky parts like matching different editions of the same book (which have different ISBNs) and normalizing titles that vary between platforms.
If you’ve ever tried to snag a popular California State Parks campsite, you know the frustration: spots get booked months ahead, and then a bunch of them open up only one day before the weekend. That makes it tough to plan, and it leaves campsites sitting unused longer than they need to.
The key change: refunds now reward earlier cancellations
Here’s the refund schedule the policy spells out (shared exactly so it’s easy to read):
7 or more days before your reservation starts: You get all site fees back, minus the cancellation fee.
2–6 days before: You lose the first night’s site fee, and the rest is refunded (minus the cancellation fee).
Less than 2 days before (or no-show):No refund — all fees are forfeited.
A couple important details people will miss if they don’t read closely:
There’s an $8.25 cancellation fee, and the reservation fee paid at booking is non-refundable.
“Reservation start” is defined as 12:00 PM on the arrival date (not the campground’s check-in time). To qualify for the “7+ days” full refund tier, you need to cancel by 12:00 PM exactly one week before.
Why this is a good idea
This setup gives people a real reason to cancel earlier if they’re not going to use the site. Instead of waiting until the last minute (when plans change or weather looks bad), the best financial move is to decide at least a week out — which should help campsites reopen sooner for everyone else.
What this means if you’re hunting for a campsite
If you’re hoping to catch someone else’s cancellation, the sweet spot is starting about 7 days before the date you want to go. That’s the point where many people will be motivated to cancel to avoid losing bigger chunks of their payment.
So if you want a Friday night site, start checking the Friday a week before, and keep an eye on openings through the week.
(And if you’re the one holding a reservation you might not use: canceling sooner doesn’t just help your wallet — it helps someone else actually get outside.)
If you ride your bike a lot—commuting, gravel, road, or mountain—your drivetrain is quietly wearing out beneath you. Most of that wear starts with one part: the chain.
The good news? There’s a cheap little tool that can dramatically cut your maintenance costs:
It usually sells for around $11–$16 depending on the shop.
And used right, it can easily save you hundreds of dollars over the life of a bike.
Why “Chain Stretch” Matters (and Why It’s Not Really Stretch)
Chains don’t literally stretch like rubber bands. The pins and rollers wear down over time, which makes the distance between links slightly longer. That tiny elongation means:
The chain no longer meshes perfectly with the teeth on your cassette and chainring.
Load gets concentrated on the leading edges of teeth instead of spread evenly.
Over time, the cassette and chainring teeth get “shark-finned” and worn out.
A worn chain is cheap to replace.
A worn cassette and chainring? Not so much.
Meet the Park Tool CC-3.2
The Park Tool CC-3.2 is a simple “go/no-go” gauge:
One side measures 0.5% elongation
The other side measures 0.75% elongation
For modern 11–12 speed drivetrains (like SRAM GX Eagle), most mechanics recommend replacing the chain around 0.5% wear to protect your cassette. Older 8–10 speed systems can usually go a bit further, up to around 0.75%.
How it works:
Hook one end of the tool into a link.
Try to drop the opposite end into the chain.
If the 0.5% side drops in cleanly on an 11/12-speed chain → time to think about a new chain.
If the 0.75% side also drops in → you’ve waited too long; the cassette is probably already wearing.
That’s it. No math. No measuring. Just “keep” or “replace.”
The Economics: SRAM GX Eagle Example
Let’s put some real numbers on this using a SRAM GX Eagle 12-speed drivetrain, a super common “mid-pack” group set.
Approximate street prices (US):
Part
Approx. Price (USD)
Park Tool CC-3.2 chain checker
$12–$16
SRAM GX Eagle 12-speed chain
$35–$45
SRAM GX Eagle XG-1275 cassette
$220–$255
SRAM GX Eagle direct-mount chainring
$40–$50
(Of course, prices vary by shop, sale, and region—but these numbers are a realistic ballpark.)
Now let’s look at two riders over 3,000 miles of riding.
Rider A: Uses a Chain Checker, Replaces Chains Early
Rider A checks their chain regularly. On a modern 12-speed drivetrain, a chain can often go roughly 1,000 miles (give or take—conditions matter) before hitting 0.5% wear if it’s cleaned and lubed reasonably well.
So over 3,000 miles, Rider A might use:
3 chains × ~$40 each ≈ $120
Because the chains were replaced before they got too worn, the cassette and chainring stay in good shape and don’t need replacing yet.
Total drivetrain wear cost for 3,000 miles: ~$120
Cost per mile:
120 ÷ 3000 = 0.04 → about 4¢ per mile
Rider B: Never Checks Chain Wear, Waits Until It Skips
Rider B just rides. No checking. Eventually, the chain gets so long that:
Shifting starts to feel rough
The chain skips under load
The cassette teeth are visibly hooked
At that point, you usually can’t just slap on a fresh chain—
the cassette is worn to match the old chain, and a new chain will skip.
So Rider B ends up needing:
1 new chain ≈ $40
1 new cassette (XG-1275) ≈ $240
Possibly 1 new chainring ≈ $45
Total: ~$325
Now do the same cost-per-mile math over those 3,000 miles:
325 ÷ 3000 ≈ 0.108 → about 11¢ per mile
The Bottom Line: 4¢ vs 11¢ per Mile
Rider A (with a $12–$16 chain checker): ~4¢ per mile in wear parts
Rider B (no checker, just rides it into the ground): ~11¢ per mile
There is also a higher likelihood that the chain will break before the 3000 miles, causing this expense earlier.
Even if the exact numbers shift a bit for your terrain, weight, and riding style, the pattern is clear:
Replacing chains early is dramatically cheaper than replacing chains + cassette + chainring later.
And that $12–$16 Park Tool CC-3.2 pays for itself the first time it saves your cassette. After that, it’s just making you money.
How to Use This Tool in Your Routine
You don’t need to obsessively measure your chain every ride. But building a simple habit makes a big difference:
If you ride a lot (3–5x per week):Check every 2–3 weeks or roughly every 300–500 miles.
If you’re an occasional rider:Check once a month or after a few big rides.
If you ride in mud, rain, or dust:Check more frequently; grit accelerates wear.
A quick routine:
Wipe down the chain (cleaner chain = more accurate reading).
Drop in the CC-3.2 on the 0.5% side.
Read the tool
If it doesn’t quite drop all the way in → keep riding.
If it drops in easily on a GX Eagle → order a new chain.
Want to build your own DeskHog but found the official project page lacking in instructions? I did too. Here’s the step-by-step guide I wish I had when I started—complete with photos, links, and tips.
I downloaded the .3mf file and imported it into my 3D printer software. The slicer recommended adding supports, which I did.
I used PLA+ at 0.2mm layer height
Here is what the print looked like:
The print includes 3 pieces:
Bottom case
Top case
Keypad
After printing, I removed the supports and cleaned things up with a knife and sandpaper. The hardest part was getting the keypad to fit cleanly into the top case.
Here’s the final result after cleanup.
Assembling the Hardware
Attach the LiPo battery to the board.
Place both inside the bottom case.
Snap the top case on.
Connect the DeskHog to your computer via USB-C.
Setting Up the Firmware
1. Clone the repo:
git clone https://github.com/PostHog/DeskHog.git
cd DeskHog
2. Install the UV Package Manager (if you don’t have it)
I’m using UV package manager to make this process easier. If you don’t have uv install, here is how:
This creates and activates a fast Python virtual environment.
4. Install PlatformIO, which the project uses to build and flash the firmware:
uv pip install platformio
5. Flash the Firmware
Make sure you’re in the root project folder (where platformio.ini lives), then:
platformio run --target upload
This builds and uploads the firmware using USB (with Auto-DFU support).
6. Power and charge:
To power off: Hold the ● + ▼ buttons.
To power on: Press the reset pad.
Charging is automatic when plugged into USB-C.
What It Does Out of the Box
Once booted, you’ll see:
ProvisioningCard: Shows a QR code for Wi‑Fi setup
InsightCard: Displays PostHog data
FriendCard: A sweet ‘encouragement’ screen featuring Max the hedgehog
Connecting to Wi-Fi
Once powered on, the DeskHog shows a QR code for Wi-Fi setup. Scan it to connect to your network—just pick your SSID and enter the password.
From here you can select your network and enter your password. Once connected you’ll see 2 screens. A screen with: WiFi, IP, Signal, Version and API. And a FriendCard
Add PostHog Insights
Go to the IP address displayed on your DeskHog.
You’ll see the configuration page.
Enter your PostHog Team ID (or Project ID) and a read-only API key.
Add the Insights you want to display.
And you’re done! You now have your own fully functional DeskHog.
Final Thoughts
This was a fun weekend project. If you’re already using PostHog, it’s a nice little dashboard for your desk—and a conversation starter too.
I recently put together a small Python script to automate downloading all the available Kensington Outlook newsletters from the Kensington Community Council website.
I wrote it because I enjoy reading the Outlook on my iPad. Until now, I had to go through the site and manually download each PDF. This script fetches all the PDF links from the site, downloads them to a folder on my Mac, and skips any files that are already downloaded. That folder is synced to my iPad, so I can easily read the latest issues offline.