Marshall Bowers

Conjurer of code. Devourer of art. Pursuer of æsthetics.

Devlog: Today I Did

Saturday, November 12, 2022
855 words
5 minute read

We use Threads1 pretty heavily at WorkOS. One of the things we use it for is to run async standups.

I found myself exerting more effort than I would have liked compiling my standup updates, specifically around wrangling the URLs to PRs and Linear issues that I had worked on that day.

This process involved massive amounts of drudgery in the form of clicking through various PRs on GitHub, copying their URLs and the URLs of any attached Linear issues, and then transcribing these into Threads.

In a small win for bar-raising, Threads supports copy/pasting Markdown into their rich-text editor and translating it into their rich-text format. This was a small improvement over using the built-in WYSIWYG editor directly, as I could compose in Markdown in a text buffer and then copy it in with the right formatting. However, I still had the problem of having to curate this Markdown by hand.

This seemed like a problem that could be solved trivially with a bit of code, and thus the first version of today-i-did was born.

The Workflow

The workflow for today-i-did looks something like this:

I keep a file in my home directory that I use to edit my daily standup notes. I compose them in Markdown and jot down the URLs of the PRs I've worked on that day. When I'm done, it might resemble something like this:

## What are you working on?

- Added examples for Directory Sync operations
- Renamed `VerifyFactor` to `VerifyChallenge`

## What's up next?

- Continue work on Rust SDK

Then, I run the file through today-i-did and it outputs the following to stdout:

## What are you working on?

- Added examples for Directory Sync operations ([SDK-539](, [PR 70](
- Renamed `VerifyFactor` to `VerifyChallenge` ([SDK-540](, [PR 71](

## What's up next?

- Continue work on Rust SDK

I can then copy this over to Threads, where I get a nicely-formatted status update:

A screenshot of the status update pasted into Threads

Under the hood the program scans the input file looking for GitHub PR URLs. When it finds them, it uses the GitHub API to pull down the comments on the PR to look for mentions of Linear issues by the Linear GitHub bot, which it then pulls down using the Linear API.

It then takes this information and formats it nicely as a series of Markdown links and swaps them in for the original GitHub PR URL.

Version 1

The first version of today-i-did was a small PureScript program, clocing in at 209 lines of PureScript and 37 lines of JavaScript FFI code to interact with the GitHub and Linear SDKs.

Since both the GitHub and Linear SDKs are available as npm packages, it was fairly trivial to pull them in and call out to them over the PureScript FFI.

I've been using this version daily Monday through Friday since September 2021, and it's certainly saved me many hours at this point.

PureScript → Rust

A couple weeks ago, I decided to rewrite today-i-did in Rust in order to make its usage more streamlined. After all, it's a lot easier to install and consume a single binary than having to drag around a bunch of—albeit transpiled—JavaScript and npm modules.

The rewrite came in at exactly 200 lines of Rust code, which is a testament to just how expressive Rust can be. Granted, there are still a few parts that aren't as elegant as the PureScript implementation. For one, the line replacement algorithm isn't quite a succinct as using traverse.

Most of the work for the rewrite actually came in the form of building a crate to interact with the Linear GraphQL API. There's still some more work to do on it before it's fully functional, but it's complete enough to use for today-i-did.

This morning I wrapped up a few loose ends that had been bothering me. Up until now I've been relying on a .env file to provide the configuration, which has finally moved configuration out to a separate config file. This should make it more amenable to running as a binary available on my $PATH.

While this tool is very specific to my current needs, I wanted to put it out in the open in the off-chance that someone else gets some mileage out of it. With that said, the repo is public.

At the very least, maybe it will inspire you to solve your own problems.


The old version, not the one seen on their marketing site.