diff --git a/server_apps.go b/server_apps.go
index 3cbe71a3fd11159a5427aada1b0154e8c9aa2b03..ddb53acefd7244f6ccf41a16713a40d52e637ea1 100644
--- a/server_apps.go
+++ b/server_apps.go
@@ -8,11 +8,13 @@ import (
 	"log"
 	"net/http"
 	"regexp"
+	"strings"
 	"time"
 
 	"github.com/go-git/go-git/v5"
 	"github.com/go-git/go-git/v5/config"
 	"github.com/go-git/go-git/v5/plumbing"
+	"github.com/go-git/go-git/v5/plumbing/object"
 	"github.com/go-git/go-git/v5/storage/memory"
 	"github.com/pelletier/go-toml/v2"
 )
@@ -28,6 +30,7 @@ type appInfo struct {
 	author      string
 	description string
 	version     int
+	commit      string
 }
 
 var (
@@ -35,44 +38,7 @@ var (
 	reAppRepo = regexp.MustCompile(`^([a-zA-Z\-_\.0-9]+)/([a-zA-Z\-_0-9]+)$`)
 )
 
-func (s *server) getAppInfo(ctx context.Context, repo string) (*appInfo, error) {
-	url := "https://git.flow3r.garden/" + repo
-	g, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
-		URL: url,
-	})
-	if err != nil {
-		return nil, fmt.Errorf("when cloning: %w", err)
-	}
-
-	cfg, err := g.Config()
-	if err != nil {
-		return nil, fmt.Errorf("when getting config: %w", err)
-	}
-	if len(cfg.Branches) < 1 {
-		return nil, fmt.Errorf("no branches")
-	}
-	var bname string
-	for name, _ := range cfg.Branches {
-		bname = name
-		break
-	}
-	bname = "main"
-	h, err := g.ResolveRevision(plumbing.Revision(bname))
-	if err != nil {
-		return nil, fmt.Errorf("resolving revision failed: %w", err)
-	}
-	err = g.Fetch(&git.FetchOptions{
-		RefSpecs: []config.RefSpec{
-			config.RefSpec(fmt.Sprintf("%s:refs/heads/%s", bname, bname)),
-		},
-	})
-	if err != nil && err != git.NoErrAlreadyUpToDate {
-		return nil, fmt.Errorf("fetch failed: %w", err)
-	}
-	obj, err := g.CommitObject(*h)
-	if err != nil {
-		return nil, fmt.Errorf("CommitObject(%v): %w", h, err)
-	}
+func (s *server) parseAppToml(ctx context.Context, obj *object.Commit) (*appInfo, error) {
 	f, err := obj.File("flow3r.toml")
 	if err != nil {
 		return nil, fmt.Errorf("toml open failed: %w", err)
@@ -129,9 +95,76 @@ func (s *server) getAppInfo(ctx context.Context, repo string) (*appInfo, error)
 		menu:        data.App.Menu,
 		description: data.Metadata.Description,
 		version:     data.Metadata.Version,
+		commit:      obj.Hash.String(),
 	}, nil
 }
 
+func (s *server) getAppInfo(ctx context.Context, repo string) (*appInfo, error) {
+	url := "https://git.flow3r.garden/" + repo
+	g, err := git.Clone(memory.NewStorage(), nil, &git.CloneOptions{
+		URL: url,
+	})
+	if err != nil {
+		return nil, fmt.Errorf("when cloning: %w", err)
+	}
+
+	cfg, err := g.Config()
+	if err != nil {
+		return nil, fmt.Errorf("when getting config: %w", err)
+	}
+	if len(cfg.Branches) < 1 {
+		return nil, fmt.Errorf("no branches")
+	}
+	var bname string
+	for name, _ := range cfg.Branches {
+		bname = name
+		break
+	}
+	h, err := g.ResolveRevision(plumbing.Revision(bname))
+	if err != nil {
+		return nil, fmt.Errorf("resolving revision failed: %w", err)
+	}
+	err = g.Fetch(&git.FetchOptions{
+		RefSpecs: []config.RefSpec{
+			config.RefSpec(fmt.Sprintf("%s:refs/heads/%s", bname, bname)),
+		},
+	})
+	if err != nil && err != git.NoErrAlreadyUpToDate {
+		return nil, fmt.Errorf("fetch failed: %w", err)
+	}
+	obj, err := g.CommitObject(*h)
+	if err != nil {
+		return nil, fmt.Errorf("CommitObject(%v): %w", h, err)
+	}
+
+	highestVer := 0
+	highsetVerNil := true
+	firstTime := make(map[int]*appInfo)
+	for {
+		info, err := s.parseAppToml(ctx, obj)
+		if err == nil {
+			ver := info.version
+			firstTime[ver] = info
+			if ver > highestVer || highsetVerNil {
+				highestVer = ver
+				highsetVerNil = false
+			}
+		}
+		if len(obj.ParentHashes) == 0 {
+			break
+		}
+		obj, err = g.CommitObject(obj.ParentHashes[0])
+		if err != nil {
+			return nil, fmt.Errorf("CommitObject(%v): %w", h, err)
+		}
+	}
+
+	if highsetVerNil {
+		return nil, fmt.Errorf("no version")
+	}
+	return firstTime[highestVer], nil
+}
+
 func (s *server) getAppRegistry(ctx context.Context) ([]*appDescriptor, error) {
 	s.gitMu.Lock()
 	defer s.gitMu.Unlock()
@@ -218,7 +251,6 @@ func (s *server) getAppRegistry(ctx context.Context) ([]*appDescriptor, error) {
 			log.Printf("App: %q: okay", app.repository)
 		}
 		app.appInfo = info
-		log.Printf("%+v", app.appInfo)
 	}
 
 	log.Printf("Got %d apps", len(res))
@@ -233,7 +265,9 @@ func (s *server) handleApps(w http.ResponseWriter, r *http.Request) {
 	}
 
 	type app struct {
-		RepoURL     string `json:"repoURL"`
+		RepoURL     string `json:"repoUrl"`
+		Commit      string `json:"commit"`
+		DownloadURL string `json:"downloadURL"`
 		Name        string `json:"name"`
 		Menu        string `json:"menu"`
 		Author      string `json:"author"`
@@ -253,8 +287,14 @@ func (s *server) handleApps(w http.ResponseWriter, r *http.Request) {
 		if a.appInfo == nil {
 			continue
 		}
+		// Guarenteed to be exactly 2 parts because of the regex
+		parts := strings.Split(a.repository, "/")
+		orga := parts[0]
+		repo := parts[1]
 		resp.Apps = append(resp.Apps, app{
 			RepoURL:     "https://git.flow3r.garden/" + a.repository,
+			Commit:      a.appInfo.commit,
+			DownloadURL: fmt.Sprintf("https://git.flow3r.garden/%s/%s/-/archive/%s/%s-%s.zip", orga, repo, a.appInfo.commit, repo, a.appInfo.commit),
 			Name:        a.appInfo.name,
 			Menu:        a.appInfo.menu,
 			Author:      a.appInfo.author,