RSSAmplifier

Blog

Shane Osbourne

Software Engineering

shane-o.devRSS feed ↗15 posts

Latest posts

Clean up your PRs

PRs are harder to review in GitHub when they contain countless threads from AI agents and bots. How did we end up here? We started off with the odd comment here or there when a bot finds an obvious bug in your code, or an automation ran... Fast forward a year and we're now having full conversations with AI bots during a pull request. This creates additional noise and makes it harder for humans to…

Is Test Blindness Another Second-Order Effect of AI Coding Agents?

Six months into widespread agent adoption, some productivity gains are clear. What's less obvious to me is what changed underneath. Before agents became the default, I'd circulated an internal memo across several of the teams I work with about what I expected to happen when code production was effectively democratised: more output, faster, but with a growing burden on reviewers and on long-term…

3 steps towards a faster TRU (Time to Reviewable URL)

Today I am formalizing TRU - a new metric for Frontend teams that shows how quickly you can produce reviewable work. By definition, it's also a good measurement of how quickly your team can iterate on ideas and bugfixes too. TRU ( T ime to R eviewable U RL) is the amount of time it takes for a code change to be reflected onto a public URL. Note: Not producing reviewable URLs yet? Don't worry,…

Shrinking a Rust binary output from 18mb to 7mb

Simple tweaks to the options in the release profile can dramatically reduce the footprint of the resulting binary After spotting an 18mb binary lurking in the node_modules folder of a project I'd released with napi-rs , I was curious about how much I could reduce it. I was convinced of two things: First, that I'd only be able to trim a megabyte here or there with a combo of various release…

Using Cargo workspaces with a `napi-rs` project

Using workspaces to manage crates and dependencies within a default `napi-rs` project. Creating a new project with https://napi.rs is as easy as: npm install -g @napi-rs/cli Followed by... napi new ... which is great. But I wanted to keep the project within a Cargo Workspace so I made the following changes (using the project name bslive , which stands for Browsersync Live, in case you wondered)…

Making a Rust application slower than the Node version. And then fixing it.

Whilst working on a new version of Browsersync I ran into an interesting example where my Rust version was not faster than the original NodeJS implementation - in fact it was 2x slower! 😱 In this previous post I outlined how the Browsersync proxy works: it sends requests to the target url with modified headers and then buffers the response body in memory before applying string replacements. I had…

Re-Writing in Rust == faster with more features

Making a feature faster than the node equivalent, whilst adding more capabilities At over 3.2m downloads a month from NPM, https://browsersync.io is still heavily used in the industry. As I continue to modernise the implementation (by working on a Rust port) I came across an interesting case study that became up to 5x faster in a real-world setting, whilst also allowing me to add additional…

How to avoid a potential bug with JSDoc + @typedef

With no build-step in sight, it can be tempting to put more and more type information into comments. Consider the following type definition: /** * @typedef { 'css' | 'xpath' } SelectorKind */ This declares a new type alias for a union of the literal types 'css' and 'xpath' , it can then be used in parameter position like so: /** * @param { SelectorKind } kind * @param { string } selector */…

At least 7 reasons to avoid @ts-expect-error and @ts-ignore

After a deep dive, I now consider both to be dangerous. Picture the scene. You've run into a type-level problem in your application, and you just want to suppress the error for now and come back later to resolve it. You know (via tests) that the value works, you just need to please Typescript temporarily so you can move forward... interface Args { selector : string ; element : HTMLElement }…

Type Assertions vs Default Values in Typescript + JSDoc

A practical example of using type assertions, despite their discouragement Typically, usage of the as keyword in Typescript is frowned upon, for good reason. Type-assertions represent a place in code where you're saying that you know better then tsc . Sometimes you do, often you don't. Others have written at length on this subject, so I don't want to enumerate the good/bad use-cases again here.…

Destructure with care. The story of a Typescript gotcha

A problem that even the strictest Typescript/ESLint settings cannot help with. The decision to inline types directly into method signatures vs extracting into an interface or type is a common discussion point at the moment. The inline vs interface vs type debate very much focuses on the RHS (right-hand-side) of the parameter definition - but today I'd like us to consider a dangerous pattern on the…

X-State, Service re-entry

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aliquam aperiam blanditiis, cumque dicta distinctio dolor eaque eos incidunt inventore minus provident quasi quia quibusdam quidem quos sequi tempora unde voluptatibus? document . querySelector ( '[name=room]' ) ?. addEventListener ( 'change' , ( e ) => { invariant ( e . target instanceof HTMLSelectElement ) service . send ( { type :…

Typescript and JSDoc, shared names for Types + Values

(written by a human, no AI content) I saw this pattern recently, taking advantage of the fact that Typescript doesn't mind if you re-use the same name for a value and a type. import * as z from "zod" // Define a schema export const User = z . object ( { name : z . string ( ) } ) // export an inferred type for use elsewhere // but, using the same name, `User` 🤯 export type User = z . infer <…

Test

import { type Metadata } from 'next' import { Providers } from '@/app/providers' import { Layout } from '@/components/Layout' import '@/styles/tailwind.css' export const metadata : Metadata = { title : { template : '%s - Shane Osbourne' , default : 'Shane Osbourne - Software designer, founder, and amateur astronaut' , } , description : 'I’m Shane, a software engineer from Nottinghamshire, England'…

Unwrap a value with the question mark operator in Rust

What's wrong with the following function in Rust? fn measure_cargo_toml ( ) -> usize { let toml = std :: fs :: read_to_string ( "Cargo.toml" ) ? ; toml . len ( ) } Putting whether this is an optimal way to read a file's length to one side for a moment, let's take a look into why this causes a compile-time error. std::fs::read_to_string("Cargo.toml") returns Result<String, std::io::Error> and we…