What I'm Reading, Listening to, and Using - Spring 2019

June 12, 2019 - Category: media

I’ve listened to, read, and used a lot more than what is on this list, but this stuff is what I recommend to others. If you want specific recommendations, shoot me an email!



New Newsletters I’ve Joined

Don’t call it a comeback. Newsletters have been here for years. Here are new one’s I’ve joined:


New Tools

  • MailDev - Intercepts outgoing emails during local development and put them in an web inbox on your locahost so you can test.
  • ngrok - Secure public URLs to your local development environment. We are using it to catch webhooks from third party services while we test locally
  • Story Grid - A method for writing fiction. I’m working through the book while I write something of my own
  • CSS Grid Generator - Tool for generating CSS Grid code and learning problems with your own
  • Studio Neat’s Tote Book, a great follow up to the Panobook
  • Animated Knots - I’ve been tying a lot more knots lately with my boat building. This is a very useful resource.
  • Cape Falcon Kayak’s video courses - This is what I’ve been using to learn how to build my kayak and paddle
  • Memos - An app that adds text search to your Photos library and allows you to copy it out of photos. Indespensible.

Adding Homebrew MySQL Service to your PATH

June 11, 2019 - Category: development

If you install mysql via Homebrew ($ brew install mysql) and start it via Homebrew services ($ brew services start mysql), chances are that you’ll see an error when trying run mysql -u root on the command line.

If that error is command not found: mysql, the issue is likely that you need to add the Homebrew mysql directory to your PATH.

On my machine, Homebrew installed mysql here:


To add this to your PATH, add this to your .bash_profile (or .zhsrc if you use ZSH):

export PATH=/usr/local/Cellar/mysql@5.7/5.7.25/bin:$PATH

Then, reload your shell and type mysql -u root to confirm all is working.

You can quit the mysql CLI via mysql> \q

Getting around foreign key constraints when restoring a Craft CMS database

June 10, 2019 - Category: development

Are you getting foreign key constraint errors when trying to restore Craft CMS database backups? Here is how to solve it.

Every time I download a backup of a Craft 3 database and try to restore it to my local development environment, I get this error: Cannot add foreign key constraint

This happens because the SQL dump has the data in a different order than it should be loaded in according to the foreign key constraints.

The solution is to use a MySQL session variable to turn off foreign key constraints and be able to load your data in the order it is currently in.


WARNING: Do NOT even think of doing this in your production environment. Only development. You’ve been warned.

Open your .sql dump in your favorite text editor and put this line at the top:


Save it, then retry your import. Works for me every time.

I wrote this post so that I don’t have to search how to do it in six months when I need to do it again.

p.s. if you want to do something similar in postgres instead of mysql, search for SET CONSTRAINTS ALL DEFERRED.

How to Upgrade from PHP 5.6 to 7.2 for WordPress

March 23, 2019 - Category: development

Last week I updated three large WordPress sites for Praxis from PHP 5.6 to 7.2. Here is the process I used on all three to make it a smooth transition.

Note: I do contract work and can help you figure this upgrade out. Reach out if you are interested in getting a quote.

1. Figure out the scope of the problem.

First you need to know how much time you should plan to spend doing this work. A quick way to find the scope of the problem is to run the PHP Compatibility Checker plugin. It checks your themes and plugins, spitting out errors, warnings, and filenames/line numbers of the offending code so you know where to start.

  • Small theme upgrades?
  • Plugin updates?
  • Massive refactoring of custom code?

Even if you get a 100% clean pass with the compatibility checker, I suggest going through the rest of my list. The compatibility checker isn’t perfect. You don’t want to roll those downtime dice. Fool me one time, and all that.

2. Test the upgrade locally.

Copy down your current theme and database into your local development environment. This is important. Don’t use the version you have from work you did a few months ago. Stuff changes on the server all the time. Pull down a copy to be safe. Configure your local environment to use PHP 7.2. If you are unsure whether or not you are on 7.2 locally, you can always use a WordPress plugin like Debug Info to check. Also make sure to turn on error reporting in your php.ini.

Don’t have a local development environment yet? I like Valet.

While you are at it, I suggest upgrading your WordPress core to the latest version (5.1.1 as of this writing) and updating all of your plugins. Rip that bandaid off. On the plus side, it will probably make the PHP upgrade process easier, too.

3. Find, diagnose, and solve any issues.

This is either the easiest or hardest step. If your site looks fine and everything functions as it should, great! You won the lottery.

It is probably worth double checking that you actually are on PHP 7.2 with the debug plugin mentioned above. Also worth clicking all around your site and checking everything. Sometimes most of the site will load, but you’ll get weird inline error messages like this:

Warning: Use of undefined constant Y - assumed ‘Y’ (this will throw an Error in a future version of PHP) in /nas/content/staging/creativecourse/wp-content/themes/business-pro-theme/page-archive.php on line 70

If you navigate to the site locally and all you see is a sea of errors like I did on one site, you have some work cut out for you. Here are some tips:

  • If the errors include wp-includes or wp-admin in the file paths, you need to update your WordPress core.
  • If the errors include wp-content/themes/ in the file path, and you don’t have any custom work done on the site, the quickest fix is to upgrade your theme or find a new one.
  • If the errors include wp-content/plugins/somepluginname/ in the file path, find that plugin and disable it or update it and try again. You should still be able to get to your /wp-admin/ dashboard even with a broken site because the most recent core works with PHP 7.2.
  • If your site includes a bunch of custom code and the errors are happening with the custom code, you have more work to do. If you are comfortable debugging and fixing it, great! If not, you might need to call the person or company who wrote it. Or decide if you can do without it. Hopefully you followed conventions and put it in a child theme.

Keep track of the changes you make. I keep a running list in my favorite notes app. Theme updates, core updates, plugin updates, and code changes. Write it all down. Better yet, version control it with git and commit along the way.

If you have any premium plugins that require activation keys, make sure that you contact the developer to get two extra development keys for local testing and production. Most will provide those to you at no extra cost. Some platforms give you 5 keys per purchase anyway just for this reason. I decided to roll the dice and not get keys for one plugin, and ended up crashing my production site with the upgrade because I didn’t test that plugin. Don’t be like me.

All three of my sites needed some work done, even the newest of the three. One didn’t load at all until I made some theme updates. One plugin crashed a site and had no updates available, so I had to do without. Some custom code I had written needed updates, too. It took me a few afternoons to sort it all out.

4. Deploy to a staging environment on PHP 7.2 first.

Once you have everything sorted out locally and you’ve triple tested everything, go ahead and push your work up to a staging environment. Good hosts like WPengine make having a production and staging environment easy. You can control the PHP versions independently. I use WPengine’s git deployment to move my local updates to the staging server. SFTP works, too. If you need database changes, you’ll also need to do a database migration. Core and plugin updates will need a database migration (Don’t forget to change the site URL or you won’t be able to log in!) If you need to do a database migration, use the instructions here

My preferred workflow is to copy the full production site (code + database) to staging, push my code changes, then do the core and plugin upgrades on the server (they often involve database changes), which is when my change list above comes in handy. I don’t like doing database migrations unless I absolutely have to, because with lots of users logging in to the site, data is bound to get out of sync with my local version in a matter of hours. Getting back in sync is tricky, so I choose to avoid the problem altogether by only moving code, keeping my change file, and replaying the steps on the staging server.

If you don’t have a staging environment and can’t get one with your host, or you can’t upgrade the PHP version yourself on your host, then you need a different plan. You’ll need to set up another site on another host that uses PHP 7.2, then put the updated copy of your site there. If you do this,proceed to the alternate #5 below.

Don’t assume everything works. Test it! Use different user account levels (admin, editor, author, contributor, and subscriber). Try every critical function on your site.

Only move on to the next step when you are certain everything works like it should.

5. Deploy to production.

Before you do anything else here, make a back up of your site and your database. Good hosts like WPengine make this easy. If you are doing this manually, verify the backups work locally. You can to be able to roll back if there is an issue.

I planned a maintenance window with my users a few days ahead of time. If you have users who rely on the site, you should, too.

Update your production environment to PHP 7.2, then copy the staging server to the production server. If you are confident that there were no users updating content in between the staging copy above and now, you can copy database and all. If there are changes you don’t want to lose on the production database, you’ll need to use my method and only copy over the theme code, then do core and theme updates on the production site. This is when planned downtime comes in handy.

Alternate deploy (no staging)

If you set up a separate site on 7.2 (not a staging environment of a production site) and it works, then you are ready to cut over to the new version. Configure that new site to use your domain (URL field on the setting page), then point your domain’s DNS records at the new server. If you need to do a database migration, use the instructions here.

6. Verify everything works

Don’t assume everything works. Test it! Use different user account levels (admin, editor, author, contributor, and subscriber). Try every critical function on your site. Use the Debug Info plugin to verify that the PHP change took.

If there are any issues, you might need to roll back to an earlier restore point and PHP version (or point your domain back to your other server), or do some real-time debugging. If your production and staging servers mirror each other in every way except PHP version, you shouldn’t run into anything unexpected. If you do, figure out where the difference is and go back to #3 above.

Photos Published in Newton Graphic Science Magazine

March 21, 2019 - Category: photography

Newton Graphic Science Press in Japan published one of my photos twice in the past year. They found it via my Illum project on Flickr and requested permission to publish it.

Of course, I said yes. Check out this cool cover:

Newton Cover

Here are the spreads the sent me afterward:

Newton Cycloid 1 Newton Cycloid 2

The photo is from a high school physics contest held by the American Association of Physics Teachers where I won Second Place in 2008. Here is my original post from 2008 when I found out.

Now I’ve been published in Japan. Sweet. Remember to freely share your work. You never know who’ll end up seeing it and what opportunities might come your way.

Moving from Homestead to Valet for WordPress Development

March 20, 2019 - Category: development

I used to use Homestead for local WordPress and Craft development. It was nice because everything worked out of the box. Well, theoretically. In reality, it worked about 80% of the time, but 20% of the time I’d get cryptic errors from Vagrant like:

  • Some box with the same name exists. We won’t tell you where, but trust us, it exists and that can’t happen.
  • The SSH command responded with a non-zero exit status. Vagrant assumes that this means the command failed. The output for this command should be in the log above. Please read the output to determine what went wrong.
  • Error! You have to destroy this box and rebuild it. Hope you backed up those local databases, because they are gone forever!

It was always a rabbit hole of headaches that could take half a day to resolve. Most resolution happened in the form of me destroying a box and rebuilding it, losing my databases. So much fun.

Today I got the SSH error again and had enough. I went looking for a better solution and found Valet. Their pitch:

Valet is a Laravel development environment for Mac minimalists. No Vagrant, no /etc/hosts file. You can even share your sites publicly using local tunnels.

Sounds great. It won’t eat my battery, RAM, CPU, or take gigs of disk space for vitual machines. Sign up the heck up.

It was super easy to set up with tools I already use: Homebrew and Composer. Instructions here.

The only tricky part was making composer directory available in my Mac’s PATH, since I’ve only ever used it inside individual projects. Turns out with zsh you have to give the explicit path, you can’t just use ~/, so I added this to my .zshrc: export PATH=/Users/cagrimmett/.composer/vendor/bin:$PATH

Once I loaded the profile with source ~/.zshrc, I was good to go. I linked my main projects folder so Valet knows where to look for projects, changed my wp-config.php files to point at my local mysql database instead of Homestead’s, then navigated to project-folder-name.test and was good to go!

Valet runs at startup, so I don’t have to run cd ~/Homestead && vagrant up anymore, just brew services start mysql@5.7 if I need a database running (which you usually do for WordPress development.)

I like that Valet makes it so easy to use different PHP versions. For example, to use 7.2, you go to your command line and type: valet use php@7.2

I’m looking forward to never using Vagrant or VirtualBox again. Peace out 👋

Doing a Digital Declutter

March 19, 2019 - Category: Life

I recently read Cal Newport’s Digital Minimalism. I was more or less convinced before I picked the book up that I’m wasting too much time on social media and my ability to focus is becoming fragmented. I read this to hear his solution to this issue: Ruthlessly cutting out digital tools that don’t bring you value.

I’d consider myself, in Newport’s parlance, a digital maximalist. I have accounts on every major platform, am an early-adopter of new digital platforms, social networks, and tools, and evangelize tools I find useful. My always connected.

While I generally love this, I’ve noticed that my ability to focus for long periods of time and to sit and observe without needing a distraction (my phone) has diminished significantly. This is a problem. I value my observation abilities and don’t want to lose them. I also have so many things I want to do: Blog posts to write, spoons to carve, places to explore, etc.

  • How much am I missing by mindlessly scrolling through my phone?
  • What could I be doing instead of scrolling?
  • How much value am I getting from the scrolling anyway?

Following Newport’s advice for doing a digital declutter, I took stock of what I think is causing me the most harm: Mindless scrolling. Here’s where it shows most:

  • Computer
    • Reddit
    • Twitter
    • Hacker news
    • Kottke
    • Daring fireball
  • Phone
    • Facebook
    • Twitter
    • Reddit
    • Instagram
    • Email
    • Slack
    • Hacker news
    • Kottke.org
    • Daring Fireball

Only particular cycle that is bad for me is checking Slack, then my email, then Twitter, then Slack, then my email, then Twitter in an endless loop on my phone. I have no idea why I do this. Sometimes I’ll zone out and do this for 15 minutes straight without seeing anything new. Crazy.

I saw that my friend Chris Johnson is doing a digital declutter, so I decided to join him.

Here is my Digital Declutter plan for the next 30 days:

Removing Distractions

  1. Use Focus to block all social media on my Mac.
  2. Use 1Blocker and Screen Time to block all social media in Safari on my iOS devices.
  3. Remove the native social media apps from iOS and macOS.
  4. Reorder my home screen to be productive apps: Writing and journaling apps, workout apps, reading apps, weather, and train/subway schedule apps.
  5. Move work-related and email apps to a folder on the farthest-right screen. The idea is that I need to have them for the workday, but I need to take a deliberate action to get to them. This keeps me from doing the Slack > Email > Slack > Email cycle.
  6. Using the Downtime feature in Screen Time to block access to everything but writing and journaling, iMessage, and Phone between 8pm and 7am.
  7. Removing everything except writing, journaling, coding, and drawing apps from my iPad. Keep the iPad a dedicated device for creation and reflection.
  8. As much as possible, leave my phone on the bookshelf by my desk. This means it is out of my line of site while I’m at my desk, but if I get an important text message, I’ll hear it.
  9. Turn off notifications for everything except Slack, Voxer, and iMessage. The essentials I can’t miss. Pare down the notifications in Slack to just direct messages and @mentions. Channel posts can wait.
  10. I’ll monitor my pickups and phone usage with Screen Time.

Replacing with valuable activities

  1. Instead of scrolling through my phone immediately after I wake up, instead go work out first thing, then make my list of what I want to accomplish for the day.
  2. While at work, focus on work. Don’t take social media breaks. If I need a break, I can go for a walk or write.
  3. Spend my evenings cooking, writing, reading, and spending time with Amanda.
  4. Keep a running list of productive things I can do when I feel the urge to zone out and scroll:
    • Write (cooking blog, journal, main blog, Crash blog)
    • Carve
    • Sharpen my carving knives
    • Take photos
    • Read
    • Draw
    • Go for a walk
    • Listen to an audiobook and clean the house
    • Find a new recipe for my Cooking the Books challenge
    • Sleep
    • Spending time with friends
    • Exercise

You’ll probably still see my post my blog posts on social media, but I’ll be doing that through Buffer so I stay away from the feeds. I still want those readers, you know.

After 30 days I’ll reintroduce some things back into my life, but probably not all, and I’ll probably slim things down quite a bit. Probably some Instagram and Twitter, but restricted times. I’ll probably keep the main phone restrictions. We’ll see.

I’m looking forward to this. I want my attention, observation, and creativity back. And along the way, my happiness.

Setting up a New Mac from Scratch

February 4, 2019 - Category: Tech

My trusty 2013 MacBook Pro died over the weekend. Here is everything I installed and configured on my new machine to get it up to speed. Setting up a new machine from scratch is a great way to clear the cruft that inevitably builds up over time.

I’m sure I’ll find a few more things in the next month that I forgot, but this is ~95% complete.

  • 1Password - Without this I wouldn’t be able to get in to anything. First app I installed.
  • Dropbox - File transfer was going to take a while and I needed it for syncing data for other apps below.
  • Setapp - One of the best purchases in the last few years. One subscription for tons of apps. Below is what I installed right away.
  • Drafts - I installed the Mac beta. All of my text starts here.
  • Things - My current to-do list
  • Soulver - Best Mac and iOS calculator
  • Tweetbot - My fav third-party Twitter client
  • Copay - Bitcoin wallet
  • Pixelmator Pro - I’ve completely stopped using PhotoShop and use this instead.
  • Trello - Project management. I prefer the desktop app to the web client because I like standalone apps.
  • FruitJuice - Gotta keep your laptop battery healthy.
  • Amphetamine - Keeps your Mac awake when it needs to do things like download your entire Dropbox. Better than Caffeine, the alternative.
  • Slack
  • DaisyDisk - Visualizes your disk space and helps you find giant files
  • The Unarchiver - Installing a lot of new stuff means unzipping a lot of files. The Unarchiver is the best at it.
  • Hazel - Automated organization. Renames files I download and stores them automatically, dumps the trash, organizes my desktop, etc.
  • RescueTime - Time management software that I’ve used since 2010.
  • Keybase - Security and identity
  • Notion - Project management, documents, collaboration, publishing. Basically runs my work life.
  • Zoom - Video conferencing
  • Rocket - Slack-like Emoju picking for your entire OS 🚀
  • TextExpander - Text snippets + AppleScript automation kickoff
  • CarbonCopyCloner - I set up weekly drive clones to external harddrives. Saved my bacon multiple times. I didn’t lose a thing this time because I backed up the night before automatically.
  • Backblaze - Offsite backup. Critical.
  • WordPress - I like managing all of my sites from this single app.
  • Adobe CreativeCloud - Primarily for Typekit
  • Sketch
  • Spotify
  • Turbo Boost Switcher Pro - Toggles the Turbo Boost feature on your CPU for better battery life.

Chrome Extensions

  • Zoom video conferencing - Changes all Google Hangouts links to Zoom links
  • Wappalyzer - Figuring out what stacks other sites are using & figuring out whether or not things I install are actually activated
  • DuckDuckGo - My default search engine
  • Ghostery - Death to all tracking scripts
  • Stayfocusd - Blocks social media during the workday
  • What Have You Made Today? - My favorite new tab screen
  • Larder - My bookmarking of choice

Development Tools

Misc Settings and preferences I set

  • Hot corners - Can’t live without them since 10.4
  • Generated new SSH keys
  • defaults write com.apple.finder AppleShowAllFiles YES - Gotta see those hidden files
  • Apple’s enhanced dictation - The extra suite of dictation tools
  • True Tone on
  • Night Shift on

How I Approach Social Media

October 22, 2018 - Category: thoughts

On a recent episode of Office Hours, a listener asked about the purpose of social media. Isaac and TK recommended taking a pragmatic approach. Here is my take on what that looks like.

How I approach social media in general

  • Consumption and projection, not discussion - I find things people share and share my own work and things I find interesting, but I very rarely participate in discussions. Meaningful discussion is very difficult online, especially on social media. I prefer to take discussions to more direct, personal mediums like Messages, Slack, and Voxer. I prefer in-person when that is possible.
  • Sparingly and intentionally - I’m not a Waldenponder. I get a lot of value out of social media. But I don’t recommend indiscriminately spending time on it, either. It is designed to keep you there so they can serve more ads to you. It is inherently manipulative. Some (like Twitter) are so full of negativity that you’ll get worked up if you aren’t careful. Photo-heavy services distort reality and have the unfortunate effect of making you feel like your life sucks. Social media is a dangerous place, so make sure you engage sparingly and intentionally. Know what you want to get out of it. Put blockers in place so you don’t spend more time than you pre-determine is worth it for you. I recommend 1Blocker (Safari on macOS and iOS) and StayFocusd (Chrome). Remove the apps from your phone (best) or at least put in limits with iOS 12’s new Screentime feature.
    • I prefer to replace social media time with reading and podcasts as much as possible.
  • Whenever possible, post from a third-party service - Most of my Facebook and Twitter posts come from Buffer. This keeps me from having to log in or have the service’s apps on my phone.

Here’s how I use the major social channels:


I like Facebook less every week. I only hop on a few days a week now. The content there is mostly trash. I go on to keep up with friends from high school and college, as well as family. I rarely comment and I almost never engage in a discussion there. It isn’t as toxic as Twitter because you generally have closer ties with someone involved in the thread on Facebook, but it is still usually bad.

I think people waste too much time on Facebook unintentionally and would do well to delete the apps from their phones and only check it from one specific device that you use only as a secondary or tertiary device. For me that is my iPad. I keep Facebook blocked on my computer and my phone to reign in my unintentional time wasting.


Twitter is my second favorite social service to browse. It is where I get a lot of recommendations, find out about new apps, and get my news. I don’t read traditional news outlets unless I find an interesting story linked on Twitter or a blog I read (see below).

I curate who I follow pretty regularly, so I have a pretty good “content to garbage” ratio, or at least one I’m willing to tolerate enough to check out during breakfast and lunch.


I still want something like Mastodon to take off, but I haven’t found any communities that are active enough to invest in. I prefer using the internet as a place to consume (find recommendations, keep up with what friends and family are doing, learn new things) and project (write and share my own stuff), but not converse. Most of the internet is a terrible place for conversing. It just isn’t set up for that. Perhaps Mastodon can fill that gap if I find the right community?

I’m trying out https://refactorcamp.org right now and having a pretty high hit rate of good content. Still not great for discussion, though.

If you have any Mastodon communities you recommend, I’d love to hear about them.


I’m photographer. I love posting to Instagram. It is probably my favorite social service to browse, too. So much good stuff in my feed! That said, it is the one I’m most likely to waste too much time on because I like it so much. So I delete it from my phone most of the time and only download it when I want to post to it, keep it around for a few days with 15 minute time limits set with Screentime, then delete it until I want to post again.


Oh, Reddit. I want to love you, but I can’t. The comments are so toxic, even in decent subreddit communities. Every subreddit I start getting involved in inevitably devolves to inside jokes, gatekeeping, and beginners asking the same question covered hundreds of times. (On the Kombucha sub, it is always “is my scoby okay?!?!). It gets tiring.

I love reading AMAs, but I never get there in time to ask a question. And when I did ask questions in AMAs a few times, I got banned for asking for a month because I asked the same question each time: What are you reading right now? Apparently that isn’t allowed.

Also, the search is completely terrible. There is probably tons of useful stuff locked away in threads that no one can find, forever lost to the ether.

I’m over Reddit. My RescueTime stats show that I visit the site less and less each year.


I don’t use Pinterest. I can’t reliably find anything in that awful sea of ubiquitous images. You have to sift through a pile of garbage to find one useful thing. I prefer to avoid the whole mess in favor of other services. I use http://Are.na as a personal pinboard.


I only watch YouTube videos I find embedded elsewhere or that someone sends me. I can’t remember the last time I went directly to YouTube.com to just see what was happening. I don’t like the video medium unless I’m trying to learn something, and I tend to find those videos through search engines. I don’t watch YouTube videos recreationally.


This is too small right now to be super useful, but I’m hopeful for it. A service dedicated to book, show, and restaurant recommendations. Requests for this sort of thing on regular social media tend to get lost in the sea of other garbage and algorithmic timelines, so people often don’t respond until days later. Likewise keeps these asks front-and-center. You should join me on Likewise! https://likewise.com/invitedby/5bbe223985965466d44255eb

Hacker News

Great tech news source. I don’t participate in the comments/community there. I’ll often click through to the comments section to get a tl;dr of the article or get hot takes on current events. I find a lot of products and tools here that I bookmark and end up using or recommending later.

Product Hunt

There was a period where I checked Product Hunt daily and found a lot of cool stuff there. Now I check it maybe once a week and only find a fraction of the cool stuff I once found there.


I hate it. I refused to be on it for years. I have a profile now to set a good example for Praxis participants, and I may even cross post one of my articles there, but I get very little value from the service.


I went through a phase where I answered questions on Quora, but I got bored by it pretty quickly. I didn’t invest enough to get over the hump and get a large return, so it just felt like I was wasting my time. Plus, there are so many shitty answers on there by people who just spend all day answering questions they only know a little bit about. Costless question asking and costless answering lead to a pretty low quality of content. The early days were cool because it was costlier to be in a small community like that instead of elsewhere. But now it sucks.

Stack Overflow

I’ve never asked a question on SO, but I sure am glad it exists. I’ve had dozens, maybe hundreds, of questions answered by previously existing questions there. I have chipped in and answered some questions there, but I don’t make a regular habit of it.

Where else do I get media?

  • Blogs
    • marginalrevolution.com
    • kottke.org
    • daringfireball.net
    • macstories.net
    • ribbonfarm.com
    • complete-review.com/saloon
    • stratechery.com
    • lesswrong.com
    • seriouseats.com
  • Podcasts
    • Conversations with Tyler
    • Data Stories
    • Design Matters
    • The Knowledge Project
    • Longform
    • Mac Power Users
    • The Memory Palace
    • Presentable
    • Recode Decode
    • The Speakeasy
    • The Talk Show
    • Waking Up
    • Cortex
    • Hello Internet
    • Thoroughly Considered
    • Planet Money
    • This American Life
    • Office Hours
    • Serial
    • Accidental Tech Podcast
  • Newspapers and magazines
    • I don’t read newspapers often, but I occasionally like to pick up a NYTimes or WSJ on the weekend and spend an hour going through it.
    • I like reading the short fiction in The Atlantic and The New Yorker. We don’t subscribe to them anymore, but I’ll pick one up at the news stand in Grand Central occasionally.
    • I read Lucky Peach for a few years until they shut it down. I loved it. I’m looking for a replacement. Any recommendations?
  • Newsletters
    • Breaking Smart
    • Ryan Holiday’s Reading List
    • Studio Neat Gazette
    • MacStories Weekly
    • Tim Ferriss’s 5-Bullet Friday

What work/life balance means to me

October 11, 2018 - Category: thoughts

I have a certain capacity for creative output. That level may increase or decrease over time, but it stays relatively constant day-to-day.

You can think of this capacity as tokens that I have available to spend each day. I can either spend these tokens at my full-time job, at a side gig, or on a personal project.

I feel most balanced when I use 80% of my creative capacity at my full-time job and 20% elsewhere.

When I use 100% of my capacity at my full-time job for an extended period of time (say 2 weeks or more), I feel unbalanced. My overall creative capacity starts to decline. Some might call this feeling burned out.

When I use more than 20% on personal projects or side gigs (i.e. less than 80% at work) for more than two days in a row, I feel unbalanced, like I’m neglecting my work responsibilities. Like I’m falling behind and my output isn’t up to par.

I’ve never taken complete breaks from creating things. The manifestation just tends to shift. On vacations I tend to pick up photography and journaling to fill the creative gap. Sometimes drawing. During the holidays I tend to make more elaborate meals and try making new cocktails.

I’ve also never shifted 100% of my capacity into personal projects for an extended period. I haven’t been unemployed for more than a week in the past 7 years. Vacations are breaks from personal projects as much as traditional work, so that is why the output tends to shift to photography, journaling, and drawing.

I routinely go 3-4 weeks at a time at a 95/5 split on work/personal. Those times my personal creative output tends to be listening notes from podcasts and cooking. Days during high work periods where I manage to put out a longer blog post, I’m almost certainly eating leftovers or takeout. (Tonight, for instance: 3 blog posts plus curating a bunch of book recommendations on Likewise and I ate leftover soup for lunch and made a taco salad from leftovers in the fridge for dinner.)

I radically cut down the amount of side gigs I take on in order to prioritize personal projects. In fact, I have no side gigs going on at the moment.

What would my creative output look like when focused 100% on the personal side? I haven’t experienced that since high school and college, but the photography projects I focused on during those periods still rank among what I consider my best. Even periods where I’ve shifted to a 20/80 split on work/personal resulted in projects I’m proud of and look upon fondly.

In the next few years, I’d like to take a complete month away from full-time work and focus on personal projects for the entire time. Deliberately throw myself out of balance in a way I’m not used to and see what I create.

Celebrating 10 Years

June 22, 2018 - Category: news

I started this blog ten years ago today. I was in a room at the Doubletree in Tarrytown, NY. It was a Sunday and it was the weekend in-between my first FEE seminars. I ordered a pizza and some ziti from Capri Pizza and decided to tear down my old HTML site and give blogging a try. I figured out how to install WordPress 2.5.1 and went to town. Here is what the site looked like:

original blog

Here is the post that started it all.

Since then, I went on to work at FEE and move to NY, passing that Doubletree regularly. I blogged for a year straight. I’ve lived in 5 different cities and posted from every single one. I’ve written 763 posts on this blog since then and I average about 20,000 unique visitors a month according to my server logs.

The blogging has paid off. Here are things that have come from my blogging and showing my work:

  • Photography gigs
  • Selling my photos
  • Photos published in dozens of newspapers, magazines, and websites because they were licensed as Creative Commons
  • The Light Graffiti Guide Sean and I wrote has been used by teachers all across the world.
  • Photos being shown at the 2008 Nuit des musées at Le Compa, Conservatoire de l’agriculture in Chartres, France.
  • More contract gigs than I can count
  • My second full-time job (eResources)
  • My third full-time job (Praxis). Here is the post that put Isaac over the edge.
  • Weekly emails from people who found my tutorials helpful.
  • Meeting authors whose books I wrote about
  • Invitations to meetups and discussion groups
  • People using my code for their own projects
  • I’ve served up appliance repair manuals to hundreds of thousands of people. I let my Dad upload the ones he had back in 2008 to so that people could find them for free. Just doing our part to keep the Right to Repair/Fix It movement strong.

Also, not only is today my 10 year blogging anniversary, it is also my 5 year wedding anniversary! Amanda and I had just started dating when I put this blog up, so she’s been here since the beginning. Little did I know that we’d get married exactly five years later.

It’s been a fun ten years and I’m not done yet. cagrimmett.com is here to stay. Check out my archives and stay tuned for more.

Automated Accountability Check-ins

June 11, 2018 - Category: development

The Need

During the Praxis bootcamp, participants are expected to make every single day a non-zero day. Most participants ask us to hold them accountable, so they email one of our staff each day and that person emails them the next day if they miss a check-in. This takes an enormous number of emails, staff overhead to keep track of, and is difficult to search. Isaac asked me, “What can we do to automate this?”

It needs this functionality:

  1. The ability to submit what you’ve worked on that day.
  2. The ability to see what you’ve submitted.
  3. The ability for staff to see what you’ve submitted.
  4. The ability to send an email to people who didn’t submit a check-in the previous day.
  5. The ability for logging who missed a check-in.

The Restrictions

There are existing systems that do this sort of thing, so why make our own? During the bootcamp, our customers use what we call The Portal, which is a curriculum platform I built on top of WordPress with the help of Restrict Content Pro and wpcomplete. I didn’t want to add another destination into the mix. I wanted a reason for customers to go to the Portal every day and keep their work moving forward. Plus, they already have logins there. No need to add yet another login for them to keep track of.

The Solution Sketch

  1. USP Pro for front-end post submission
  2. Custom page template to display the content box but hide the tedious stuff: User ID, today’s date, user email
  3. New WordPress user meta field for keeping track of accountability opt-ins
  4. Custom script to check if opt-ins have posted within the last 28 hours and fire off a webhook to Zapier to send the email and log missed check-ins to a spreadsheet
  5. Zapier time trigger with a GET hook to run the script at a specific time to kick off the accountability check
  6. Custom template to display all opted-in participants and their check-ins to make it easy for staff to see everything in one place
  7. Hide the check-ins from search results

The Solution Details

Front-end post submission and showing posts

USP Pro handles all sorts of complexity when it comes to submitting posts from the front-end of a WordPress site: The form, the custom post type, assigning the current logged-in user to be the author, and displaying success and error messages. They also have hooks and filters to make using the posts in templates easier.

I used a custom template to hide some of the things I’d need to make the whole process work: User ID to assign as the post author, name and today’s date for the title of the post.

After the form, we also want to show all the posts someone has already submitted. I do that with a WP_Query_  Note: I added a Delete button here in case some makes an accidental post. If you want someone to be able to delete their check-in post, they need to have the role Author.

Here is the code I used for the template and comments about what it does:

// Check if user is logged in. Display the check-in form if so, login form if not.
if ( is_user_logged_in() ) {
//Get the logged in user, today's date, and the PHP session ID
	$current_user = wp_get_current_user();
	$today = date("F j, Y");
	$ses_id = session_id();					    
  <div class="usp-pro-form">

	<!-- USP Pro @ https://plugin-planet.com/usp-pro/ -->
	<div id="usp-pro" class="usp-pro usp-form-4102">
	<form id="usp-form-4102" class="usp-form" method="post" enctype="multipart/form-data" action="" data-parsley-validate="" data-persist="garlic">

		<fieldset class="usp-fieldset usp-fieldset-default">
			<label for="usp-content" class="usp-label usp-label-content"><h2>What did you do to make today a non-zero day?</h2></label>
			<textarea name="usp-content" id="usp-content" rows="5" cols="30" maxlength="999999" data-required="true" required="required" placeholder="Today I..." class="usp-input usp-textarea usp-input-content"></textarea>
			<input type="hidden" name="usp-content-required" value="1">

		<div class="usp-hidden">
		// Get first and last name, today's date, set that as the title.
			<?php 	echo '<input name="usp-title" value="' . $current_user->user_firstname . ' ' . $current_user->user_lastname . ' ' . $today . '" type="hidden" />';
		// Get user ID for author
				  	echo '<input type="hidden" name="usp-logged-id" value="' . $current_user->ID . '">';
		// Get PHP session ID. Plugin thing.
				  	echo '<input type="hidden" name="PHPSESSID" value="' . $ses_id . '">';

			<input type="text" name="usp-verify" id="verify" value="" style="display:none;" class="exclude">

			<input type="hidden" name="usp-form-id" value="4102">

		<!-- Using the shortcode for the submit button because it includes a bunch of error checking, success message, and other stuff. -->
		<?php echo do_shortcode('[usp_form id="submit"]'); ?>


	<div class="submitted-checkins">
		<h2>Your submitted check-ins:</h2>
	// Show posts from this user ID with the type usp_post
			$post_query = new WP_Query( array(
				'post_type' => 'usp_post',
				'author' => $current_user->ID,
				'orderby' => 'date',
				'order' => 'DESC',
			) );

			if ( $post_query->have_posts() ) :
				while( $post_query->have_posts() ) : $post_query->the_post(); ?>

					<article <?php post_class(); ?>>
						<h3><?php the_time('F j, Y \a\t g:i a') ?></h3>
						<div><?php the_content(); ?><a href="<?php echo get_delete_post_link(); ?>" style="font-size: .8em;">Delete this check-in</a></div>

				echo "<p>You have no check-in posts. Submit one above!</p>";

} else {
?><div style="padding:50px;"><?php					  
   echo do_shortcode('[login_form]');


The page (with a little bit of CSS added, which I’ll leave as an exercise for the reader) looks like this: Automated Accountability Check-in Home Page

Adding custom user meta field for accountability opt-in

This goes in functions.php:

add_action( 'show_user_profile', 'accountability_opt_in' );
add_action( 'edit_user_profile', 'accountability_opt_in' );

function accountability_opt_in( $user ) { ?>

	<h3>Daily Accountability</h3>

	<table class="form-table">

			<th><label for="checkin">Opt in to daily check-in accountability?</label></th>

				<input type="checkbox" name="checkin" id="checkin" <?php if (get_the_author_meta( 'checkin', $user->ID) == 'True' ) { ?>checked="checked"<?php }?> value="True" /><br />
				<span class="description">Yes, opt me in.</span>

<?php }

add_action( 'personal_options_update', 'save_accountability_opt_in' );
add_action( 'edit_user_profile_update', 'save_accountability_opt_in' );

function save_accountability_opt_in( $user_id ) {

	if ( !current_user_can( 'edit_user', $user_id ) )
		return false;

	update_usermeta( $user_id, 'checkin', $_POST['checkin'] );

So, at the bottom of the user profile, it now shows this: Automated Accountability Check-in User Meta Field

Checking to see if opted in user has submitted a post in the last 28 hours


  1. I trigger this by hitting the URL once a night. So no one else can trigger it, hit it with a long random key as a query string, which I first get and check.
  2. Load WordPress so I can use pre-built functions.
  3. Get list of opted-in users.
  4. Check to see if they have posts. If so, get the most recent one. If not, send a webhook to Zapier.
  5. If they have posts, check the date of the most recent one. If it is within the last 28 hours, do nothing. If not, send a webhook to Zapier. 28 hours gives a little wiggle room for early/late posts.

I like to leave the echos on for debugging if something goes wrong. Like, for example, if you use the_date instead of get_the_date and occasionally get NULL instead of a date and can’t figure out why. (Hint: the_date only fires once in a loop. Thanks for the help debugging, Eric Davis!)

Also, I know I’m using two different types for formatting for if statements. It helps me keep them separate.

$key = $_GET['key'];

if ($key == 'RANDOM_KEY' ) {

	require_once($_SERVER['DOCUMENT_ROOT'] . '/wp-load.php');

	function get_user_by_meta_data( $meta_key, $meta_value ) {

		// Query for users based on the meta data
		$user_query = new WP_User_Query(
				'meta_key'	  =>	$meta_key,
				'meta_value'	=>	$meta_value

		// Get the results from the query, returning the users
		$users = $user_query->get_results();

		return $users;

	$opted_in_users = get_user_by_meta_data( 'checkin', 'True');

	foreach ($opted_in_users as $user) {
		echo $user->ID . ' ' . $user->user_email . '<br />';
		$post_query = new WP_Query( array(
			'post_type' => 'usp_post',
			'author' => $user->ID,
			'post_status' => 'publish',
			'orderby' => 'date',
			'order' => 'DESC',
			'showposts' => '1',
		) );

		// The Loop
		if ( $post_query->have_posts() ) :
		while ( $post_query->have_posts() ) : $post_query->the_post();
		  $date = get_the_date('U');
		  echo 'last post date: ' . $date . '<br />';
		  //echo 'now: ' . time() . '<br />';
			if( $date > (time() - 100800)) {
				echo 'Within 28 hours <br /><br />';
			 } else {
				echo 'Not within 28 hours <br /><br />';
				// Initialize curl
				$curl = curl_init();
				$data = array(
					'user_id' => $user->ID,
					'email' => $user->user_email,
					'first_name' => $user->first_name,
					'last_name' => $user->last_name,
					'last_post' => $date,
				$jsonEncodedData = json_encode($data);
				$opts = array(
					CURLOPT_URL             => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/',
					CURLOPT_POST            => 1,
					CURLOPT_POSTFIELDS      => $jsonEncodedData,
					CURLOPT_HTTPHEADER  => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData))                                                                       
				// Set curl options
				curl_setopt_array($curl, $opts);

				// Get the results
				$result = curl_exec($curl);

				// Close resource

				echo $result;

		else :
			echo "No posts <br /><br />";
			// Initialize curl
				$curl = curl_init();
				$data = array(
					'user_id' => $user->ID,
					'email' => $user->user_email,
					'first_name' => $user->first_name,
					'last_name' => $user->last_name,
				$jsonEncodedData = json_encode($data);
				$opts = array(
					CURLOPT_URL             => 'https://hooks.zapier.com/hooks/catch/1503890/aqztv5/',
					CURLOPT_POST            => 1,
					CURLOPT_POSTFIELDS      => $jsonEncodedData,
					CURLOPT_HTTPHEADER  => array('Content-Type: application/json','Content-Length: ' . strlen($jsonEncodedData))                                                                       
				// Set curl options
				curl_setopt_array($curl, $opts);

				// Get the results
				$result = curl_exec($curl);

				// Close resource

				echo $result;	    


} else {
	exit("You are not authorized. Go away.");

Template for viewing posts by user

Viewing posts in the wp-admin area is less than ideal. They are grouped by date instead of author and the full contents doesn’t show unless you click. No want to view updates at a glance. So I made a template for that.

// First, check if user is an admin.
if ( !is_user_logged_in() || !current_user_can('administrator') ) {
	   wp_redirect( site_url() );  exit;
} else {
// User IDs are passed via query string. If no string, show all names
	$user = $_GET['id'];
if ( $user == null ) {?>
	<h2>Participants who have submitted check-ins:</h2>
		$args1 = array(
		 //'role' => 'author',
		 'orderby' => 'first_name',
		 'order' => 'ASC'
		$authors = get_users($args1);
		foreach ($authors as $user)	{
			$user_post_count = count_user_posts( $user->ID , 'usp_post' );
			if ( $user_post_count > 0) {
				echo '<a href="/check-ins/?id=' . $user->id . '">' . $user->first_name . ' ' . $user->last_name . '</a><br />';
} else {
	$user = get_user_by('id', $user);

	$post_query = new WP_Query( array(
		'post_type' => 'usp_post',
		'author' => $user->ID,
		'orderby' => 'date',
		'order' => 'DESC',
	) ); ?>
	<br />
	<p><a href="/check-ins/">&larr; Back to all participants with check-ins</a></p>
	<h2><?php echo $user->first_name . ' ' . $user->last_name .'\'s check-in posts';?></h2>

			if ( $post_query->have_posts() ) :
				while( $post_query->have_posts() ) : $post_query->the_post(); ?>

					<article <?php post_class(); ?>>
						<h3><?php the_time('F j, Y \a\t g:i a') ?></h3>
						<div><?php the_content(); ?></div>

				echo "<p>You have no check-in posts. Submit one above!</p>";

Here is what the template outputs:

check-in post user list

user posts

The best way to do this is to hook in to when the post type is initiated and toggle the ‘exclude_from_search option so ‘true’. I added this to functions.php:

add_action( 'init', 'usp_post_hide_search', 99 );

function usp_post_hide_search() {
	global $wp_post_types;

	if ( post_type_exists( 'usp_post' ) ) {

		// exclude from search results
		$wp_post_types['usp_post']->exclude_from_search = true;

Using Zapier

I opted to use Zapier for two things:

  1. Triggering the nightly post checking script via GET. wp_cron isn’t super reliable if you need something to run at a given time, and since I’m hosting on WPengine, I don’t have access to the server cron. We already use Zapier for a ton of stuff, so I fire off a GET to my script with the query string random key.
  2. Sending the email and logging emails sent. I could have done this all in the script, but I wanted staff to be able to easily change the email and possibly add more post-email items like sending a message to Slack and Salesforce, or sending a text message to the user.

Going Forward

Here are a few ways I want to improve this when I have time:

  1. Extend the template that staff uses to see check-in posts to include filter criteria.
  2. Let users opt-in and opt-out of automated accountability instead of admins opting them in or out.
  3. I might want to expire or hide posts
  4. USP by default doesn’t preserve line breaks. I’ll need to update that.


784 posts since June 22, 2008.