meet grug. grug no big brain, but grug survive many code winter. grug self-aware smol brain. grug wise.
grug tell many secret for young grug (also mostly for grug, grug forget important things). young grug should read whole grug story1, but grug story long so old grug share favorite part:
grug enemy apex predator complexity. complexity bad. complexity come in many form, no always see, but sense. complexity very bad. grug often swing club but just hit grug own head. complexity worse than t-rex. grug favorite club “no”, but sometimes use “ok” club to save tribe. grug build code cave strong, try no let complexity demon in.
grug no get lost in big brain fog. big brain want think-think. grug no want think, grug want build. grug must build to think.
grug think test, agile, refactor, architecture all like old berry in forest — some taste good, some make tummy ouch. grug learn be cautious …but grug still like berry.
grug dubious of shamans like test shaman, agile shaman, architect shaman. shaman make idol not club. shaman claim know all good berry, but still make tummy ouch. grug lose many shiney rock to shaman. grug reach slowly for club, but grug try stay calm.
grug love tool. tool separate grug from dinosaur. grug use tool to build more think less. grug favorite tool ide, type system, debugger. grug trade all shiney rock for good tool.
grug often feel like grug have no idea what grug doing, grug feel like impostor. grug fear look dumb in front of big brain. grug realize young grug feel this, old grug feel this. grug stay calm. when grug confused grug just say so. grug ask for help. when grug fall down grug not swing club, just make joke. grug see big brain fall down too, grug help. nobody impostor if everybody impostor. except you, young grug… young grug impostor! that bad joke. grug sorry.
young grug too face big brain, t-rex, shaman, complexity demon. grug tale full of oops and ouch. grug whole tale like map to hidden shiney rock. young grug learn and will do okay, make shiney rock pile. young grug become old grug, then choose give rock pile for better cave and dino drumstick or give rock pile for better club and tool. just be careful when give shiney rock to shaman.
old grug big thank htmx for sharing grug story, and many other good story
↩I’m approaching 2024 with two variations on a resolution: a motif and fortnights.
I’m choosing a single word as a motif representing the year ahead. It should be somewhat aspirational and intentionally non-concrete. It is not a goal in itself but should guide decision making, inform habits and goals. It’s intentional psychological priming.
This year’s motif is restoration.
Restoration is meant to be interpreted broadly. It involves anything aimed at renewing or revitalizing myself physically, mentally, emotionally, or spiritually, going beyond mere rest to replenish energy, balance, and overall well-being.
Rather than choose one habit or goal for the year, I’m taking on a new one every fortnight, or every two weeks. Each fortnight’s goal will relate to the restoration motif. I’m going to approach this as a scientist. What is restorative to me? The goal is not to change my habits (though I hope at least a few stick) but to learn, fail often, and attempt to answer this question.
I’ll do six of these experiments per season, with one week breaks interspersed.
Have ideas for what I should try? Get in touch!
To help explore ideas for restoration experiments, I’ve created a five-axis framework inspired by Maslow’s Heirarchy, Ikigai, and Dimensional Wellness:
Physiological: Rejuvenate the body and replenish energy. Includes sleep, diet, and fitness.
Cognitive: Reduce cognitive stress and fatigue, enhance executive function, foster mental well-being, satisfy creativity and curiosity.
Sensory: Reduce external stress, enrich senses, seek beauty, and create or be in harmonious environments.
Social-emotional: Foster emotional resilience, social belonging, high esteem and self-respect.
Purpose: Be impactful, grow professionally, parent well.
I’ll interpret each axis along an active/passive spectrum. Each will be interpreted in slightly different ways, but active actions should push boundaries and present worthwhile challenges while passive ones should be restful and at times indulgent.
]]>
This is a mental model and framework for understanding society at large, but applies equally well for many large dynamic systems of people, software, architecture, and nature. Faster layers tend to be at the visible surface and change discontinuously, innovating when faced with new input. Slower layers are more powerful, integrating and codifying lessons from faster layers while providing inertial continuity that constrains them.
When struggling to weigh fast with slow, balancing allowing change with providing continuity, consider how Pace Layers can help you think about the system you’re working within.
To change is to lose identity; yet to change is to be alive2
Stewart Brand is an overall fascinating and polarizing person. 1960s San Francisco counter-culture leader, editor of the Whole Earth Catalog, president of the Long Now Foundation, and friend of Douglas Engelbart, Steve Jobs, Buckminster Fuller, and Brian Eno.
↩This and many other great moments from Brand’s lecture at The Long Now’s Interval.
↩
It’s easier to ask forgiveness than it is to get permission.
— Grace Hopper1
This is classic advice when operating in a large organization. There’s a problem to be solved, you have a bold solution in mind and everything necessary to take action, but there will be very real costs felt broadly. You think the tradeoff is worth it, but will your team or higher-ups agree?
You might assume you need permission to incur the costs, but you likely have the best information on the decision. If it’s a good idea, go ahead and do it. Grace Hopper encouraged doing the right thing whether or not your higher-ups know it to be. If you’re wrong or get flak for the costs: ask forgiveness; you acted in good faith.
This good advice is missing one critical thing: radiating intent2.
Rather than shifting from permission before to forgiveness after, shift from asking permission to telling as many as you can about your intention. Radiate it loudly and clearly before you act so that no one will be surprised.
Elizabeth Ayer, in her excellent article on radiating intent explains why it’s superior to asking forgiveness (which I’ve editorialized):
Invites participation from those with critical info or a desire to help.
If wrong it gives a chance for someone to stop you without leaving you waiting to begin. You control the timeline.
Leaves evidence of good faith. Better to be known as predictable than underhanded.
Keep responsibility and own the outcome, good or bad. Doesn’t transfer blame (or credit) as asking permission does.
Sets the example that bold action is encouraged from everyone, not just higher-ups.
If it’s a good idea, go ahead and do it. Say loudly what you are doing along the way. Radiate intent!
Write it down. Start with a short description of what you intend to do, and why. Don’t bury the lede. Expand your thinking from there: document assumptions, options and trade-offs. This will help you communicate clearly, create a single source of truth, and avoid repeating yourself.
Make timelines clear. When do you plan to begin? Are there significant milestones worth noting? If others want to participate they need to know when. If there is urgency, make it known.
Share visibly and broadly. Start in whatever channel your team uses most. Share in the channels your stakeholders use. If you’re in a smaller organization, just send it to everyone. As you share ask “who else should know about this?”
Share often. Share when you decide on your intention. Share a reminder. Share when you begin. Share when you reach milestones. Share when you complete. Share outcomes and lessons learned (and only then might you ask forgiveness!)
Scale volume with impact. Radiating intent matters most when those around you will be impacted most. Share broader and more frequently in situations that demand it, and narrower otherwise.
This advice as quoted was popularized by all around badass Grace Hopper. However various forms have been cited, back as far as St. Benedict in 500 AD. Likely some form of it has existed as long as there has been organized human society.
↩This idea of clearly stating intent was popularized by L. David Marquet who in his book “Turn the Ship Around!” suggests that giving intent, not instructions, gives control and creates leaders.
↩Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.
— Melvin Conway (1967)
This is often used as an observation of poor software quality. Overly complex and disconnected software corresponds with similarly dysfunctional organizations.
However the inverse can be a useful tool when building organizations when you can do so downwind of a desired saliently designed system, and thus use the resulting org structure to accelerate towards the desired architecture. In doing so, you ensure your organization best serves the users of the system (presumably, your customers) rather than being self-serving1.
But where does this word “torus” come from? It happens to be the Latin word for… a pillow. The classic round poof with a pin through the middle to keep it flat2.
Later used to describe architectural decoration that reminded them of that shape, in particular the rounded molding at the base of some Doric columns.
I find it fascinating how words we consider academic and sterile have roots in everyday things. “That column base looks like a fluffy pillow, let’s call it so” led to a defined term as mathematics provided architectural rigor.
Air flights have a surprisingly complex impact on climate warming. In addition to burning jet fuel emitting CO₂ and other greenhouse gasses, the contrails left behind linger as artificial clouds. These contrails act as a blanket both trapping thermal heat as well as reflecting away solar radiation. The net effect of which could be warming or cooling1.
Many factors impact this net effect including the duration, path of flight, changes in altitude, wind speed, and most significantly time of day. The physics involved mean the sunlight reflective effects of a contrail can only occur in the daytime; without this a contrail can only have a warming effect. To maximize a net-cooling effect, contrails need to be formed early in the day so they can reflect sunlight for their duration.
Factoring the effects of contrails into the overall climate impact of air travel means that not all flights are equal offenders. The top 5% of flights contribute roughly a third of overall climate warming effects. Modest routing and timing adjustments could have an outsized impact on the overall climate impact of air travel.
And yes, this is just one more reason why red eye flights are awful.
For a far more nuanced understanding of the effect of contrails, see the incredible resource at contrails.org.
↩Reflecting on the books I read last year, four thousand weeks by Oliver Burkeman stuck with me the most. While pitched as yet another productivity management book, it’s something of a bait-and-switch tricking you into reading about philosophy. In particular directly acknowledging our own mortality and in fact pushing back on the idea of optimizing yourself to accomplish more.
I liked this book so much that I decided to build something of a digital book report. It contains some of the most salient excerpts, quotes, and themes quotes from the book. I hope you enjoy it and does a small part in helping you make the most of your own finitude.
]]>Ugh.
A quick solution is to open the Keyboard Preferences, in the Shortcuts tab add a new App Shortcut for “Exit Full Screen” for all applications, and choose something a little more intentional. In my case, I use Control-Escape1.
Especially helpful as I have remapped Caps Lock to be Control.
↩The noTunes app is an incredibly simple (open source!) app which solves exactly this problem. More specifically it just keeps the Apple Music app from opening at all, or lets you launch a replacement app if you so choose.
Install it with brew
, launch it, then add it to “Login Items” in System Preferences to ensure it launches automatically.
brew install --cask notunes
]]>brew install --cask
. However, where I’ve found Brew most helpful is as a lightweight way to set up new computers with all the apps and utilities I rely on. To do this, I use Brewfile.
This is brew’s equivalent of a package.json or Gemfile, it’s just a list of all software that should be installed. To get started quickly run brew bundle
and to learn more, see the Homebrew/homebrew-bundle repo.
However, keeping the Brewfile up to date as new things are installed does not happen automatically. Here are a few additional things I do:
I keep my .Brewfile
in my homedir, and track it as part of my dotfiles.
I use brew alias
to keep commonly used commands and their arguments terse. The most important being brew add
which is a replacement for brew install
which also updates the Brewfile, brew remove
replacing brew uninstall
and brew sync
which installs anything new after pulling dotfiles changes from another machine. You can see all of these scripts which I also include in my dotfiles repo.
The final result is that setting up a new machine with a list of apps I use is pretty easy, as is keeping that install base the same across a few computers I use. Brew alias makes it easy to keep everything correct.
For personal machines, this has been much more simple and useable than Vagrant, Ansible, or other more professional environment management tool.
]]>fatal: The current branch my-pr-change has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin my-pr-change
Perhaps you have a script you use which automates setting this upstream for your new branches, but as of git v2.37 which was released June 2022, Git can handle this directly.
From your terminal, run:
git config --global push.autoSetupRemote true
This allows a simple git push
to automatically create remote branches to match.
I repeated this from a typical week a few years ago, before we had our kids, which left a clear high-level comparison of how having kids has impacted the balance of my time.
To add a little bit of color:
Work is the time I spend on my job and career. This is the portion of time which changed the least, mostly due to job expectations and my own desire to continue investing in having career impact.
Sleep took a significant hit. My brain chemistry seems to require a lot of sleep, I am definitely not one of those people who thrives on 4 hours a night, I need at least 6 hours. Anyone who has young kids can empathize with the struggle to maintain a healthy sleep schedule.
Social is the dedicated time I spend with my wife and my friends. The dramatic reduction has stress-tested my partnership and greatly limited my time with friends. This used to be even lower, but over the last few months has recovered a bit to where it is today.
Self is the time I spend on myself, either recharging and playing games or working on creative projects. This is the portion which has taken the greatest hit, and I believe is to blame for the pressure I’m feeling on my mental health. I’ve also found myself so tired from limited sleep, work pressure, and the toil of childcare that I spend all of this time on podcasts or mindless games (and bad habits like reading Reddit/News) and don’t have contiguous time to spend on creative projects.
Childcare is the time I spend with my kids, almost always with my wife as well. We’re privileged to have our older son in 9-5 preschool and an amazing nanny through the day for our baby. Despite this help, time spend on the morning routine, dinner, bedtime routine, and weekends sure adds up.
I absolutely love my kids, and to be clear I’m thrilled to have the time to spend with them, especially when they’re young. However the impact on how I spend my time is real, and the reduction of time I can invest into myself has taken a toll. I don’t have any immediate ideas of how to recover that time (if you do, I’m all ears!), but the exercise in seeing it was helpful.
Before Kids | After Kids | |
---|---|---|
Work | 54 hr/wk | 47 hr/wk |
Sleep | 58 hr/wk | 42 hr/wk |
Social | 22 hr/wk | 7 hr/wk |
Self | 34 hr/wk | 7 hr/wk |
Children | 65 hr/wk |
MeetingBar sits in your menu bar and at a glance shows either your next meeting along with a countdown until it starts or your current meeting and time remaining. Click to drop open a menu with an item for each calendar entry for the day. Choose an item to immediately open the attached Google Meet or Zoom link.
There are quite a few nice details beyond this, but that’s the gist. Oh, and it’s open source and can be installed with brew:
brew install meetingbar
]]>
When clicking an anchor link, the transition be can be smoothed by adding the scroll-behavior
rule to the root element.
:root {
scroll-behavior: smooth;
}
For navigating to a specific page location using window.scrollTo()
or similar methods, provide an options object instead of explicit coordinates.
// Old way
window.scrollTo(0, 100)
// Smooth way
window.scrollTo({ top: 100, behavior: 'smooth' })
]]>
To list them here, I’ll show them in git configfile syntax. You can find yours at either ~/.gitconfig
or ~/.config/git/config
.
[alias]
# My MVPs (@leeb)
s = "!git add -A; git status -s"
sl = log --graph --simplify-by-decoration --pretty=format:'%D %C(dim)(%cr)' --all --not --tags
last = log -1 HEAD
addremove = add --all
fixup = "!f() { TARGET=$(git rev-parse "$1"); git commit --fixup=$TARGET ${@:2} && EDITOR=true git rebase -i --autostash --autosquash $TARGET^; }; f"
unstage = restore --staged
discard = restore
# Find the ancestor and merge bases of sets of commits (@leeb)
oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'
all-merge-bases = "!f() { eval $(git for-each-ref --shell --format='git merge-base master %(refname);' refs/heads) | sort | uniq; }; f"
common-merge-base = "!f() { git rev-list --no-walk $(git all-merge-bases) | tail -n1; }; f"
# Replace "git git" with "git" (@jkreeftmeijer)
git = !git
# Nice shortcuts (@mathias)
st = status -s
# Show the diff between the latest commit and the current state (@mathias)
d = !"git diff-index --quiet HEAD -- || clear; git --no-pager diff --patch-with-stat"
# Show different kinds of things (@mathias)
tags = tag -l
branches = branch --all
aliases = config --get-regexp alias
remotes = remote --verbose
contributors = shortlog --summary --numbered --email
# Credit an author on the latest commit: git credit "Lee Byron" lee@leebyron.com (@mathias)
credit = "!f() { git commit --amend --author \"$1 <$2>\" -C HEAD; }; f"
# Show the user email for the current repository. (@mathias)
whoami = config user.email
# Remove branches that have already been merged with main, "delete merged" (@mathias)
dm = "!git branch --merged | grep -v '\\*' | xargs -n 1 git branch -d"
# Find branches with commit, tags with commit, comments with code, and commits with message (@mathias)
fb = "!f() { git branch -a --contains $1; }; f"
ft = "!f() { git describe --always --contains $1; }; f"
fc = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short -S$1; }; f"
fm = "!f() { git log --pretty=format:'%C(yellow)%h %Cblue%ad %Creset%s%Cgreen [%cn] %Cred%d' --decorate --date=short --grep=$1; }; f"
# Stage commits by chunk (@skwp)
chunkyadd = add --patch
# Alternative to "git stash" (@skwp)
# via http://philjackson.github.io/2013/04/07/handy-git-tips-to-stop-you-getting-fired.html
snapshot = !git stash save "snapshot: $(date)" && git stash apply "stash@{0}"
snapshots = !git stash list --grep snapshot
# Nice shortcuts (@DLX)
cl = clone --recursive
co = checkout --quiet
subup = submodule update --recursive --init
# Pretty log (@4lb0)
l = log --graph --decorate --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
# Undo last commit (@4lb0)
undo = reset --soft HEAD~
# Add and commit: git c "message" (@4lb0)
c = "!f() { git add --all && git commit -m \"$1\"; } f"
# Force push less likely to clobber your coworker's work (@HostileUX)
pushf = push --force-with-lease
# Add changed files into the existing commit (@samhogy)
whoops = commit --amend --no-edit
# Push a new branch to origin
pushu = !git push -u origin $(git symbolic-ref --short HEAD)
# Another prettier but more verbose log (@_angelmm)
ll = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(bold red)- %an%C(reset)' --all
# Use fzf to add, restore, restore staged, fix previous commit (@mattorb)
fza = "!git ls-files -m -o --exclude-standard | fzf --print0 -m --preview 'git diff {}' --preview-window=top:10:wrap | xargs -0 -t -o git add --all"
fzr = "!git ls-files -m --exclude-standard | fzf --print0 -m --preview 'git diff {}' --preview-window=top:10:wrap | xargs -0 -t -o git restore"
fzrs = "!git diff --name-only --staged | fzf --print0 -m --preview 'git diff {}' --preview-window=top:10:wrap | xargs -0 -t -o git restore --staged"
ffix = !HASH=`git log --pretty=oneline | head -n 100 | fzf` && git fixit `echo ${HASH} | awk '{ print $1 }'`
]]>
For example, while git show
is useful, sometimes you just want the last commit information and not the entire contents of the commit. Consider adding git last
git config --global alias.last 'log -1 HEAD'
Now running git last
is equivalent to git log -1 HEAD
! This gets included into git help and shell completion.
Another useful alias is git unstage
to remove a file from the staged commit, and git discard
to remove the changes altogether.
git config --global alias.unstage 'restore --staged'
git config --global alias.discard 'checkout HEAD --'
My most used alias is one which stages all changes and then prints the current status, which I simply call git s
. To run multiple commands, prefix with !
so the alias runs as a shell script.
git config --global alias.s '!git add -A; git status'
I use this so frequently, that I have an alias gs
set up in my shell config:
alias gs='git s'
Finally, you might want more complex positional arguments. To do this, define then invoke a shell function. I have an alias git fixup
which has almost the same API as git commit
but takes an existing commit as the first argument. That will apply the staged change to that commit and rebase all later commits atop it. Useful when working in a stacked PR and fixing an issue in one of the earliest commits:
git config --global alias.fixup '!f() { TARGET=$(git rev-parse "$1"); git commit --fixup=$TARGET ${@:2} && EDITOR=true git rebase -i --autostash --autosquash $TARGET^; }; f'
]]>u
and ⌃⇧R
(“CTRL-R” in Vim lingo, note the capital R) respectively. This works as you’d expect, but there are a few shortcomings:
It is very easy to undo, but dexterously challenging to redo.
If you undo, then accidentally make an edit, you can’t redo.
Fortunately Vim has a very powerful undo feature called “undo trees” which you might understand as similar to git branching. Undoing and then starting a new change is just a separate branch in the undo tree. There are a number of ways of interacting with the undo tree1, but a comparatively simple one is undo time travel.
To go to an earlier state, use g-
, to go to a later state, g+
. If you mess up with undo and redo, you can usually just try g-
repeatedly until you’re back where you want to be. While this is not quite as easy to remember as u
for undo, I find “go earlier” and “go later” as pretty great mnemonics and really appreciate the symmetry.
These are shorthand for the :earlier
and :later
commands, which are quite flexible. As an example, :earlier 5m
will go to whatever version you were looking at five minutes ago. Amazing.
Another example of interacting with the undo tree is :undolist
, which shows all leafs in the tree. To learn more about undo, try :help undo-redo
.
A type I’ve come to find very helpful is “numeric string”:
type NumericString = `${number}`
This type is a “subtype” of string, which means it can be used anywhere a string is expected, but not every string can be used where NumericString
is expected.
This is particularly helpful in describing API payloads which include numeric strings instead of numbers to represent numeric values which need to be exact or support high precision (and avoid floating point rounding errors) such as currencies.
]]>I use this as a much simpler version of private fields that doesn’t require a class interface and is scoped to a whole module rather than just a single class (fantastically helpful for a more functional programming style).
This is a great way to generate “subtypes” of a primitive like a string or number, which is particularly useful for representing things like URLs, UUIDs, and other things which are string-like but not strings.
You can emulate this behavior in TypeScript by lying to the compiler. For example, let’s create a UUID
type:
export type UUID = string & { [$uuid]: true }
declare const $uuid: unique symbol
export function isUUID(value: unknown): value is UUID {
return uuid.validate(value)
}
export function createUUID(): UUID {
return uuid.v4() as UUID
}
This introduces the type UUID
which you can use anywhere you use a string, but you can also write functions that accept only a UUID
and not just any string.
This works by telling TypeScript that there exists a variable called $uuid
that is a unique symbol (the result of calling Symbol()
) and that the type UUID
is both a string
and (&
) an object with a required property of that unique symbol1. However none of this exists at runtime, there is no variable or unique symbol, so there’s no way of actually creating a UUID
type outside of casting, which we only do in this bit of library code.
Perhaps you don’t actually want to expose that UUID
is implemented as a string
, just remove the string &
:
export type UUID = { [$uuid]: true }
This version cannot be used where a string
is expected, even though it’s still a string value at runtime.
This works surprisingly well, but there are shortcomings:
Errors are not specifically helpful. Provide the wrong value where UUID
is expected and see the details of this hack exposed.
This pattern is a bit inscrutable compared to Flow’s clearer explicit syntax. Don’t forget to include a comment explaining what this does.
There’s still no concept of “module private” fields without going around the type compiler yet again.
(As of TS v4.5) Equality checks require explicit typecasts. For example userEmail === "admin@site.co"
will fail tsc with “This condition will always return false”. This example can be resolved with the typecast ("admin@site.co" as Email)
. This should ideally not be necessary since the intent is to define a subtype, which should be comparable to the supertype.
More examples of where opaque types are useful:
URL
, Email
, Username
, ID
or anything else you might want to validate before using and offer a guarantee that if you have a value of that type, it has been validated.
HTML sanitization, to type a string which has been HTML sanitized, while having unsafe functions that accept only sanitized strings.
Subtypes of number, like integers or positive values.
Opaque types for numeric or string database IDs which may be used alongside other strings or numbers.
Unique types for overlapping identifiers such as classic MySQL auto increments that you’d hate to mix up, like UserID
and MessageID
.
I’d still love to see explicit support for this long-loved feature from Flow built into TypeScript, but this technique works reasonably well despite the shortcomings.
There are variants of this technique, a common alternative being {_brand: typeof $uuid}
but I like this one the best for a couple reasons. I don’t like the IDE showing ._brand
in the typeahead completion; it will not show a symbol property. I find it slightly more helpful to see the name of the symbol shown when encountering an error .
My company, like many in the tech industry, defines a progression of levels for engineers1. Others may use a set of titles, these are often interchangeable. To grossly simplify:
Level | Scope |
---|---|
Junior Eng | First few years in industry. Expected to learn quickly, complete assigned small projects with little guidance, and continue to grow. |
Mid-level Eng | 2-6 years in industry. Completes moderate projects with autonomy and contributes to the team in other ways. |
Senior Eng | 5 or more years experience. Accountable for large projects requiring a team and external stakeholders. Mentors and improves their team. |
Staff+ Eng2 | Typically over 10 years experience. Peer to managers, accountable for their team’s roadmap and technical vision. Then… things get complicated. |
Most companies provide expectations guidelines alongside these levels, often breaking things down by technical skills, soft skills, leadership, etc. These can be helpful tools for both engineers and managers to guide them in their career, but I’ve found this often breaks down at the Staff Eng level, for a few reasons:
There just aren’t that many Staff+ Eng to compare and generalize. Most organizations will have less than 1 in 10 engineers at this level.
Every one of these engineers is a different person, who found a unique impact relative to what was needed and what they’re good at. No two Staff+ Eng follow exactly the same path.
The gamut of potential impact is so diverse that what is central to one Staff Engineer’s impact may be near absent from another’s. It can be hard to even define base expectations.
A different approach is necessary. Rather than defining one set of expectations, describe Staff Eng Archetypes
The Tech Lead guides the technical execution of their team via day to day leadership. The most common Staff Eng archetype and a natural extension of the expectations of a Senior Eng. This is such a common path to management that there’s a term “TLM” for the variant which also takes on some people management, furthering their autonomy.
The Architect sets the vision for a domain, and is accountable for its success, often requiring many teams working together. This may be a purely technical vision or, more often for a front-end engineer, a product vision.
The Solver goes deep to solve problems no one else can. Not purely technical, really tough problems are often equal part organizational and require excellent communication skills and “bedside manner”.
The Right Hand extends a leader’s bandwidth by helping to operate a complex organization, converting inefficiencies into well run programs. They bounce between whatever is most urgent be it tech, people, process, or business.
If these all sound really different from each other, that’s the idea! There are just as many ways to becoming a Staff Eng as there are engineers with that title. I love that these four archetypes each highlight a very different core competency: tactical leadership, vision setting, technical excellence, and organized execution. It wouldn’t be possible to define base expectations across these for all Staff Eng.
I also appreciate that using archetypes is still flawed. Not everyone will fit into these four buckets, some may bridge between multiple, others will define their own path. For those with aspirations to become a Staff Eng and those that manage them, my advice is to not emulate others but find and lean into your own strengths.
Designers often have a very similar career progression. You can generally replace “engineer” with “designer” throughout this entire post. A fantastic comparable resource for designers is staff.design.
↩Some companies define titles beyond Staff Eng, like “Senior Staff Eng”, “Principle Eng”, “Distinguished Eng”, “Architect”, or “Technical Fellow”. What I share here is also true for these engineers, just further into the limit. These are often collectively referred to as “Staff+ Eng”.
↩One of many fantastic resources from staffeng.com!
↩I find this model a helpful tool in thinking about my own career, but it’s important to remember that it’s just that: a model. While I like the framework, as I’ve read more about Ikigai I also have some real concerns with it:
It’s mired in the pseudo-science of “self help” that I’m really suspicious of. Most books frame Ikigai as an elusive thing to start searching for today, that you need to find happiness, and need coaching to achieve.
It feels like yet another foreign cultural fetishization. Ikigai is a Japanese concept but nearly all material written on it is English.
The origins of Ikigai’s introduction to English readers is via a TED talk on the unusual lives of centenarians in Okinawa, Japan which don’t seem to relate to the venn diagram; where did that come from?
These concerns turn out to be valid. In fact the origin of this venn diagram is not Japanese at all, but instead comes from a book by Spanish Astrologist, Andrés Zuzunaga. Marc Winn’s blog post combined Andrés Zuzunaga’s original graphic with the idea of Ikigai presented in Dan Buettner’s TED talk and voilà, a meme!
Despite it’s shortcomings and misappropriation I still really like this mental model for considering career progression and debugging gaps in a sense of fulfillment. Here’s my translation of Andrés Zuzunaga’s original graphic in English:
Ikigai is still a very real concept, just not the same one as presented by most of these blog posts and books. Japanese neuroscientist Ken Mogi (who has also written a book on Ikigai, mostly about food) has a video addressing this venn diagram with an attempt to bring the term back to an original intent. In the video he proposes a new (crude, hand-drawn) diagram to take its place with two distinct changes:
The four circles of the venn diagram are replaced with two axis: small to big and private to public.
Rather than Ikigai being found at the center of this diagram, he emphatically repeats that all of it is Ikigai.
The goal being not to find the one perfect thing which checks all boxes but instead to cultivate a broad diversity of things big and small, public and private, to bring a rich multifaceted purpose to life.
I also appreciate this very different model, and took the liberty to capture Ken’s “true Ikigai diagram” with a few additions in a similar spirit:
Years later, Marc wrote a follow up post on the origins of his article on Ikigai and addressed how it took on a life of its own.
↩Modifier keys are held down so that other key presses result in a different symbol or command. But what if they’re pressed alone? Nothing happens? That’s an opportunity.
The first I tried was the “Vim key”. Caps lock is remapped to the much more useful control key when held. When pressed alone, it is escape. This key is really easy to reach from the home row and this made getting in and out of Vim insert mode feel much easier. However I found myself falling back to muscle memory to reach for escape or use the equivalent and now easier to press control+c.
Much more ergonomically offensive is backspace. I’m a terrible typist so backspace is probably one of my most pressed keys and far enough from the home row that my whole hand must move. So the next mapping I’ve tried is Caps lock as control and backspace. This muscle memory has been hard to unwire, so I’ve disabled my true backspace key.
Here’s a small part of my Karabiner “complex_modifications” configuration:
"manipulators": [
{
"type": "basic",
"from": {
"key_code": "caps_lock",
"modifiers": { "optional": ["any"] } },
"to": [ { "key_code": "left_control", "lazy": true } ],
"to_if_alone": [ { "key_code": "delete_or_backspace" } ] },
{
"type": "basic",
"from": {
"key_code": "delete_or_backspace",
"modifiers": { "optional": [ "all" ] } },
"to": [ { "key_code": "vk_none" } ] },
Also, this works both ways! Not only can you provide a behavior for pressing modifier keys on their own, but also consider making a typical key a modifier when held. As an example, I map the return key to behave as “right control” when held.
]]>grep
1. It was written overnight by the GOAT, Ken Thompson, back in the early 1970s. It allows you to search through a file (or stdin) for a regular expression pattern2. Useful then, useful now. Incredible.
However, it turns out that software written fifty years ago might start to show signs of age. Enter ripgrep, or rg
in your terminal. It is faster, it is aware of your .gitignore
files, it prints results in color, it has a modern RegExp engine, it supports full Unicode, it searches compressed files, it has an easy to use and very powerful set of options, and of course it takes half as many letters to type.
Do yourself a favor and install it:
brew install ripgrep
Want to learn more? Check out the ripgrep project page, this very in depth article explaining why it’s fast and how it works, or this fantastic resource comparing many grep replacements.
The name “grep” comes from g/re/p
– globally run a regular expression and print. It was literally that exact command extracted from the ed
text editor. There’s a great Computerphile video which tells this story.
Ken Thompson didn’t invent Regular Expressions, but did popularize their use in computers. One of many reasons he is one of the greatest of all time.
↩tail
, cat
, and less
. Bash denizens will be familiar with the results.
Good luck if you try taking a look at a massive file, a file with long lines, or to get a quick read on some code. Maybe your habit is to open files in Vim or another editor so you can at least move around quickly and get some basic syntax highlighting.
There’s a better way…
Use bat
, it’s just cat
but with wings. It syntax highlights, it shows line numbers, it intelligently pipes to less
(if it needs to), it even integrates with git
to point out modified lines. It knows where its running and where its printing and falls back to the simple thing when you just need it to cat
a file.
Learn more about bat
at github.com/sharkdp/bat, and install with Homebrew:
brew install bat
One more thing. Combine bat
with prettier
as prettybat
, or with man
as batman
, a few of many bonus tools that comes with bat-extras
.
launchctl
.
The man page for launchctl is not particularly helpful, so it required some searching and experimenting to understand. This, along with launchd, are a powerful system for configuring services within the operating system. One way launchd determines that a service should be started is by watching for changes to a file. Perfect.
A “service” is any executable program (like a shell script or a JavaScript file) and a configuration plist file placed in a specific location. For example, lets set up a service that runs a website builder every time a source file is updated.
Here, ~/Library/LaunchAgents/com.leebyron.website-agent.plist
defines the name of my agent, the program to run, where to write standard output, and the path to watch. Changes to that path will run my program. Note that the program is not run inside a shell, so a node script needs to provide a full path to the node executable, rather than a executable “hash bang”.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.leebyron.website-agent</string>
<key>ProgramArguments</key>
<array>
<string>/Users/leebyron/.nvm/versions/node/v17.3.0/bin/node</string>
<string>/Users/leebyron/projects/website/generate.js</string>
</array>
<key>StandardOutPath</key>
<string>/Users/leebyron/projects/website/output.log</string>
<key>StandardErrorPath</key>
<string>/Users/leebyron/projects/website/output.log</string>
<key>WatchPaths</key>
<array>
<string>/Users/leebyron/projects/website/src</string>
</array>
</dict>
</plist>
Finally, tell launch control about this service. You’ll need to know your uid number (which you can find by running the id
command). Replace “500” here with whatever your uid happens to be.
launchctl bootstrap gui/500/ ~/Library/LaunchAgents/com.leebyron.website-agent.plist
To remove this service, replace bootstrap
with bootout
:
launchctl bootout gui/500/ ~/Library/LaunchAgents/com.leebyron.website-agent.plist
]]>
The first way is a cheat. In the first tab of Keyboard preferences, ensure “Press 🌐 to: ” is set to “Show Emoji & Symbols”. Now tap the fn
key to bring up the Symbols picker. The search does a half decent job of finding the symbol you need. Clicking it behaves as a key press.
The second way is best if you know the code. From the Keyboard preferences in the “Input Sources” tab, press the +
button and add the “Unicode Hex Input” keyboard. When using this keyboard, ⌥+<key>
no longer produces an alternate symbol, but instead allows you to type in hex codes.
Now by holding ⌥ and typing 229b
, I can type: ⊛
One small caveat is that this only supports 4-digit UTF-16 codes, but it does support surrogate pairs. This is annoying to type, but does allow entering Emoji directly via keyboard.
For example holding ⌥ and typing d83ddcbe
: 💾
In iTerm, open Preferences (or press Command-,
and in the Keys tab add a new Key Binding.
Keyboard Shortcut: ⌘z
Action: Send text with “vim” Special Chars
Value: \<M-u>
(The backslash is important)
Now within the terminal, hitting Command-Z (Undo) will map to Meta-U, which will perform Undo in Vim.
]]>To do so for a specific app:
defaults write com.googlecode.iterm2 ApplePressAndHoldEnabled -bool false
Or to disable system wide:
defaults write -g ApplePressAndHoldEnabled -bool false
After changing this setting, you need to restart the app or sign in session.
I learned this TIL recently from @rsms care of his excellent macOS Fixes doc.
]]>
In System Preferences → Keyboard, press “Modifier Keys…” to open a menu mapping physical keys to the action they apply. I always remap my caps lock key to “Control”, since I spend a fair amount of time in Terminal, where Control is used a fair amount (like Control-C to send SIGINT
, or Control-Z to send SIGTSTP
).
There is other software for much more powerful key remapping, which I’ll write about in the future. It’s nice that this is an OS-provided preference, so you can quickly set it without installing anything.
↩There’s a faster way.
After selecting text, press ⌘E to fill that text into the find buffer. It may look like nothing happened, but now press ⌘G to find the next instance of that text (⌘⇧G to find the previous). Continue pressing ⌘G until you find the instance you’re looking for.
This has a benefit of not changing modes; your cursor remains within the text document rather than being caught by the find input.
This works in Safari, Chrome, TextEdit, Notes, VSCode, Terminal (but not iTerm), and most other places you work with text in macOS.
]]>Here’s how to get rid of it.
Remove everything from the Desktop folder.
Make sure nothing is being automatically saved to the desktop. The most likely culprit is Screenshot.app.
Disable the Desktop folder from the desktop, and restart Finder
defaults write com.apple.finder CreateDesktop false
Killall Finder
Open Finder, right click Desktop in sidebar and “Remove from Sidebar”
You can’t actually delete the Desktop folder itself, because Finder will just recreate it upon finding it missing. You can instead render it useless by symlinking it back to your home directory1.
rm -rf ~/Desktop
ln -s ~ ~/Desktop
sudo chflags -h schg ~/Desktop
This last step stops Finder from replacing the symlink with an empty directory.
As an aside, if you want to keep your Desktop but want to put it somewhere else (like within Dropbox), these same symlinking steps will achieve this, just change the linked location.
↩Capture your entire screen with Command+Shift+3
Capture a portion of the screen with Command+Shift+4, then drag and release a rectangle.
Before dragging, press space bar to toggle on capture window mode, then click the window to capture. This has the nice benefit of including the window’s drop shadow with image transparency.
While dragging, hold space bar to change from sizing the capture area to moving the capture area.
Pull up the Screenshot app with Command+Shift+51, and access the three image capture modes as well as two video capture modes and an options menu.
By default all screenshots are saved to the Desktop. To keep your Desktop from becoming a junk drawer, I highly recommend saving them in some other dedicated location. I personally create a folder called “Screenshots” that I put at the top level of my Dropbox folder. If you don’t use Dropbox, consider a folder in your home directory.
Open the Screenshot app and in the bar that appears click “Options”, and under the “Save to” heading choose “Other Location” and choose or create the dedicated folder. Afterwards, I recommend adding your Screenshots folder to your Finder’s sidebar for quick access.
While you’re at it, take a look at the other options. I like to disable “Show Floating Thumbnail” since that feature delays saving the screenshot to disk for a number of seconds.
Alternatively, you can use the defaults
terminal command:
defaults write com.apple.screencapture location "~/Dropbox/Screenshots"
To make sure it worked (and check your other settings):
defaults read com.apple.screencapture
Or you can open it directly via Finder. It’s in /Applications/Utilities
and called “Screenshot.app”
Raskin was no fan of the computer mouse2 and thought keyboard driven UIs could be much more powerful, and the Cat has a couple tricks which show that he was truly onto something, most notably: Leap.
The Cat keyboard is really unique. This computer has no mouse and no arrow keys3. Instead it has “Leap” keys under the space bar.
To move around you hold down a Leap key with your thumb (in the direction you want to move) and start typing the thing you want to move to. The cursor moves in real-time with each key press.
If you got it wrong you can hit “Undo” to go back.
To keep looking for the same thing, hold “Use Front”4 and “Leap Again”.
There’s a dedicated “Page” button so you can Leap through page by page to rapidly move through a large file (or press it on its own to create a new blank page).
Pressing both Leap keys highlights the text between the cursor and the previously leapt location. Leaping with highlighted text moves that text along with the cursor.
This is so different from what we’re used to that it seems like it might be frustrating to use and rightfully part of history instead of current kit, but I find this inspiring. I wish I could get my computer to work this way!
I’ve been using VIM exclusively the last few weeks and trying to get used to it. I’m realizing that I spend way more time moving around then I actually do typing new things. I’ve come to really appreciate the /
and ?
commands which search ahead and back for some text to move towards. It’s very powerful, but slightly awkward to use, so I find myself not using it as much as I should. Having that functionality under my thumbs would feel like a super power.
There aren’t that many Canon Cats still floating around, but if you want to get a feel for what using one of these is like, you can run its software in emulation mode thanks to the Internet Archive! How to use this is not very clear, but here’s what I figured out so far:
option keys (left and right) are Jump
\
key is Undo
control is “Use Front”
Full screen the emulator to immerse yourself!
Obviously, it’s not quite the same as having the Cat keyboard in front of you, but I still found it very curious to use and gave me confidence that this could be an easily learned and very fun to use digital world to live in.
There are surprisingly almost no modern tools that reference back to Leap and the Canon Cat. One notable exception is the Left text editor by Hundred Rabbits, which very recently added Leap.
Here are some resources with a lot more content, digitized instruction manuals, history, and other bits and bobs down this particular rabbit hole:
Finally, I’ll leave you with this convincing promotional video showing off the Cat in use, showing both more of what it can do5 as well as some truly excellent ’80s hairstyles.
Jef Raskin started the project by founding a company, Information Appliances, and called the computer SWYFT: “Superb With Your Favorite Typing”. Canon acquired his company and their marketing team came up with the name “Cat.” No idea if Apple intentionally took a pot-shot at Jef with Swift.
↩Jef Raskin briefly discussed the Canon Cat’s Leap innovation in an Feb 1992 episode of High Tech Heros, a public access show from Los Altos.
↩Arrow behavior is kinda still there. Tapping Leap on its own moves the cursor ahead by one. they fittingly called this “Creep”. Shift+Leap would scroll the page up and down.
↩The “Use Front” key is essentially your “Command” key, but they printed the commands on the front of the key caps, hence “Use Front”.
↩Like running calculations (or code!) live in a document with a key press instead of having a dedicated calc app. Really cool ideas.
↩This is the reality of the branch of the multiverse we find ourselves in tracing back to a quote from Sir Tim Berners Lee from 1999:
I have a dream for the Web [in which computers] become capable of analyzing all the data on the Web – the content, links, and transactions between people and computers. A “Semantic Web”, which makes this possible, has yet to emerge, but when it does, the day-to-day mechanisms of trade, bureaucracy and our daily lives will be handled by machines talking to machines.
Some variation of this is is right…
However, for us Webmasters, it’s tough to keep track of the peculiarities of how each platform would like to consume semantic information. Having just added some meta data to this very page, I’ll drop some links and tl;dr of what I learned along the way:
RDF (Resource Description Language) and OWL (Web Ontology Language) were early standards that live on. They don’t get a lot of direct practical use1. There is also microformats which still has some use, but is no longer preferred.
Open Graph is a standard proposed by Facebook which addressed the mess that was the web at the time2. It has a surprisingly broad schema and some quirks3. Since then, JSON-LD has become the preferred tool. JSON is way easier for representing data, and there’s a huge set of available schema.
Facebook (and other Meta apps, like Messenger) use Open Graph with some minor Facebook specific additions.
Use the share debugger to see what information was parsed and any warnings. It shows a share preview based on an older version of their desktop site and can’t be relied on.
Twitter uses Open Graph if it finds them, but adds “Card Tags”. This is most helpful when you want something to appear slightly differently on Twitter vs Facebook.
Use their card validator to see what a card will look like. This shows for desktop, I’m not aware of a mobile preview.
Google will also use Open Graph if it finds it, but prefers JSON-LD.
There are multiple validators for both generic JSON-LD and Google specific results. Despite being the most mature of these tools, I’ve found it least helpful in giving error messages. Caveat caelator.
One last resource that I found very helpful was the structured data linter. This tool understands just about all of the above. It’s not all that helpful for understanding how any one service will interpret your page, but is very useful to ensure the meta data you expect is being found, and that there are no inconsistencies or other problems.
RDF actually does get a fair amount of practical use via RDFa (RDF for attributes) because it can markup existing HTML files as opposed to requiring separate XML files. In fact, this site uses RDFa to embed licensing data in the footer! Check it out in the structured data linter.
↩Facebook made an interesting deck on the design decisions of Open Graph that details these problems. It’s an interesting read.
↩
Open Graph uses <meta>
tags, but annoyingly uses a property=
attribute which was borrowed from RDF but is non standard for a <meta>
tag. It ideally should have used name=
(which Twitter cards does). Someone should have caught that in code review.
RSS was created in 1999 (the best era of the web if you ask me) by Netscape as a simplification of RDF. It was …tossed over the wall. This was right about the time of the Netscape & AOL merger, and RSS disappeared from netscape.com. Meanwhile RSS was very popular in the early days of the internet and adopted by many media companies figuring out this whole world wide web thing. In 2002 the version of RSS we mostly know was created by an independent group (retitled from “Rich Site Summary” to “Really Simple Syndication”) but under dubious legal circumstances and under the shadow of the AOL/Netflix conglomerate which technically owned the copyright.
Emerge Atom, an open standard introduced in 2003 meant to replace RSS. It’s vendor neutral and open, solves some of RSS’s quirks, extensible, internationalized, championed by Google and other cool young tech companies, and even blessed by the IETF as RFC4287. And it still gets called “RSS”, sigh… open source is hard sometimes.
Two decades later, please use Atom for your syndication feeds. It has been hardened by two decades of use and still works great.
In fact, even this site has one! Consider subscribing with NetNewsWire, an open source feed reader.
]]>
0x00
0x10
0x20
0x30
0x40
0x50
0x60
0x70
0x00
␀
⌃@
\0
␐
⌃P
Space 0
@
P
`
p
0x01
␁
⌃A
␑
⌃Q
!
1
A
Q
a
q
0x02
␂
⌃B
␒
⌃R
"
2
B
R
b
r
0x03
␃
⌃C
␓
⌃S
#
3
C
S
c
s
0x04
␄
⌃D
␔
⌃T
$
4
D
T
d
t
0x05
␅
⌃E
␕
⌃U
%
5
E
U
e
u
0x06
␆
⌃F
␖
⌃V
&
6
F
V
f
v
0x07
␇
⌃G
\a
␗
⌃W
'
7
G
W
g
w
0x08
␈
⌃H
\b
␘
⌃X
(
8
H
X
h
x
0x09
␉
⌃I
\t
␙
⌃Y
)
9
I
Y
i
y
0x0A
␊
⌃J
\n
␚
⌃Z
*
:
J
Z
j
z
0x0B
␋
⌃K
\v
␛
⌃[
\e
+
;
K
[
k
{
0x0C
␌
⌃L
\f
␜
⌃\
,
<
L
\
l
|
0x0D
␍
⌃M
\r
␝
⌃]
-
=
M
]
m
}
0x0E
␎
⌃N
␞
⌃^
.
>
N
^
n
~
0x0F
␏
⌃O
␟
⌃_
/
?
O
_
o
␡
⌃?
The first 128 Unicode values are ASCII. UTF-8, the most common modern encoding, uses a variable number of bytes to cover the full Unicode spectrum, but just happens to use exactly one byte for the first 128 and exactly matches ASCII. That means every ancient ASCII file is also a valid modern UTF-8 file. This is a beautiful hack and a major reason for the success of UTF-8.
The number digits are carefully placed so BCD can be converted to ASCII and vice-versa in one instruction: ascii = bcd XOR 0x30
.
Many keys you still reach via “shift” on a modern keyboard are either 0x10
or 0x20
above their standard key, a holdover from mechanical typewriters.
Lowercase letters are exactly 0x20
above uppercase.
Your “control” key has a ⌃
on it because its original purpose was to remap typical keys to control keys by xor’ing the highest bit 0x40
(XOR
also happens to be ^
in C). Some of these vestiges of the past still work everywhere, and all should work in your terminal! Try ⌃H
for a home-row oriented backspace.
These days it’s really UTF-8 thats ubiquitous.
↩Get a clear desktop and open all windows you want to screencast, not full screened. Also open Quicktime Player.
Don’t forget to increase font size in any terminals, browsers, or IDEs to increase visibility. 100 columns works okay.
File → New Movie Recording
Don’t actually record a movie. This just abuses the webcam preview so you can have your face in the screen cast. Resize the window down and position it in the corner of your desktop.
File → New Screen Recording
Choose the “Record Selected Portion” and resize the area to be 1280×720 (or a large size of the same aspect ratio if your screen allows).
You can’t move windows around while resizing the recording area, but you can during recording. If necessary, start a recording to get windows all lined up.
Open options and make sure your microphone is on.
Record! Press ⌘⌃esc to stop. Trim the resulting video.
I did exactly this to record a screencast about this very tool!
Footnotes are created similar to shortcut reference links, but the identifier starts with a ^
.
order in which they are referenced[^a-note].
A footnote definition is formatted exactly like link reference definitions2: in a box followed by a comma.
[^a-note]: Despite footnotes appearing numbered, the identifier can be any text.
This is helpful if you may add more notes later and don't want to be bothered to
reorder them.
Despite footnotes appearing numbered, the identifier can be any text. This is helpful if you may add more notes later and don’t want to be bothered to reorder them.
↩Unlike a link reference definition, a footnote definition needs an empty line immediately after it, otherwise content from multiple lines is joined into a single paragraph.
↩Write references with a box around the reference identifier followed by a colon and the URL, optionally include a trailing title in quotes (which may go on the following line):
[links]: https://spec.commonmark.org/0.30/#links
[links]: https://spec.commonmark.org/0.30/#links 'Nearly a hundred tests'
Then within prose, refer to them with typical link syntax, but with a trailing box instead of parentheses. If the linked text is the same as the identifier, just use a standalone box.
Here is a link about [markdown links][links]. What's the web without [links]?
Here is a link about markdown links. What’s the web without links?
References can appear anywhere in a Markdown file. I often place them right after the paragraph where they’re used. If they’re used in multiple places, I’ll group them together at the end of a section or end of the whole document.
References can also be used for images. The syntax for the reference is the same, but the image use itself starts with an !
.
![moebius]
[moebius]: https://uploads4.wikiart.org/images/m-c-escher/bond-of-union.jpg
'Bond of Union, M.C. Escher 1956'
]]>
http://programming.sirrida.de/calcperm.php
I just used this to map sprite binary data to Unicode braille patterns. The 8 dots in a braille pattern can map to a byte, but perhaps not in the order you expect.
]]>c
key starts a chord to change something. This is super useful for changing the word under the cursor. cw
will change from the current position until the end of the next word, and ciw
will change the whole word under the cursor (read: c
hange i
n w
ord).
This, combined with other movements can lead to very quick changes. One I’ve found quite useful is ci"
which changes the contents of a quoted string, and ca"
(c
hange a
ll "
quoted) to include the quote marks themselves. Or when working in prose, cis
will change the current sentence and cip
the current paragraph.
Sometimes a change command is hard to think about first, or the area you want to change is subtly different from what the change command would do. Replace c
with v
to get a similar visual motion (like v3w
for 3 words), muck about, then press c
to change.
Changing the whole line (but keeping the indentation) is simply cc
.