September 19, 2025 @ 09:45 PM UTC
Although Hungarian notation is redundant and pointless when writing code (thanks to editor tooltips and type checking), it's still meaningful for JSON API requests and responses, sakes field types more self-documenting.
September 18, 2025 @ 02:57 AM UTC
TIL that when you do a fetch request to upload a FormData object containing a Blob (i.e. a multipart upload), Bun sets the filename of the part to "" (`filename=""`) while Node.js sets it to "blob" (`filename="blob"`). The Bun version is parsed in the Cloudflare Workers parsing logic as not a file part at all, but a string! This... made for an interesting debugging session. Bun version: ``` POST /uploads/undefined/parts/1 HTTP/1.1 x-api-key: sk_car_f1bQmchDDphTvHZJP14aNF Content-Type: multipart/form-data; boundary=-WebkitFormBoundary603f01c6ade8412bb9aed76026dd5aa1 Connection: keep-alive User-Agent: Bun/1.2.20 Accept: */* Host: localhost:3000 Accept-Encoding: gzip, deflate, br, zstd Content-Length: 5243094 ---WebkitFormBoundary603f01c6ade8412bb9aed76026dd5aa1 Content-Disposition: form-data; name="file"; filename="" Content-Type: application/octet-stream <DATA> ---WebkitFormBoundary3b78453f0cb946b4bc436dda5e193be5-- ``` Node.js version: ``` POST /uploads/undefined/parts/1 HTTP/1.1 host: localhost:3000 connection: keep-alive X-API-Key: sk_car_f1bQmchDDphTvHZJP14aNF content-type: multipart/form-data; boundary=----formdata-undici-090585224843 accept: */* accept-language: * sec-fetch-mode: cors user-agent: node accept-encoding: gzip, deflate content-length: 5243060 ------formdata-undici-090585224843 Content-Disposition: form-data; name="file"; filename="blob" Content-Type: application/octet-stream ------formdata-undici-075238484550-- ```
June 21, 2025 @ 02:50 AM UTC
A function that _should_ work to get the full extension of a file, with sanitization: // GetFullExtensionSanitized returns the full extension of a file (such as .tar.gz), as compared to // filepath.Ext, which only returns the last part of the extension (such as .gz). func GetFullExtensionSanitized(filename string) string { filename = filepath.Base(filename) extension := "" for { ext := filepath.Ext(filename) if ext == "" { break } extension = ext + extension filename = strings.TrimSuffix(filename, ext) } if len(extension) > 15 { // no extension is likely to be >15 characters, so we reject these return "" } var builder strings.Builder builder.Grow(len(extension)) var prev rune for _, r := range extension { if r == '.' && prev == '.' { continue } if !slices.Contains([]rune(".abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"), r) { continue } builder.WriteRune(r) prev = r } extension = builder.String() return extension }
June 18, 2025 @ 09:51 PM UTC
git archive `git stash create` to create an archive with the current working directory
June 17, 2025 @ 12:52 AM UTC
Inspect the platform of a Docker image on a remote registry: docker manifest inspect [IMAGE_REFERENCE] --verbose | jq .Descriptor.platform
May 29, 2025 @ 07:34 PM UTC
In case you're wondering why your registry auth isn't working when doing Docker operations using the Docker Go SDK, even though the Docker CLI works: You might need to use the Docker CLI library to authenticate with the registry. Ran into this issue with ECR + a Docker config that uses the OSX Keychain credential helper. See https://github.com/moby/moby/issues/34503 and https://github.com/moby/moby/issues/39377#issuecomment-1119914406.
April 25, 2025 @ 09:49 PM UTC
Prediction: People will be defining voice agent behavior using React-esque semantics sooner or later. I don't mean as part of a web app, I mean that voice-only agents will have conversational flows defined using React/JSX. Something like this, essentially (this will seem familiar if you've built a voice agent flow with Pipecat Flows or another orchestration tool): export function ConversationFlow({ patientName, patientBirthday }) { const [authStatus, setAuthStatus] = useState('unauthenticated'); return ( <Conversation> <Node> <System>You are a friendly voice assistant.</System> <System>Ask the user to confirm their name and their birthday.</System> <Tool name="verify" properties={{ name: ..., birthday: ... }} execute={(props) => { if (props.name === patientName && props.birthday === patientBirthday) { setAuthStatus('authenticated'); } else { setAuthStatus('forbidden'); } }} /> </Node> {authStatus === 'authenticated' ? <AuthenticatedFlow /> : <UnauthorizedFlow />} </Conversation> ); } This probably won't be the exact form, since you need to keep in mind that: 1. You can't "re-render" because you're laying things out in time instead of space. 2. The ordering of elements is meaningful, and state (such as `verified`) can only flow downwards since you can't go back in time. ...and design the affordances accordingly. But this model definitely feels more amenable to building good abstractions than defining conversation flows in code.
April 11, 2025 @ 06:57 PM UTC
Fired off a quick thread about a pet peeve of mine: git diffs. https://x.com/KabirGoel/status/1910768886955614672. This... might need a blog post.
April 09, 2025 @ 08:54 PM UTC
Something I've noticed about my work style: I'll hotfix things where necessary, but I'm uncomfortable with treating hotfixes as solutions. I strongly prefer to ask why something happened and how we can change the system around it to make it impossible--all the way down to the system's core design. (Of course, part of gaining maturity as an engineer is being able to weigh the benefits of changing a system with the cost of wranlging debt.) This is a useful way of thinking about and debugging organizational processes as well, and there you don't have to worry about the cost of paying off debt.
March 14, 2025 @ 08:55 PM UTC
You should never use magic numbers for your z-indexes. Centralize them using CSS variables in a global file. Your future self will thank you.