Writing

I should do it more

Practicing Product

I think side projects, software at least, are a lot like the Civilization games. You can’t wait to start. The first 10% is awesome. 10–40% is complex and the difficulty ramps up. 40–100%, all you can think about is starting over on something else. At around 80%, you just quit and actually do start over. [comment]

I laughed, like you do when alone by the computer: with a smile and a snort, when I read this hitting comparison on Hackernews.

It feels like you are almost done before you even type rails new. And so you jump right in but already after the first few minutes you realize you’ve made the stupid mistake again.

The stupid mistake of thinking that anything would be easy just because you can imagine a few parts of it in your naive head.

Yet, side projects — or lets just call them projects because why does it matter if they’re on the side? — are the best way to learn. Even if you don’t finish them you learned something. You can read all the books or blog posts in the world and still learn more by actually building something.

You get to experience the whole process from designing to developing to redesigning to planning features, eventually (hopefully) deploying and marketing your thing. When you consider the whole of it, it isn’t quite so small anymore.

But what if the things you want to practice are specifically the last parts?

I recently launched many.link.

It all began when I saw someone use linktr.ee on Instagram. I liked the idea but felt the execution lacking. Their administration tool was anywhere from bad to not working. The actual profiles looked bland. I could do something like it1 fast enough, I thought.

First, I needed a short, quirky domain so I typed words like bio into iwantmynameand bio.kiwi was available.

My gut feeling told me some things would be important for this to work:

  1. The domain had to be short enough and signal intent. People expect your Instagram link to do one thing. How do we signal that this one might contain several — only by the name?
  2. The profile pages needed to look presentable and well arranged, easy to understand. And…
  3. Be customisable. People who have multiple things to link to are also people who are concerned about their online presence. Aesthetics are a big part of anything. How do we make it easy to customise yet hard to mess up. Pre-built themes would be relatively easy to provide so we’ll start there.
  4. Frictionless sign up. OAuth (the “Sign in with whatever” buttons) makes it easy to provide authentication features for every service on the planet. But would that make sense? Intentionally I kept it to Instagram. Adding Facebook or Twitter might take a second to add but it would confuse the entire sentiment of the service.

One problem though: this was in the middle of the Cambridge Analytica story and Facebook had seemingly put a cooler on handing out API keys to anyone and their neighbour. So after applying to get my Instagram app approved and waiting, checking for updates for a few daysweeks to no news, I eventually put the project away.

Showing what I had so far to a few not-so-maker-scene-but-very-Instagram-savyfriends made me realise that the domain, although cute 🥝, didn’t tell the story well enough. My friend Emilie suggested allmylinks and .app was available. Bought the domain but did nothing with it.

There it was for a month or two. Online and working on the wrong domain. Far from done, even further from done enough to submit.

Then came Christmas and I discovered Magnify. A few days later they launched on ProductHunt. Nice little product, simple concept, good execution. They were #1 for the day. Wait a second? I have one of those lying around somewhere!

I checked Instagram’s developer section. It had gotten approved!? What’s the minimum amount of work I need to do to put this on ProductHunt? The landing page and … polish?

I copied the general idea of Magnify’s landing page — header, short text, big preview on the right. Made sure everything looked presentable on the inside too.

Then I discovered allmylinks.com doing the exact same thing (there are so many!) and went back to iwantmyname. I typed all kinds of things into the box and pulling the lever like a granny working a slot machine. Suddenly, there was many.link. Works!

A few evenings’ work and I had it ready for launch. I put it on PH at 00:13 San Fransisco time and to my surprise it went to #1 for the day.

There’s so much more to making a thing than programming it. We all know that it’s a lie that if you build it they will come. I wanted to practice all the other parts than building.

many.link was the perfect project to practice ProductHunt. Everyone has an Instagram account and is familiar with the problem. The problem is so small, you can explain it with a single sentence. The running costs are the price of whatever the cheapest VPS you can find costs.

Now, this leaves the project in an awkward place as its main purpose has been fulfilled. Could I do ProductHunt? I could. Can I make it generate some revenue? We’ll find out in the future.

I ended up selling many.link.

Introducing: Spotify Hard Mode

I for one miss the days of spending at least 30 minutes at every party going through the host’s CDs for the ones I knew, the ones I knew I wanted, and most of all; the ones I didn’t know I wanted.

The days when a friend would bring his newly purchased cd to your house so you could listen to it together. But only until he left and you’d have to wait?! until his next visit to hear that one genius track again.

I’m nostalgic, I know, but I’m not going to sit around and sob. I’m bringing it back:

With Spotify Hard Mode you can offer to lend out records in your Spotify collection to your friends — or request to borrow from theirs.

When one of you lend out a record it disappears from your collection and appears in the other’s. If it’s a record that you know you’re going to miss you can add a timed request for your friend to turn your record back.

This will not be automated.

Instead your friend will be pinged to turn back the record by themselves and in the process be asked to write a few words as a review. Not like a public facing, thumbs up or down, dreadful review like the internet is brimming with. But a short text based, personal review from one friend to another.

When a record comes back to your collection it will suddenly be at the top of the list again (because you sort your albums by date added like any reasonable person) and you will once again be reminded of how much you listened to this all summer, two years ago, and just how good it is. The best records will keep being on top of your collection pile.

If you cheat and manually re-add the record while it’s lent out, Spotify Hard Mode will punish you by removing two other random records from your collection plus add a few thousand plays of Smurf songs to wreak havoc on your Discover Weekly and Daily Mixes.

YouTube Binge Bundler

It’s 2am. You planned to go to bed 4 hours ago but even though you could’ve guessed where this would end you visited YouTube.

Eyes sore, head spinning. You are at your twentieth video in a row. Or was it thirtieth? In the middle of a binge, the stream of videos seems so natural. You flow between categories and themes, hand in hand with YouTube’s algorithms, in a game of never ending Ooh that reminds me of…

You’ve long forgotten where this began. The last few videos were of Queen songs with the vocals singled out. What an amazing talent he was! And you can’t go to bed *juuust *yet because there’s at least a few more Related Videos and what about cover versions? There must be some good cover versions of this song.

If you’re anything like me you can’t control YouTube at night.

Introducing: YouTube Binge Bundler™

YouTube Binge Bundler™ looks at your YouTube History for what looks like a recent binge and turns it into a playlist, easily shareable with friends and family.

If your binge looks like other binges, our Machine Learning robots will suggest videos to add to your journey.

I always end up having at least a few Michael Jackson recordings in there. But that’s just me. You might have your own go-to things that are never not relevant. They will find other late night Michael Jackson aficionados and who knows — you might end with new, tired and confused friends.

If you’re up to the challenge you can provide a description. Like a sort of guided trip or journal entry of a fever dream. Fun!

Skip.life

Why read it later when you can skip it for life?

Skip.life — The “Read it Later” service for people too busy to Read It Later

I enjoy a good, steamy hot take as much as anyone, I just don’t always have the time right now. That’s why I use Instapaper. Or, used to use.

Because my Instapaper reading list grew faster than I would read whatever I would put in it, every visit became more about feeling guilty than enjoying taking the ;dr part out of the tl blog posts.

So I moved to Pocket.

And what a relief! Just me and a completely fresh Pocket archive, empty of articles and guilt. Ready to be filled with longreads of whatever we are very, very concerned about this week.

And so I quickly began throwing anything in there. I even added my friends as friends and added a few of their recommendations. Some of them had many and that made them seem just the kind of smart that I want to be.

Until a few days later when my Pocket started to look like my Instapaper. Uselessly filled with a ambitions I couldn’t live up to. So I ditched it and began using Safari’s built-in Reading List instead. And what a relief?

I was beginning to see a pattern and needed a drastic change. So I made skip.life:

Skip.life integrates 100% with your browser

Introducing skip.life

Skip.life is a browser extension, an app and a way of life. Just like you’d expect from Instapaper, Pocket or the other guilt trippers. Come across an article you’d love to read *just not right now? *Click the “Save” Button™ and you will be treated to a very satisfying animation of a robot arm carefully archiving the article for later enjoyment, then the tab will close.

Yet, nothing else will happen.

Skip.life will skip it for you.

You’ll feel satisfied that you didn’t miss out on the article. You definitely saved it for later. And 6 minutes later you will have forgotten both the article and every intention of reading it — and best of all, you’ll feel zero anxiety when you log into skip.life and see that you have 0 unread articles.

You must be very smart!

Getting a project's Ruby version from the Gemfile

It used to be that every project had its own .ruby_version file. But you’d also have to specify the same version in the Gemfile. I say nay to .ruby_version and instead get it from one place:

alias ruby-vers="cat Gemfile | grep '^ruby' | sed -E \"s/.*[\\\"'](.+)[\\\"']/\1/"\"

Now you can $ chruby ruby-vers your way to success!

Using Preact in production with Rails' Webpacker

Preact is like React but smaller in size. I think? Anyway, so I’ve been told. It’s also different in some ways and I’m not entirely sure how, but they have this thing called preact-compat that’ll plug right into your Webpack config and use Preact instead of React which seems like free filesize savings. Right on!

First:

$ yarn add preact-compat webpack-merge

In config/webpack/production.js:

const environment = require("./environment");
const merge = require("webpack-merge");

module.exports = merge(environment.toWebpackConfig(), {
  resolve: {
    alias: {
      react: "preact-compat",
      "react-dom": "preact-compat",
    },
  },
});

I saved 2 whole megabytes with just this change. No, of course that’s not true.

Disable long-press for Hyper on macOS

Hyper is great and even though I still cling to iTerm I sometimes try and shake things up by using Hyper for a few days.

macOS has a feature where if you press a character like u and hold it, a popup menu appears that’ll let you pick out a ü instead. Great if you’re writing German prose. Not great if you are undoing a whole day’s work in vim.

So let’s disable that feature only for Hyper. Put this in your term and smoke it:

defaults write co.zeit.hyper ApplePressAndHoldEnabled -bool false

If you want to disable it system wide then use the same command but without the co.zeit.hyper part.

Conditionally load local plugs in vim

If you’re using vim (you should) and vim-plug for plugins (you should) and you have some plugins locally (you might) but still want your setup to be portable (you should) then you might want to only load plugins locally if they exist (here’s how).

function! s:maybeLocalPlug(args)
  let l:localPath = $HOME . "/dev/" . expand(a:args)

  if isdirectory(l:localPath)
    Plug l:localPath
  else
    Plug 'mikker/' . expand(a:args)
  endif
endfunction

call s:maybeLocalPlug('lightline-theme-pencil')
call s:maybeLocalPlug('vim-rerunner')
call s:maybeLocalPlug('vim-dimcil')
call s:maybeLocalPlug('vim-colors-paramount')

This checks to see if the plugin exists locally at ~/dev/plugin-name and if it doesn’t it loads from good old Github.

Sprinkling React

A beautiful thing about jQuery was how you could sprinkle it on top of an already working site. With all of today’s SPAs and what nots this seems like a distant past. We’ve all drunk the Kool Aid and the benefits of React (and the like) are just too huge to ever look back.

Still, there’s no reason we can’t still sprinkle instead SPA-ing. That was a horrible sentence. Let’s move on.

Here’s what I do on 10er.dk.

The api is basically as follows. You have a div with some data- props that we’ll catch later and switch in the magic.

<article>
  <div data-react="NumberWang" data-props='{"numbers":[1,2,3,5,68]}'></div>
</article>

Ok, so that means look up the component called NumberWang, render it with these props and throw it back in here.

Here’s sprinkleComponents.js. It uses Webpack and its require.context:

import React from "react";
import { render } from "react-dom";

let context = require.context("./components", true, /\.(\/index)?js$/);

export default function sprinkleComponents() {
  const nodes = document.querySelectorAll("[data-react]");

  for (const node of nodes) {
    const regexp = new RegExp(node.dataset.react + "(/index)?\\.js");
    const match = context.keys().find((path) => path.match(regexp));
    const Comp = context(match).default;
    const props = node.dataset.reactProps
      ? JSON.parse(node.dataset.reactProps)
      : {};
    props.children = node.innerHTML;

    render(<Comp {...props} />, node);
  }
}

// We can even get hot module reload (so dx am I right?)
if (module.hot) {
  module.hot.accept(context.id, (upd) => {
    context = require.context("./components", true, /\.(\/index)?js$/);
    sprinkleComponents();
  });
}

This expects to find the component NumberWang at either ./components/NumberWang.js or ./components/NumberWang/index.js relative to sprinkleComponents.js.

Let’s make it – ./components/NumberWang.js:

import React from "react";

export default ({ numbers }) => (
  <div>{numbers[Math.floor(Math.random() * numbers.length)]}</div>
);

And finally in your main.js or index.js or client.js or whatever:

document.addEventListener("DOMContentLoaded", () => {
  sprinkleComponents();
});

This is the technique that I use to selectively apply some js sauce on 10er.dk, a mostly server rendered Rails app.

Serving a Keybase.io claim with Next

I recently moved my personal website to next.js because why not. The people at ▲ Zeit are on a roll and I want in on it.

Keybase quickly started complaining about me removing my claim. You’re supposed to host a file at /keybase.txt with specific content to claim a domain is truly yours. So how do we do that with Next?

Here’s pages/keybase.txt.js:

import React, { Component } from "react";

export default class KeybaseTxt extends Component {
  static getInitialProps({ res }) {
    res.end(claim);
  }

  render() {
    return <div>We'll never get this far</div>;
  }
}

const claim = `=====...long long claim thing...`;

getInitialProps is Next’s way of fetching data before render so it works both on the server and the client. During server render it’s passed req and res from node and apparently we can just end it right there. So dumb it’s smart.

Pages

  • /uses — My current setup

COMPUTERS (Danish)RSS

Danish newsletter on life with computers

Past issues

PostsRSS

Musings on building software