code: func uploadBytes(ctx context.Context, bs blobserver.StatReceiver, bb *Builder, size int64, s []span) *uploadBytesFuture {
  	future := newUploadBytesFuture()
  	parts := []BytesPart{}
  	addBytesParts(ctx, bs, &parts, s, future)
  
  	if err := bb.PopulateParts(size, parts); err != nil {
  		future.errc <- err
  		return future
  	}
  
  	// Hack until perkeep.org/issue/102 is fixed. If we happen to upload
  	// the "file" schema before any of its parts arrive, then the indexer
  	// can get confused.  So wait on the parts before, and then upload
  	// the "file" blob afterwards.
  	if bb.Type() == "file" {
  		future.errc <- nil
  		_, err := future.Get() // may not be nil, if children parts failed
  		future = newUploadBytesFuture()
  		if err != nil {
  			future.errc <- err
  			return future
  		}
  	}
  
  	json := bb.Blob().JSON()
  	br := blob.RefFromString(json)
  	future.br = br
  	go func() {
  		_, err := uploadString(ctx, bs, br, json)
  		future.errc <- err
  	}()
  	return future
  }
docs: // uploadBytes populates bb (a builder of either type "bytes" or
  // "file", which is a superset of "bytes"), sets it to the provided
  // size, and populates with provided spans.  The bytes or file schema
  // blob is uploaded and its blobref is returned.