Notes

  • View

    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.

  • View

    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--
  • View

    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
    }
  • View

    git archive git stash create to create an archive with the current working directory

  • View

    Inspect the platform of a Docker image on a remote registry: docker manifest inspect [IMAGE_REFERENCE] --verbose | jq .Descriptor.platform

  • View

    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.

  • View

    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.

  • View

    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.

  • View

    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.

  • View

    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.