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, makes field types more self-documenting.
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-- 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
} Inspect the platform of a Docker image on a remote registry: docker manifest inspect [IMAGE_REFERENCE] --verbose | jq .Descriptor.platform
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.
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:
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.
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.
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.
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.