Finish over motion: deep coding blocks, waiting work while builds run, debugging without thrash, handoffs, and metrics that lie - with worked examples.
I once sent a maintenance client a weekly summary I was quietly proud of. Fourteen commits. Three pull requests reviewed for their other contractor. Two calls. A framework minor upgrade, a cleaner deploy script, and a fix for a deprecation warning that had been filling the logs for months. It was a long email, and every line of it was true.
The reply was one sentence: "Thanks, is the contact form still sending us spam?"
It was. I had looked at it on the Monday, decided it needed a honeypot field and some rate limiting, and then spent the rest of the week on work that was easier to start. Nobody would have called that week lazy, which is exactly what made it hard to see. I had been busy for five days, and the one thing the client noticed every morning in their inbox had not changed.
That is what I mean by fake busyness: genuine motion that does not add up to a change anyone outside your editor can see. Developers are unusually good at it, because our tools hand out so many small rewards. Green badges, cards sliding right, commit graphs filling in, a notification count dropping to zero. None of those are bad. They measure that something moved, and it is very easy to let them stand in for whether something finished.
That is what I mean by fake busyness: genuine motion that does not add up to a change anyone outside your editor can see.
What follows is the set of habits I've ended up with after a few years of client work, small Android apps and remote teams. None of them are clever. Most of them are ways of writing things down earlier than feels necessary.
Remote work makes motion feel like proof
On my first properly remote team I answered chat within about a minute, all day. Nobody could see me at a desk, so the little green dot next to my name felt like an attendance sheet. If someone asked a question at 09:04 and I replied at 09:05, that was evidence I was working.
The cost took me months to notice. My mornings were cut into ten-minute strips, and the feature I was meant to own kept sliding a day at a time. What finally changed it was a colleague saying, fairly gently in a retro, that she would rather get a reply from me at 11:00 with a merged pull request than at 09:05 with nothing to show. She wasn't complaining about my speed. She was pointing out that nobody had asked for it.
I think a lot of developer busyness on remote teams comes from this. You cannot be seen working, so you perform availability instead, and availability is the enemy of the kind of work that ships. If that sounds familiar, available all day is a bad system goes further into it. The rest of this page is about the other half: replacing visible motion with visible finishes.
Write the finish line before the first commit
The habit that has done the most for me is writing what finished means on the ticket before I touch the code. Not a goal. A sentence I could be held to.
Here is what happens without one. On a team I worked with, I had a webhook retry ticket, call it PAY-218. The test suite went green at 15:40. The card was already in the done column because someone had dragged it there at standup. The feature flag was still off, and nobody had clicked through a retry on staging. If I had said "PAY-218 is done" the next morning, everyone would have nodded. No user could hit that code path.
The finish line I should have written at the start, and did write the next time, looked like this: "Pull request merged. Staging orders 4412 and 4413 each retried once with the expected response codes. Flag still off. Product decides the rollout cohort Monday at 10:00 UTC." That sentence is boring, and that is the point. Either those things are true or they aren't.
When someone asks for a percentage, I give locations instead: "Signature check is in review. Staging has not been clicked. Flag is off." Percentages invite optimism. Locations don't. And if I did less than the sentence, I say less. "Merged, not verified" is a perfectly respectable status. "Done" when it isn't costs someone else an afternoon later. The longer version of this argument, including the release build that taught it to me properly, is in green CI is not finished work.
Screenshot
Screenshot · 16:9a ticket comment naming the merged pull request, two staging order checks, and the flag still off, showing a finish line someone can verify, tracker capture, ratio 16:9, screenshot
Name the block, and say how it ends
A focus block without a named target is browsing with a timer. I've done plenty of it: open the project, scroll a couple of files, check a log, and forty minutes later realise I never decided what I was trying to change.
So before I start, I write down one file, function or test. Then I decide what ends the block, because "when the timer goes" ends it at whatever moment I happened to get bored. Different kinds of work end differently:
Work
Rough length
It ends when
Feature slice with tests
45 to 90 minutes
The failing test passes, or a new test is red for the reason I named
Code review
25 to 40 minutes
The review is submitted, or a note says which file and line I resume at
Refactor
50 minutes or more
The extraction I named is done, callers updated, old path deleted
Incident
No timer
One person leads; everyone else stays async unless paged
Triage
15 to 25 minutes
The queue is in a known state and the window closes
The lengths are my guesses, not rules. On a cold branch it often takes me about twenty minutes just to get back to the point where I can make a useful change: check out, run the right test filter, remember which hypothesis died yesterday. A reply to chat at minute eight spends that warm-up for nothing, so I hold messages until the first meaningful commit or the first new failing test.
Twenty-five minutes is plenty for a review and nowhere near enough for a bug I haven't reproduced. Pomodoro for client work has more on picking interval lengths that match the job. If the room itself keeps breaking the block, that is a different problem with different fixes, and protect focus at home deals with it.
One goal, one branch. Opening a second ticket "just to see" is how I end up with five half-started threads and nothing to merge. If five small things genuinely are the job that day, I call it a scatter day out loud and plan it that way, which is what five small tickets are harder than one feature is about.
Fill the five-line start worksheet
Fill the five-line start worksheet
Primary: PAY-218 webhook retry, failing test and fix, branch retry-218
Secondary: review PR 512 only if it stays under fifteen minutes
If time: draft staging verify steps for orders 4412 and 4413
Waiting on: suite, about eighteen minutes; no second feature while it runs
First move: open the confirmWebhook test and run the filter
I write these five lines before the IDE finishes indexing and before I open chat. The order matters. If chat comes first, the day's priorities get decided by whoever messaged at 08:50.
The Primary has to be specific enough that a stranger could tell whether it happened. "Make progress on checkout and stay on top of reviews" fails that test. "PAY-218, failing test for the confirmWebhook retry, fix on branch retry-218, pushed before the 13:00 review window" passes it.
The First move line is the one people skip, and it is the one that matters most at 08:40 when I'm not quite awake. "Look at the code" is not a first move. "Open the test, run the filter, read the failure" is. Secondary exists for when the Primary lands early and an empty hour starts suggesting a brand new feature. If time is the line I can delete at 15:00 without guilt.
On a day an incident eats the morning, I rewrite the five lines at lunch rather than pretending the morning plan still holds. On a review day, the Primary is the review pile with a number on it, not a feature I'm not going to touch.
Rank waiting work by what it costs to drop
Suites, image builds, emulator boots and deploys all open gaps in the day, and gaps are where my fake busyness used to breed. An eighteen-minute test run felt too long to sit through, so I'd start something. Usually a second feature. Then the suite would come back red, I'd switch back, and twenty minutes later I couldn't remember where I'd left the second thing either.
What I do now is write a short waiting list before I start the command, ordered by how much it hurts to abandon each item halfway:
Gap
Fine to start
Leave alone
2 to 5 minutes
Stand up. Write the next command on the ticket.
A new chat thread
5 to 15 minutes
One small review, a log line, a doc typo
Anything that needs a new branch
15 to 45 minutes
An adjacent test, a config note, the handoff draft
A refactor with no stop line
45 minutes or more
The Secondary already on the five lines
The hope that the build ends in two minutes
During the PAY-218 suite, a good gap was drafting the staging steps for orders 4412 and 4413, touching PR 512 only if I could park it inside fifteen minutes, and then getting up. A bad gap was chat until the badge changed colour, followed by a red suite and no memory of the test filter. When the run returns, I read the failure before I read anything else. What to work on while a build or export runs has the longer version of this list.
Say publicly when you're debugging
Debugging is the most fragile state I work in. The hypothesis, the last command, the order of runs that finally reproduced the bug: almost none of it is written down, and one message can knock it over. So before I go in, I post a short banner where the team actually looks:
Debugging PAY-218. Async until 11:30 UTC unless this is a P1.
Hypothesis: signature check drops retries after the first 3DS timeout.
Last command: phpunit --filter confirmWebhook on branch retry-218.
It does two jobs. People who see it hold their non-urgent question, and I've written my hypothesis down, which is half the protection. As hypotheses die I note them on the ticket so I don't try them again after lunch. After an honest hour stuck, I stop adding theories, write down the reproduce steps and what I've ruled out, and ask one person. Why that state is so expensive to lose has its own page: interruptions are expensive during debugging.
Hand the branch over with state, proof, next and blocked
When someone in another time zone is going to pick up my branch, chat scroll is not a handoff, and neither is my memory of the afternoon. I use four fields:
State: branch retry-218, pull request link, staging preview, flag off
Proof: the command or URL that shows current behaviour
Next: the first physical step, with the owner's name
Blocked: who owes what, by when, and in which time zone
The lines that save the most time are the negative ones. "Do not rebase, the review comments sit on the second commit." "Do not restart the worker pool, I already did at 16:10 UTC and it changed nothing." Without those, the person picking it up repeats my last hour before starting theirs. If they can't open the preview or the secrets vault, the note should say who grants access, or the handoff is fiction. The fuller version is in leave work someone else can continue.
Metrics that flatter the wrong behaviour
Anything that gets counted will flatter whatever moves the count. I still keep a few numbers, but I pair each one with a sentence that would stay true if the dashboard went dark:
Signal
What it flatters
What I can defend instead
Points closed
Cards moving right
Merged and verified on the path I named
Lines changed
Volume
A diff a reviewer can tie to the ticket
Green CI
A badge
Green, plus the verify step, open or done
Hours in the IDE
Sitting down
A named block that ended in an artifact or a park note
Messages sent
Visible noise
A decision with an owner and a time
My client report is the obvious example. Fourteen commits was a number that flattered me. "Contact form spam: not fixed yet, honeypot planned for Tuesday" would have been the honest line, and the client would have preferred it, because it told them what to expect. After that week I changed the report to three headings: what changed for users, what is still open, and what I need from you. The commit count moved to the bottom, and a few weeks later I stopped including it at all. Nobody missed it.
Reviews at two published edges
A review dropped into the middle of a feature block charges the warm-up twice: once to leave the branch, once to come back. On teams where I've had the choice, I publish two review windows and keep them dull on purpose, something like 10:20 to 10:45 and 13:00 to 13:40. Outside those, the pile waits unless a pull request is genuinely blocking someone else's Primary.
Big reviews get a clock and a resume line. "Paused in PaymentsController at the retry loop. The null timestamp question is on the PR. Next pass starts at the error branch." I can restart from that. "Had a look" restarts nothing. If honest review work runs past about ninety minutes, tomorrow is a review day and the feature shrinks to fit. Plan a developer day with review and feature work covers how I split those.
Photograph
Photograph · 3:2two short review windows on a developer calendar with the morning kept for one branch, showing reviews held to the edges of the day, desk photo, ratio 3:2, stock
A day that is built to finish something
Put together, a normal feature day for me looks something like this. The times move around; the shape mostly doesn't:
Clock
Block
08:30
Five lines. A debug banner if I'm already inside a bug.
08:40 to 10:10
Failing test and the fix. Reviews stay closed.
10:20 to 10:45
Review window, including PR 512 if the gap is real.
10:45 to 11:10
Messages. Then chat closes again.
11:15 to 12:05
Implement, push, paste the verify plan into the ticket.
13:00 to 13:40
Remaining reviews. Stop at a named line if the clock wins.
14:00
Overlap with the team. Twenty minutes, questions written beforehand.
16:30
The status note below, or a handoff if ownership moves.
It will not survive every day, and it doesn't need to. If the pager fires overnight, the next morning's Primary becomes "stable, and written down", and I shrink it before standup rather than after. If I retried a flaky test until it went green, I file the command and the failure before I stop, because otherwise I'll meet it again next week with no memory of having seen it.
The end-of-day status update I leave for myself
The end-of-day status update I leave for myself
Done: pull request link, what I verified, flag state
Open: branch name, test filter, what is still dirty
Blocked: who, what, time, or none
First move: the file or command I run tomorrow
When the branch stays mine overnight, I skip the long handoff and write these four lines instead. I write them at the desk, not later. The version I compose in my head while making dinner always drops the test filter I was sure I would remember. If other people read my end-of-day note, the end-of-day status update has a team-facing format. If it's only for me, restart coding where you left off tomorrow covers the same facts as a restart card.
Pick Monday's change from what last week left behind
I wouldn't adopt all of this at once. Pick one rule based on what last week actually left in the repo:
What last week looked like
One rule for Monday
Green branches, nothing a user can hit
Done sentences name merge, verify and flag
Reviews ate every feature morning
Two published review windows; the first block stays on the branch
A night page erased the next morning
That Primary gets shrunk in writing before standup
Five small tickets and no finished slice
A named scatter day, or the list cut to one Primary
Build time turned into a second feature
The waiting list is written before the suite starts
The test I use on myself now is the one that client accidentally handed me. At the end of the week, can I answer "what changed for the people who use this?" in one sentence, without listing anything I did? If I can, the week was productive however few commits it took. If all I have is a long list of activity, I was busy, and busy is the easiest thing in this job to fake, including to yourself.
End of this piece
Writer
PrimedEcho
Independent notes on remote work, focus, and freelance practice. About this site.