diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index e5c3cd38c8a0e..e4b8beeeffadf 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -145,6 +145,7 @@ confirm_delete_selected = Confirm to delete all selected items?
 
 name = Name
 value = Value
+readme = Readme
 
 filter = Filter
 filter.clear = Clear Filter
@@ -1045,7 +1046,8 @@ generate_repo = Generate Repository
 generate_from = Generate From
 repo_desc = Description
 repo_desc_helper = Enter short description (optional)
-repo_lang = Language
+repo_no_desc = No description provided
+repo_lang = Languages
 repo_gitignore_helper = Select .gitignore templates.
 repo_gitignore_helper_desc = Choose which files not to track from a list of templates for common languages. Typical artifacts generated by each language's build tools are included on .gitignore by default.
 issue_labels = Issue Labels
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go
index 51da80e4d5be4..ad790875136c3 100644
--- a/routers/web/repo/blame.go
+++ b/routers/web/repo/blame.go
@@ -114,12 +114,6 @@ func RefBlame(ctx *context.Context) {
 	ctx.Data["UsesIgnoreRevs"] = result.UsesIgnoreRevs
 	ctx.Data["FaultyIgnoreRevsFile"] = result.FaultyIgnoreRevsFile
 
-	// Get Topics of this repo
-	renderRepoTopics(ctx)
-	if ctx.Written() {
-		return
-	}
-
 	commitNames := processBlameParts(ctx, result.Parts)
 	if ctx.Written() {
 		return
diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go
index 4a62237838a4a..dc170742b93e4 100644
--- a/routers/web/repo/branch.go
+++ b/routers/web/repo/branch.go
@@ -89,7 +89,6 @@ func Branches(ctx *context.Context) {
 	pager := context.NewPagination(int(branchesCount), pageSize, page, 5)
 	pager.SetDefaultParams(ctx)
 	ctx.Data["Page"] = pager
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
 	ctx.HTML(http.StatusOK, tplBranch)
 }
 
diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go
index c5652784fa012..6d53df7c10917 100644
--- a/routers/web/repo/commit.go
+++ b/routers/web/repo/commit.go
@@ -102,7 +102,6 @@ func Commits(ctx *context.Context) {
 	pager := context.NewPagination(int(commitsCount), pageSize, page, 5)
 	pager.SetDefaultParams(ctx)
 	ctx.Data["Page"] = pager
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
 	ctx.HTML(http.StatusOK, tplCommits)
 }
 
@@ -219,8 +218,6 @@ func SearchCommits(ctx *context.Context) {
 	}
 	ctx.Data["Username"] = ctx.Repo.Owner.Name
 	ctx.Data["Reponame"] = ctx.Repo.Repository.Name
-	ctx.Data["RefName"] = ctx.Repo.RefName
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
 	ctx.HTML(http.StatusOK, tplCommits)
 }
 
@@ -266,7 +263,6 @@ func FileHistory(ctx *context.Context) {
 	pager := context.NewPagination(int(commitsCount), setting.Git.CommitsRangeSize, page, 5)
 	pager.SetDefaultParams(ctx)
 	ctx.Data["Page"] = pager
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
 	ctx.HTML(http.StatusOK, tplCommits)
 }
 
diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go
index c178ba249166c..b3a91a6070744 100644
--- a/routers/web/repo/release.go
+++ b/routers/web/repo/release.go
@@ -31,7 +31,6 @@ import (
 	"code.gitea.io/gitea/services/context/upload"
 	"code.gitea.io/gitea/services/forms"
 	releaseservice "code.gitea.io/gitea/services/release"
-	repo_service "code.gitea.io/gitea/services/repository"
 )
 
 const (
@@ -153,9 +152,6 @@ func Releases(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.releases")
 	ctx.Data["IsViewBranch"] = false
 	ctx.Data["IsViewTag"] = true
-	// Disable the showCreateNewBranch form in the dropdown on this page.
-	ctx.Data["CanCreateBranch"] = false
-	ctx.Data["HideBranchesInDropdown"] = true
 
 	listOptions := db.ListOptions{
 		Page:     ctx.FormInt("page"),
@@ -193,9 +189,6 @@ func Releases(ctx *context.Context) {
 	pager := context.NewPagination(int(numReleases), listOptions.PageSize, listOptions.Page, 5)
 	pager.SetDefaultParams(ctx)
 	ctx.Data["Page"] = pager
-
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
-
 	ctx.HTML(http.StatusOK, tplReleasesList)
 }
 
@@ -205,9 +198,6 @@ func TagsList(ctx *context.Context) {
 	ctx.Data["Title"] = ctx.Tr("repo.release.tags")
 	ctx.Data["IsViewBranch"] = false
 	ctx.Data["IsViewTag"] = true
-	// Disable the showCreateNewBranch form in the dropdown on this page.
-	ctx.Data["CanCreateBranch"] = false
-	ctx.Data["HideBranchesInDropdown"] = true
 	ctx.Data["CanCreateRelease"] = ctx.Repo.CanWrite(unit.TypeReleases) && !ctx.Repo.Repository.IsArchived
 
 	namePattern := ctx.FormTrim("q")
@@ -254,8 +244,6 @@ func TagsList(ctx *context.Context) {
 	pager.SetDefaultParams(ctx)
 	ctx.Data["Page"] = pager
 	ctx.Data["PageIsViewCode"] = !ctx.Repo.Repository.UnitEnabled(ctx, unit.TypeReleases)
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
-
 	ctx.HTML(http.StatusOK, tplTagsList)
 }
 
diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go
index e6c25d75e9315..e43841acd365c 100644
--- a/routers/web/repo/view.go
+++ b/routers/web/repo/view.go
@@ -5,18 +5,13 @@
 package repo
 
 import (
-	"bytes"
 	gocontext "context"
-	"encoding/base64"
 	"errors"
 	"fmt"
 	"html/template"
-	"image"
 	"io"
 	"net/http"
 	"net/url"
-	"path"
-	"slices"
 	"strings"
 	"time"
 
@@ -29,33 +24,21 @@ import (
 	asymkey_model "code.gitea.io/gitea/models/asymkey"
 	"code.gitea.io/gitea/models/db"
 	git_model "code.gitea.io/gitea/models/git"
-	issue_model "code.gitea.io/gitea/models/issues"
-	access_model "code.gitea.io/gitea/models/perm/access"
-	"code.gitea.io/gitea/models/renderhelper"
 	repo_model "code.gitea.io/gitea/models/repo"
 	unit_model "code.gitea.io/gitea/models/unit"
 	user_model "code.gitea.io/gitea/models/user"
-	"code.gitea.io/gitea/modules/actions"
 	"code.gitea.io/gitea/modules/base"
 	"code.gitea.io/gitea/modules/charset"
 	"code.gitea.io/gitea/modules/git"
-	"code.gitea.io/gitea/modules/highlight"
 	"code.gitea.io/gitea/modules/lfs"
 	"code.gitea.io/gitea/modules/log"
 	"code.gitea.io/gitea/modules/markup"
-	repo_module "code.gitea.io/gitea/modules/repository"
 	"code.gitea.io/gitea/modules/setting"
 	"code.gitea.io/gitea/modules/structs"
-	"code.gitea.io/gitea/modules/svg"
 	"code.gitea.io/gitea/modules/typesniffer"
 	"code.gitea.io/gitea/modules/util"
-	"code.gitea.io/gitea/routers/web/feed"
 	"code.gitea.io/gitea/services/context"
-	issue_service "code.gitea.io/gitea/services/issue"
 	repo_service "code.gitea.io/gitea/services/repository"
-	files_service "code.gitea.io/gitea/services/repository/files"
-
-	"github.com/nektos/act/pkg/model"
 
 	_ "golang.org/x/image/bmp"  // for processing bmp images
 	_ "golang.org/x/image/webp" // for processing webp images
@@ -70,140 +53,6 @@ const (
 	tplMigrating    base.TplName = "repo/migrate/migrating"
 )
 
-// locate a README for a tree in one of the supported paths.
-//
-// entries is passed to reduce calls to ListEntries(), so
-// this has precondition:
-//
-//	entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
-//
-// FIXME: There has to be a more efficient way of doing this
-func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
-	// Create a list of extensions in priority order
-	// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
-	// 2. Txt files - e.g. README.txt
-	// 3. No extension - e.g. README
-	exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
-	extCount := len(exts)
-	readmeFiles := make([]*git.TreeEntry, extCount+1)
-
-	docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
-	for _, entry := range entries {
-		if tryWellKnownDirs && entry.IsDir() {
-			// as a special case for the top-level repo introduction README,
-			// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
-			// (note that docsEntries is ignored unless we are at the root)
-			lowerName := strings.ToLower(entry.Name())
-			switch lowerName {
-			case "docs":
-				if entry.Name() == "docs" || docsEntries[0] == nil {
-					docsEntries[0] = entry
-				}
-			case ".gitea":
-				if entry.Name() == ".gitea" || docsEntries[1] == nil {
-					docsEntries[1] = entry
-				}
-			case ".github":
-				if entry.Name() == ".github" || docsEntries[2] == nil {
-					docsEntries[2] = entry
-				}
-			}
-			continue
-		}
-		if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
-			log.Debug("Potential readme file: %s", entry.Name())
-			if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
-				if entry.IsLink() {
-					target, err := entry.FollowLinks()
-					if err != nil && !git.IsErrBadLink(err) {
-						return "", nil, err
-					} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
-						readmeFiles[i] = entry
-					}
-				} else {
-					readmeFiles[i] = entry
-				}
-			}
-		}
-	}
-	var readmeFile *git.TreeEntry
-	for _, f := range readmeFiles {
-		if f != nil {
-			readmeFile = f
-			break
-		}
-	}
-
-	if ctx.Repo.TreePath == "" && readmeFile == nil {
-		for _, subTreeEntry := range docsEntries {
-			if subTreeEntry == nil {
-				continue
-			}
-			subTree := subTreeEntry.Tree()
-			if subTree == nil {
-				// this should be impossible; if subTreeEntry exists so should this.
-				continue
-			}
-			childEntries, err := subTree.ListEntries()
-			if err != nil {
-				return "", nil, err
-			}
-
-			subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
-			if err != nil && !git.IsErrNotExist(err) {
-				return "", nil, err
-			}
-			if readmeFile != nil {
-				return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil
-			}
-		}
-	}
-
-	return "", readmeFile, nil
-}
-
-func renderDirectory(ctx *context.Context) {
-	entries := renderDirectoryFiles(ctx, 1*time.Second)
-	if ctx.Written() {
-		return
-	}
-
-	if ctx.Repo.TreePath != "" {
-		ctx.Data["HideRepoInfo"] = true
-		ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
-	}
-
-	subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
-	if err != nil {
-		ctx.ServerError("findReadmeFileInEntries", err)
-		return
-	}
-
-	renderReadmeFile(ctx, subfolder, readmeFile)
-}
-
-// localizedExtensions prepends the provided language code with and without a
-// regional identifier to the provided extension.
-// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
-// Note: ext should be prefixed with a `.`
-func localizedExtensions(ext, languageCode string) (localizedExts []string) {
-	if len(languageCode) < 1 {
-		return []string{ext}
-	}
-
-	lowerLangCode := "." + strings.ToLower(languageCode)
-
-	if strings.Contains(lowerLangCode, "-") {
-		underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
-		indexOfDash := strings.Index(lowerLangCode, "-")
-		// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
-		return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
-	}
-
-	// e.g. [.en.md, .md]
-	return []string{lowerLangCode + ext, ext}
-}
-
 type fileInfo struct {
 	isTextFile bool
 	isLFSFile  bool
@@ -261,85 +110,6 @@ func getFileReader(ctx gocontext.Context, repoID int64, blob *git.Blob) ([]byte,
 	return buf, dataRc, &fileInfo{st.IsText(), true, meta.Size, &meta.Pointer, st}, nil
 }
 
-func renderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
-	target := readmeFile
-	if readmeFile != nil && readmeFile.IsLink() {
-		target, _ = readmeFile.FollowLinks()
-	}
-	if target == nil {
-		// if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
-		// simply skip rendering the README
-		return
-	}
-
-	ctx.Data["RawFileLink"] = ""
-	ctx.Data["ReadmeInList"] = true
-	ctx.Data["ReadmeExist"] = true
-	ctx.Data["FileIsSymlink"] = readmeFile.IsLink()
-
-	buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, target.Blob())
-	if err != nil {
-		ctx.ServerError("getFileReader", err)
-		return
-	}
-	defer dataRc.Close()
-
-	ctx.Data["FileIsText"] = fInfo.isTextFile
-	ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
-	ctx.Data["FileSize"] = fInfo.fileSize
-	ctx.Data["IsLFSFile"] = fInfo.isLFSFile
-
-	if fInfo.isLFSFile {
-		filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
-		ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
-	}
-
-	if !fInfo.isTextFile {
-		return
-	}
-
-	if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
-		// Pretend that this is a normal text file to display 'This file is too large to be shown'
-		ctx.Data["IsFileTooLarge"] = true
-		ctx.Data["IsTextFile"] = true
-		return
-	}
-
-	rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
-
-	if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" {
-		ctx.Data["IsMarkup"] = true
-		ctx.Data["MarkupType"] = markupType
-
-		rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
-			CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
-			CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
-		}).
-			WithMarkupType(markupType).
-			WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
-
-		ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
-		if err != nil {
-			log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
-			delete(ctx.Data, "IsMarkup")
-		}
-	}
-
-	if ctx.Data["IsMarkup"] != true {
-		ctx.Data["IsPlainText"] = true
-		content, err := io.ReadAll(rd)
-		if err != nil {
-			log.Error("Read readme content failed: %v", err)
-		}
-		contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
-		ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
-	}
-
-	if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
-		ctx.Data["CanEditReadmeFile"] = true
-	}
-}
-
 func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
 	// Show latest commit info of repository in table header,
 	// or of directory if not in root directory.
@@ -371,287 +141,6 @@ func loadLatestCommitData(ctx *context.Context, latestCommit *git.Commit) bool {
 	return true
 }
 
-func renderFile(ctx *context.Context, entry *git.TreeEntry) {
-	ctx.Data["IsViewFile"] = true
-	ctx.Data["HideRepoInfo"] = true
-	blob := entry.Blob()
-	buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
-	if err != nil {
-		ctx.ServerError("getFileReader", err)
-		return
-	}
-	defer dataRc.Close()
-
-	ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
-	ctx.Data["FileIsSymlink"] = entry.IsLink()
-	ctx.Data["FileName"] = blob.Name()
-	ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
-
-	commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
-	if err != nil {
-		ctx.ServerError("GetCommitByPath", err)
-		return
-	}
-
-	if !loadLatestCommitData(ctx, commit) {
-		return
-	}
-
-	if ctx.Repo.TreePath == ".editorconfig" {
-		_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
-		if editorconfigWarning != nil {
-			ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error())
-		}
-		if editorconfigErr != nil {
-			ctx.Data["FileError"] = strings.TrimSpace(editorconfigErr.Error())
-		}
-	} else if issue_service.IsTemplateConfig(ctx.Repo.TreePath) {
-		_, issueConfigErr := issue_service.GetTemplateConfig(ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.Commit)
-		if issueConfigErr != nil {
-			ctx.Data["FileError"] = strings.TrimSpace(issueConfigErr.Error())
-		}
-	} else if actions.IsWorkflow(ctx.Repo.TreePath) {
-		content, err := actions.GetContentFromEntry(entry)
-		if err != nil {
-			log.Error("actions.GetContentFromEntry: %v", err)
-		}
-		_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
-		if workFlowErr != nil {
-			ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
-		}
-	} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
-		if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
-			_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
-			if len(warnings) > 0 {
-				ctx.Data["FileWarning"] = strings.Join(warnings, "\n")
-			}
-		}
-	}
-
-	isDisplayingSource := ctx.FormString("display") == "source"
-	isDisplayingRendered := !isDisplayingSource
-
-	if fInfo.isLFSFile {
-		ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
-	}
-
-	isRepresentableAsText := fInfo.st.IsRepresentableAsText()
-	if !isRepresentableAsText {
-		// If we can't show plain text, always try to render.
-		isDisplayingSource = false
-		isDisplayingRendered = true
-	}
-	ctx.Data["IsLFSFile"] = fInfo.isLFSFile
-	ctx.Data["FileSize"] = fInfo.fileSize
-	ctx.Data["IsTextFile"] = fInfo.isTextFile
-	ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
-	ctx.Data["IsDisplayingSource"] = isDisplayingSource
-	ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
-	ctx.Data["IsExecutable"] = entry.IsExecutable()
-
-	isTextSource := fInfo.isTextFile || isDisplayingSource
-	ctx.Data["IsTextSource"] = isTextSource
-	if isTextSource {
-		ctx.Data["CanCopyContent"] = true
-	}
-
-	// Check LFS Lock
-	lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
-	ctx.Data["LFSLock"] = lfsLock
-	if err != nil {
-		ctx.ServerError("GetTreePathLock", err)
-		return
-	}
-	if lfsLock != nil {
-		u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
-		if err != nil {
-			ctx.ServerError("GetTreePathLock", err)
-			return
-		}
-		ctx.Data["LFSLockOwner"] = u.Name
-		ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
-		ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
-	}
-
-	// Assume file is not editable first.
-	if fInfo.isLFSFile {
-		ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
-	} else if !isRepresentableAsText {
-		ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
-	}
-
-	switch {
-	case isRepresentableAsText:
-		if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
-			ctx.Data["IsFileTooLarge"] = true
-			break
-		}
-
-		if fInfo.st.IsSvgImage() {
-			ctx.Data["IsImageFile"] = true
-			ctx.Data["CanCopyContent"] = true
-			ctx.Data["HasSourceRenderedToggle"] = true
-		}
-
-		rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
-
-		shouldRenderSource := ctx.FormString("display") == "source"
-		readmeExist := util.IsReadmeFileName(blob.Name())
-		ctx.Data["ReadmeExist"] = readmeExist
-
-		markupType := markup.DetectMarkupTypeByFileName(blob.Name())
-		if markupType == "" {
-			markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
-		}
-		if markupType != "" {
-			ctx.Data["HasSourceRenderedToggle"] = true
-		}
-		if markupType != "" && !shouldRenderSource {
-			ctx.Data["IsMarkup"] = true
-			ctx.Data["MarkupType"] = markupType
-			metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
-			metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
-			rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
-				CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
-				CurrentTreePath: path.Dir(ctx.Repo.TreePath),
-			}).
-				WithMarkupType(markupType).
-				WithRelativePath(ctx.Repo.TreePath).
-				WithMetas(metas)
-
-			ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
-			if err != nil {
-				ctx.ServerError("Render", err)
-				return
-			}
-			// to prevent iframe load third-party url
-			ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
-		} else {
-			buf, _ := io.ReadAll(rd)
-
-			// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
-			//   empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
-			// Gitea uses the definition (like most modern editors):
-			//   empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
-			//   When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
-			//   To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
-			// This NumLines is only used for the display on the UI: "xxx lines"
-			if len(buf) == 0 {
-				ctx.Data["NumLines"] = 0
-			} else {
-				ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
-			}
-
-			language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
-			if err != nil {
-				log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
-			}
-
-			fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
-			ctx.Data["LexerName"] = lexerName
-			if err != nil {
-				log.Error("highlight.File failed, fallback to plain text: %v", err)
-				fileContent = highlight.PlainText(buf)
-			}
-			status := &charset.EscapeStatus{}
-			statuses := make([]*charset.EscapeStatus, len(fileContent))
-			for i, line := range fileContent {
-				statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
-				status = status.Or(statuses[i])
-			}
-			ctx.Data["EscapeStatus"] = status
-			ctx.Data["FileContent"] = fileContent
-			ctx.Data["LineEscapeStatus"] = statuses
-		}
-		if !fInfo.isLFSFile {
-			if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
-				if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
-					ctx.Data["CanEditFile"] = false
-					ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
-				} else {
-					ctx.Data["CanEditFile"] = true
-					ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
-				}
-			} else if !ctx.Repo.IsViewBranch {
-				ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
-			} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
-				ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
-			}
-		}
-
-	case fInfo.st.IsPDF():
-		ctx.Data["IsPDFFile"] = true
-	case fInfo.st.IsVideo():
-		ctx.Data["IsVideoFile"] = true
-	case fInfo.st.IsAudio():
-		ctx.Data["IsAudioFile"] = true
-	case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
-		ctx.Data["IsImageFile"] = true
-		ctx.Data["CanCopyContent"] = true
-	default:
-		if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
-			ctx.Data["IsFileTooLarge"] = true
-			break
-		}
-
-		// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
-		// It is used by "external renders", markupRender will execute external programs to get rendered content.
-		if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
-			rd := io.MultiReader(bytes.NewReader(buf), dataRc)
-			ctx.Data["IsMarkup"] = true
-			ctx.Data["MarkupType"] = markupType
-
-			rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
-				CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
-				CurrentTreePath: path.Dir(ctx.Repo.TreePath),
-			}).
-				WithMarkupType(markupType).
-				WithRelativePath(ctx.Repo.TreePath)
-
-			ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
-			if err != nil {
-				ctx.ServerError("Render", err)
-				return
-			}
-		}
-	}
-
-	if ctx.Repo.GitRepo != nil {
-		checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID)
-		if checker != nil {
-			defer deferable()
-			attrs, err := checker.CheckPath(ctx.Repo.TreePath)
-			if err == nil {
-				ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value()
-				ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value()
-			}
-		}
-	}
-
-	if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
-		img, _, err := image.DecodeConfig(bytes.NewReader(buf))
-		if err == nil {
-			// There are Image formats go can't decode
-			// Instead of throwing an error in that case, we show the size only when we can decode
-			ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
-		}
-	}
-
-	if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
-		if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
-			ctx.Data["CanDeleteFile"] = false
-			ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
-		} else {
-			ctx.Data["CanDeleteFile"] = true
-			ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
-		}
-	} else if !ctx.Repo.IsViewBranch {
-		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
-	} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
-		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
-	}
-}
-
 func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output template.HTML, err error) {
 	markupRd, markupWr := io.Pipe()
 	defer markupWr.Close()
@@ -728,59 +217,6 @@ func checkHomeCodeViewable(ctx *context.Context) {
 	ctx.NotFound("Home", errors.New(ctx.Locale.TrString("units.error.no_unit_allowed_repo")))
 }
 
-func checkCitationFile(ctx *context.Context, entry *git.TreeEntry) {
-	if entry.Name() != "" {
-		return
-	}
-	tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
-	if err != nil {
-		HandleGitError(ctx, "Repo.Commit.SubTree", err)
-		return
-	}
-	allEntries, err := tree.ListEntries()
-	if err != nil {
-		ctx.ServerError("ListEntries", err)
-		return
-	}
-	for _, entry := range allEntries {
-		if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
-			// Read Citation file contents
-			if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
-				log.Error("checkCitationFile: GetBlobContent: %v", err)
-			} else {
-				ctx.Data["CitiationExist"] = true
-				ctx.PageData["citationFileContent"] = content
-				break
-			}
-		}
-	}
-}
-
-// Home render repository home page
-func Home(ctx *context.Context) {
-	if setting.Other.EnableFeed {
-		isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
-		if isFeed {
-			switch {
-			case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
-				feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
-			case ctx.Repo.TreePath == "":
-				feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
-			case ctx.Repo.TreePath != "":
-				feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
-			}
-			return
-		}
-	}
-
-	checkHomeCodeViewable(ctx)
-	if ctx.Written() {
-		return
-	}
-
-	renderHomeCode(ctx)
-}
-
 // LastCommit returns lastCommit data for the provided branch/tag/commit and directory (in url) and filenames in body
 func LastCommit(ctx *context.Context) {
 	checkHomeCodeViewable(ctx)
@@ -877,220 +313,6 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
 	return allEntries
 }
 
-func renderLanguageStats(ctx *context.Context) {
-	langs, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5)
-	if err != nil {
-		ctx.ServerError("Repo.GetTopLanguageStats", err)
-		return
-	}
-
-	ctx.Data["LanguageStats"] = langs
-}
-
-func renderRepoTopics(ctx *context.Context) {
-	topics, err := db.Find[repo_model.Topic](ctx, &repo_model.FindTopicOptions{
-		RepoID: ctx.Repo.Repository.ID,
-	})
-	if err != nil {
-		ctx.ServerError("models.FindTopics", err)
-		return
-	}
-	ctx.Data["Topics"] = topics
-}
-
-func prepareOpenWithEditorApps(ctx *context.Context) {
-	var tmplApps []map[string]any
-	apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
-	if len(apps) == 0 {
-		apps = setting.DefaultOpenWithEditorApps()
-	}
-	for _, app := range apps {
-		schema, _, _ := strings.Cut(app.OpenURL, ":")
-		var iconHTML template.HTML
-		if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
-			iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
-		} else {
-			iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
-		}
-		tmplApps = append(tmplApps, map[string]any{
-			"DisplayName": app.DisplayName,
-			"OpenURL":     app.OpenURL,
-			"IconHTML":    iconHTML,
-		})
-	}
-	ctx.Data["OpenWithEditorApps"] = tmplApps
-}
-
-func renderHomeCode(ctx *context.Context) {
-	ctx.Data["PageIsViewCode"] = true
-	ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
-	prepareOpenWithEditorApps(ctx)
-
-	if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
-		showEmpty := true
-		var err error
-		if ctx.Repo.GitRepo != nil {
-			showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
-			if err != nil {
-				log.Error("GitRepo.IsEmpty: %v", err)
-				ctx.Repo.Repository.Status = repo_model.RepositoryBroken
-				showEmpty = true
-				ctx.Flash.Error(ctx.Tr("error.occurred"), true)
-			}
-		}
-		if showEmpty {
-			ctx.HTML(http.StatusOK, tplRepoEMPTY)
-			return
-		}
-
-		// the repo is not really empty, so we should update the modal in database
-		// such problem may be caused by:
-		// 1) an error occurs during pushing/receiving.  2) the user replaces an empty git repo manually
-		// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
-		// it's possible for a repository to be non-empty by that flag but still 500
-		// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
-		ctx.Repo.Repository.IsEmpty = false
-		if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
-			ctx.ServerError("UpdateRepositoryCols", err)
-			return
-		}
-		if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
-			ctx.ServerError("UpdateRepoSize", err)
-			return
-		}
-
-		// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
-		link := ctx.Link
-		if ctx.Req.URL.RawQuery != "" {
-			link += "?" + ctx.Req.URL.RawQuery
-		}
-		ctx.Redirect(link)
-		return
-	}
-
-	title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
-	if len(ctx.Repo.Repository.Description) > 0 {
-		title += ": " + ctx.Repo.Repository.Description
-	}
-	ctx.Data["Title"] = title
-
-	// Get Topics of this repo
-	renderRepoTopics(ctx)
-	if ctx.Written() {
-		return
-	}
-
-	// Get current entry user currently looking at.
-	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
-	if err != nil {
-		HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
-		return
-	}
-
-	checkOutdatedBranch(ctx)
-
-	checkCitationFile(ctx, entry)
-	if ctx.Written() {
-		return
-	}
-
-	renderLanguageStats(ctx)
-	if ctx.Written() {
-		return
-	}
-
-	if entry.IsDir() {
-		renderDirectory(ctx)
-	} else {
-		renderFile(ctx, entry)
-	}
-	if ctx.Written() {
-		return
-	}
-
-	if ctx.Doer != nil {
-		if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
-			ctx.ServerError("GetBaseRepo", err)
-			return
-		}
-
-		opts := &git_model.FindRecentlyPushedNewBranchesOptions{
-			Repo:     ctx.Repo.Repository,
-			BaseRepo: ctx.Repo.Repository,
-		}
-		if ctx.Repo.Repository.IsFork {
-			opts.BaseRepo = ctx.Repo.Repository.BaseRepo
-		}
-
-		baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
-		if err != nil {
-			ctx.ServerError("GetUserRepoPermission", err)
-			return
-		}
-
-		if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
-			opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
-			baseRepoPerm.CanRead(unit_model.TypePullRequests) {
-			ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
-			if err != nil {
-				log.Error("FindRecentlyPushedNewBranches failed: %v", err)
-			}
-		}
-	}
-
-	var treeNames []string
-	paths := make([]string, 0, 5)
-	if len(ctx.Repo.TreePath) > 0 {
-		treeNames = strings.Split(ctx.Repo.TreePath, "/")
-		for i := range treeNames {
-			paths = append(paths, strings.Join(treeNames[:i+1], "/"))
-		}
-
-		ctx.Data["HasParentPath"] = true
-		if len(paths)-2 >= 0 {
-			ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
-		}
-	}
-
-	ctx.Data["Paths"] = paths
-
-	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
-	treeLink := branchLink
-	if len(ctx.Repo.TreePath) > 0 {
-		treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
-	}
-	ctx.Data["TreeLink"] = treeLink
-	ctx.Data["TreeNames"] = treeNames
-	ctx.Data["BranchLink"] = branchLink
-	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
-	ctx.HTML(http.StatusOK, tplRepoHome)
-}
-
-func checkOutdatedBranch(ctx *context.Context) {
-	if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
-		return
-	}
-
-	// get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
-	commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
-	if err != nil {
-		log.Error("GetBranchCommitID: %v", err)
-		// Don't return an error page, as it can be rechecked the next time the user opens the page.
-		return
-	}
-
-	dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
-	if err != nil {
-		log.Error("GetBranch: %v", err)
-		// Don't return an error page, as it can be rechecked the next time the user opens the page.
-		return
-	}
-
-	if dbBranch.CommitID != commit.ID.String() {
-		ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
-	}
-}
-
 // RenderUserCards render a page show users according the input template
 func RenderUserCards(ctx *context.Context, total int, getter func(opts db.ListOptions) ([]*user_model.User, error), tpl base.TplName) {
 	page := ctx.FormInt("page")
diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go
new file mode 100644
index 0000000000000..03f394d7d8262
--- /dev/null
+++ b/routers/web/repo/view_file.go
@@ -0,0 +1,313 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+	"bytes"
+	"fmt"
+	"image"
+	"io"
+	"path"
+	"slices"
+	"strings"
+
+	git_model "code.gitea.io/gitea/models/git"
+	issue_model "code.gitea.io/gitea/models/issues"
+	"code.gitea.io/gitea/models/renderhelper"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/actions"
+	"code.gitea.io/gitea/modules/charset"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/highlight"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/services/context"
+	issue_service "code.gitea.io/gitea/services/issue"
+	files_service "code.gitea.io/gitea/services/repository/files"
+
+	"github.com/nektos/act/pkg/model"
+)
+
+func renderFile(ctx *context.Context, entry *git.TreeEntry) {
+	ctx.Data["IsViewFile"] = true
+	ctx.Data["HideRepoInfo"] = true
+	blob := entry.Blob()
+	buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob)
+	if err != nil {
+		ctx.ServerError("getFileReader", err)
+		return
+	}
+	defer dataRc.Close()
+
+	ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
+	ctx.Data["FileIsSymlink"] = entry.IsLink()
+	ctx.Data["FileName"] = blob.Name()
+	ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+
+	commit, err := ctx.Repo.Commit.GetCommitByPath(ctx.Repo.TreePath)
+	if err != nil {
+		ctx.ServerError("GetCommitByPath", err)
+		return
+	}
+
+	if !loadLatestCommitData(ctx, commit) {
+		return
+	}
+
+	if ctx.Repo.TreePath == ".editorconfig" {
+		_, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx.Repo.Commit)
+		if editorconfigWarning != nil {
+			ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error())
+		}
+		if editorconfigErr != nil {
+			ctx.Data["FileError"] = strings.TrimSpace(editorconfigErr.Error())
+		}
+	} else if issue_service.IsTemplateConfig(ctx.Repo.TreePath) {
+		_, issueConfigErr := issue_service.GetTemplateConfig(ctx.Repo.GitRepo, ctx.Repo.TreePath, ctx.Repo.Commit)
+		if issueConfigErr != nil {
+			ctx.Data["FileError"] = strings.TrimSpace(issueConfigErr.Error())
+		}
+	} else if actions.IsWorkflow(ctx.Repo.TreePath) {
+		content, err := actions.GetContentFromEntry(entry)
+		if err != nil {
+			log.Error("actions.GetContentFromEntry: %v", err)
+		}
+		_, workFlowErr := model.ReadWorkflow(bytes.NewReader(content))
+		if workFlowErr != nil {
+			ctx.Data["FileError"] = ctx.Locale.Tr("actions.runs.invalid_workflow_helper", workFlowErr.Error())
+		}
+	} else if slices.Contains([]string{"CODEOWNERS", "docs/CODEOWNERS", ".gitea/CODEOWNERS"}, ctx.Repo.TreePath) {
+		if data, err := blob.GetBlobContent(setting.UI.MaxDisplayFileSize); err == nil {
+			_, warnings := issue_model.GetCodeOwnersFromContent(ctx, data)
+			if len(warnings) > 0 {
+				ctx.Data["FileWarning"] = strings.Join(warnings, "\n")
+			}
+		}
+	}
+
+	isDisplayingSource := ctx.FormString("display") == "source"
+	isDisplayingRendered := !isDisplayingSource
+
+	if fInfo.isLFSFile {
+		ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+	}
+
+	isRepresentableAsText := fInfo.st.IsRepresentableAsText()
+	if !isRepresentableAsText {
+		// If we can't show plain text, always try to render.
+		isDisplayingSource = false
+		isDisplayingRendered = true
+	}
+	ctx.Data["IsLFSFile"] = fInfo.isLFSFile
+	ctx.Data["FileSize"] = fInfo.fileSize
+	ctx.Data["IsTextFile"] = fInfo.isTextFile
+	ctx.Data["IsRepresentableAsText"] = isRepresentableAsText
+	ctx.Data["IsDisplayingSource"] = isDisplayingSource
+	ctx.Data["IsDisplayingRendered"] = isDisplayingRendered
+	ctx.Data["IsExecutable"] = entry.IsExecutable()
+
+	isTextSource := fInfo.isTextFile || isDisplayingSource
+	ctx.Data["IsTextSource"] = isTextSource
+	if isTextSource {
+		ctx.Data["CanCopyContent"] = true
+	}
+
+	// Check LFS Lock
+	lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath)
+	ctx.Data["LFSLock"] = lfsLock
+	if err != nil {
+		ctx.ServerError("GetTreePathLock", err)
+		return
+	}
+	if lfsLock != nil {
+		u, err := user_model.GetUserByID(ctx, lfsLock.OwnerID)
+		if err != nil {
+			ctx.ServerError("GetTreePathLock", err)
+			return
+		}
+		ctx.Data["LFSLockOwner"] = u.Name
+		ctx.Data["LFSLockOwnerHomeLink"] = u.HomeLink()
+		ctx.Data["LFSLockHint"] = ctx.Tr("repo.editor.this_file_locked")
+	}
+
+	// Assume file is not editable first.
+	if fInfo.isLFSFile {
+		ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files")
+	} else if !isRepresentableAsText {
+		ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files")
+	}
+
+	switch {
+	case isRepresentableAsText:
+		if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
+			ctx.Data["IsFileTooLarge"] = true
+			break
+		}
+
+		if fInfo.st.IsSvgImage() {
+			ctx.Data["IsImageFile"] = true
+			ctx.Data["CanCopyContent"] = true
+			ctx.Data["HasSourceRenderedToggle"] = true
+		}
+
+		rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
+
+		shouldRenderSource := ctx.FormString("display") == "source"
+		readmeExist := util.IsReadmeFileName(blob.Name())
+		ctx.Data["ReadmeExist"] = readmeExist
+
+		markupType := markup.DetectMarkupTypeByFileName(blob.Name())
+		if markupType == "" {
+			markupType = markup.DetectRendererType(blob.Name(), bytes.NewReader(buf))
+		}
+		if markupType != "" {
+			ctx.Data["HasSourceRenderedToggle"] = true
+		}
+		if markupType != "" && !shouldRenderSource {
+			ctx.Data["IsMarkup"] = true
+			ctx.Data["MarkupType"] = markupType
+			metas := ctx.Repo.Repository.ComposeDocumentMetas(ctx)
+			metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL()
+			rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
+				CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
+				CurrentTreePath: path.Dir(ctx.Repo.TreePath),
+			}).
+				WithMarkupType(markupType).
+				WithRelativePath(ctx.Repo.TreePath).
+				WithMetas(metas)
+
+			ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
+			if err != nil {
+				ctx.ServerError("Render", err)
+				return
+			}
+			// to prevent iframe load third-party url
+			ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'")
+		} else {
+			buf, _ := io.ReadAll(rd)
+
+			// The Open Group Base Specification: https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html
+			//   empty: 0 lines; "a": 1 incomplete-line; "a\n": 1 line; "a\nb": 1 line, 1 incomplete-line;
+			// Gitea uses the definition (like most modern editors):
+			//   empty: 0 lines; "a": 1 line; "a\n": 2 lines; "a\nb": 2 lines;
+			//   When rendering, the last empty line is not rendered in UI, while the line-number is still counted, to tell users that the file contains a trailing EOL.
+			//   To make the UI more consistent, it could use an icon mark to indicate that there is no trailing EOL, and show line-number as the rendered lines.
+			// This NumLines is only used for the display on the UI: "xxx lines"
+			if len(buf) == 0 {
+				ctx.Data["NumLines"] = 0
+			} else {
+				ctx.Data["NumLines"] = bytes.Count(buf, []byte{'\n'}) + 1
+			}
+
+			language, err := files_service.TryGetContentLanguage(ctx.Repo.GitRepo, ctx.Repo.CommitID, ctx.Repo.TreePath)
+			if err != nil {
+				log.Error("Unable to get file language for %-v:%s. Error: %v", ctx.Repo.Repository, ctx.Repo.TreePath, err)
+			}
+
+			fileContent, lexerName, err := highlight.File(blob.Name(), language, buf)
+			ctx.Data["LexerName"] = lexerName
+			if err != nil {
+				log.Error("highlight.File failed, fallback to plain text: %v", err)
+				fileContent = highlight.PlainText(buf)
+			}
+			status := &charset.EscapeStatus{}
+			statuses := make([]*charset.EscapeStatus, len(fileContent))
+			for i, line := range fileContent {
+				statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale)
+				status = status.Or(statuses[i])
+			}
+			ctx.Data["EscapeStatus"] = status
+			ctx.Data["FileContent"] = fileContent
+			ctx.Data["LineEscapeStatus"] = statuses
+		}
+		if !fInfo.isLFSFile {
+			if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
+				if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
+					ctx.Data["CanEditFile"] = false
+					ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
+				} else {
+					ctx.Data["CanEditFile"] = true
+					ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file")
+				}
+			} else if !ctx.Repo.IsViewBranch {
+				ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
+			} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
+				ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit")
+			}
+		}
+
+	case fInfo.st.IsPDF():
+		ctx.Data["IsPDFFile"] = true
+	case fInfo.st.IsVideo():
+		ctx.Data["IsVideoFile"] = true
+	case fInfo.st.IsAudio():
+		ctx.Data["IsAudioFile"] = true
+	case fInfo.st.IsImage() && (setting.UI.SVG.Enabled || !fInfo.st.IsSvgImage()):
+		ctx.Data["IsImageFile"] = true
+		ctx.Data["CanCopyContent"] = true
+	default:
+		if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
+			ctx.Data["IsFileTooLarge"] = true
+			break
+		}
+
+		// TODO: this logic duplicates with "isRepresentableAsText=true", it is not the same as "LFSFileGet" in "lfs.go"
+		// It is used by "external renders", markupRender will execute external programs to get rendered content.
+		if markupType := markup.DetectMarkupTypeByFileName(blob.Name()); markupType != "" {
+			rd := io.MultiReader(bytes.NewReader(buf), dataRc)
+			ctx.Data["IsMarkup"] = true
+			ctx.Data["MarkupType"] = markupType
+
+			rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
+				CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
+				CurrentTreePath: path.Dir(ctx.Repo.TreePath),
+			}).
+				WithMarkupType(markupType).
+				WithRelativePath(ctx.Repo.TreePath)
+
+			ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
+			if err != nil {
+				ctx.ServerError("Render", err)
+				return
+			}
+		}
+	}
+
+	if ctx.Repo.GitRepo != nil {
+		checker, deferable := ctx.Repo.GitRepo.CheckAttributeReader(ctx.Repo.CommitID)
+		if checker != nil {
+			defer deferable()
+			attrs, err := checker.CheckPath(ctx.Repo.TreePath)
+			if err == nil {
+				ctx.Data["IsVendored"] = git.AttributeToBool(attrs, git.AttributeLinguistVendored).Value()
+				ctx.Data["IsGenerated"] = git.AttributeToBool(attrs, git.AttributeLinguistGenerated).Value()
+			}
+		}
+	}
+
+	if fInfo.st.IsImage() && !fInfo.st.IsSvgImage() {
+		img, _, err := image.DecodeConfig(bytes.NewReader(buf))
+		if err == nil {
+			// There are Image formats go can't decode
+			// Instead of throwing an error in that case, we show the size only when we can decode
+			ctx.Data["ImageSize"] = fmt.Sprintf("%dx%dpx", img.Width, img.Height)
+		}
+	}
+
+	if ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
+		if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID {
+			ctx.Data["CanDeleteFile"] = false
+			ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked")
+		} else {
+			ctx.Data["CanDeleteFile"] = true
+			ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file")
+		}
+	} else if !ctx.Repo.IsViewBranch {
+		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch")
+	} else if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) {
+		ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access")
+	}
+}
diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go
new file mode 100644
index 0000000000000..d1a50800c11e0
--- /dev/null
+++ b/routers/web/repo/view_home.go
@@ -0,0 +1,351 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+	"fmt"
+	"html/template"
+	"net/http"
+	"path"
+	"strings"
+	"time"
+
+	"code.gitea.io/gitea/models/db"
+	git_model "code.gitea.io/gitea/models/git"
+	access_model "code.gitea.io/gitea/models/perm/access"
+	repo_model "code.gitea.io/gitea/models/repo"
+	unit_model "code.gitea.io/gitea/models/unit"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+	repo_module "code.gitea.io/gitea/modules/repository"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/svg"
+	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/routers/web/feed"
+	"code.gitea.io/gitea/services/context"
+	repo_service "code.gitea.io/gitea/services/repository"
+)
+
+func checkOutdatedBranch(ctx *context.Context) {
+	if !(ctx.Repo.IsAdmin() || ctx.Repo.IsOwner()) {
+		return
+	}
+
+	// get the head commit of the branch since ctx.Repo.CommitID is not always the head commit of `ctx.Repo.BranchName`
+	commit, err := ctx.Repo.GitRepo.GetBranchCommit(ctx.Repo.BranchName)
+	if err != nil {
+		log.Error("GetBranchCommitID: %v", err)
+		// Don't return an error page, as it can be rechecked the next time the user opens the page.
+		return
+	}
+
+	dbBranch, err := git_model.GetBranch(ctx, ctx.Repo.Repository.ID, ctx.Repo.BranchName)
+	if err != nil {
+		log.Error("GetBranch: %v", err)
+		// Don't return an error page, as it can be rechecked the next time the user opens the page.
+		return
+	}
+
+	if dbBranch.CommitID != commit.ID.String() {
+		ctx.Flash.Warning(ctx.Tr("repo.error.broken_git_hook", "https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running"), true)
+	}
+}
+
+func prepareHomeSidebarRepoTopics(ctx *context.Context) {
+	topics, err := db.Find[repo_model.Topic](ctx, &repo_model.FindTopicOptions{
+		RepoID: ctx.Repo.Repository.ID,
+	})
+	if err != nil {
+		ctx.ServerError("models.FindTopics", err)
+		return
+	}
+	ctx.Data["Topics"] = topics
+}
+
+func prepareOpenWithEditorApps(ctx *context.Context) {
+	var tmplApps []map[string]any
+	apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
+	if len(apps) == 0 {
+		apps = setting.DefaultOpenWithEditorApps()
+	}
+	for _, app := range apps {
+		schema, _, _ := strings.Cut(app.OpenURL, ":")
+		var iconHTML template.HTML
+		if schema == "vscode" || schema == "vscodium" || schema == "jetbrains" {
+			iconHTML = svg.RenderHTML(fmt.Sprintf("gitea-%s", schema), 16, "tw-mr-2")
+		} else {
+			iconHTML = svg.RenderHTML("gitea-git", 16, "tw-mr-2") // TODO: it could support user's customized icon in the future
+		}
+		tmplApps = append(tmplApps, map[string]any{
+			"DisplayName": app.DisplayName,
+			"OpenURL":     app.OpenURL,
+			"IconHTML":    iconHTML,
+		})
+	}
+	ctx.Data["OpenWithEditorApps"] = tmplApps
+}
+
+func prepareHomeSidebarCitationFile(ctx *context.Context, entry *git.TreeEntry) {
+	if entry.Name() != "" {
+		return
+	}
+	tree, err := ctx.Repo.Commit.SubTree(ctx.Repo.TreePath)
+	if err != nil {
+		HandleGitError(ctx, "Repo.Commit.SubTree", err)
+		return
+	}
+	allEntries, err := tree.ListEntries()
+	if err != nil {
+		ctx.ServerError("ListEntries", err)
+		return
+	}
+	for _, entry := range allEntries {
+		if entry.Name() == "CITATION.cff" || entry.Name() == "CITATION.bib" {
+			// Read Citation file contents
+			if content, err := entry.Blob().GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
+				log.Error("checkCitationFile: GetBlobContent: %v", err)
+			} else {
+				ctx.Data["CitiationExist"] = true
+				ctx.PageData["citationFileContent"] = content
+				break
+			}
+		}
+	}
+}
+
+func prepareHomeSidebarLicenses(ctx *context.Context) {
+	repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
+	if err != nil {
+		ctx.ServerError("GetRepoLicenses", err)
+		return
+	}
+	ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
+	ctx.Data["LicenseFileName"] = repo_service.LicenseFileName
+}
+
+func prepareToRenderDirectory(ctx *context.Context) {
+	entries := renderDirectoryFiles(ctx, 1*time.Second)
+	if ctx.Written() {
+		return
+	}
+
+	if ctx.Repo.TreePath != "" {
+		ctx.Data["HideRepoInfo"] = true
+		ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+path.Base(ctx.Repo.TreePath), ctx.Repo.RefName)
+	}
+
+	subfolder, readmeFile, err := findReadmeFileInEntries(ctx, entries, true)
+	if err != nil {
+		ctx.ServerError("findReadmeFileInEntries", err)
+		return
+	}
+
+	prepareToRenderReadmeFile(ctx, subfolder, readmeFile)
+}
+
+func prepareHomeSidebarLanguageStats(ctx *context.Context) {
+	langs, err := repo_model.GetTopLanguageStats(ctx, ctx.Repo.Repository, 5)
+	if err != nil {
+		ctx.ServerError("Repo.GetTopLanguageStats", err)
+		return
+	}
+
+	ctx.Data["LanguageStats"] = langs
+}
+
+func prepareHomeSidebarLatestRelease(ctx *context.Context) {
+	if !ctx.Repo.Repository.UnitEnabled(ctx, unit_model.TypeReleases) {
+		return
+	}
+
+	release, err := repo_model.GetLatestReleaseByRepoID(ctx, ctx.Repo.Repository.ID)
+	if err != nil && !repo_model.IsErrReleaseNotExist(err) {
+		ctx.ServerError("GetLatestReleaseByRepoID", err)
+		return
+	}
+
+	if release != nil {
+		if err = release.LoadAttributes(ctx); err != nil {
+			ctx.ServerError("release.LoadAttributes", err)
+			return
+		}
+		ctx.Data["LatestRelease"] = release
+	}
+}
+
+func renderHomeCode(ctx *context.Context) {
+	ctx.Data["PageIsViewCode"] = true
+	ctx.Data["RepositoryUploadEnabled"] = setting.Repository.Upload.Enabled
+	prepareOpenWithEditorApps(ctx)
+
+	if ctx.Repo.Commit == nil || ctx.Repo.Repository.IsEmpty || ctx.Repo.Repository.IsBroken() {
+		showEmpty := true
+		var err error
+		if ctx.Repo.GitRepo != nil {
+			showEmpty, err = ctx.Repo.GitRepo.IsEmpty()
+			if err != nil {
+				log.Error("GitRepo.IsEmpty: %v", err)
+				ctx.Repo.Repository.Status = repo_model.RepositoryBroken
+				showEmpty = true
+				ctx.Flash.Error(ctx.Tr("error.occurred"), true)
+			}
+		}
+		if showEmpty {
+			ctx.HTML(http.StatusOK, tplRepoEMPTY)
+			return
+		}
+
+		// the repo is not really empty, so we should update the modal in database
+		// such problem may be caused by:
+		// 1) an error occurs during pushing/receiving.  2) the user replaces an empty git repo manually
+		// and even more: the IsEmpty flag is deeply broken and should be removed with the UI changed to manage to cope with empty repos.
+		// it's possible for a repository to be non-empty by that flag but still 500
+		// because there are no branches - only tags -or the default branch is non-extant as it has been 0-pushed.
+		ctx.Repo.Repository.IsEmpty = false
+		if err = repo_model.UpdateRepositoryCols(ctx, ctx.Repo.Repository, "is_empty"); err != nil {
+			ctx.ServerError("UpdateRepositoryCols", err)
+			return
+		}
+		if err = repo_module.UpdateRepoSize(ctx, ctx.Repo.Repository); err != nil {
+			ctx.ServerError("UpdateRepoSize", err)
+			return
+		}
+
+		// the repo's IsEmpty has been updated, redirect to this page to make sure middlewares can get the correct values
+		link := ctx.Link
+		if ctx.Req.URL.RawQuery != "" {
+			link += "?" + ctx.Req.URL.RawQuery
+		}
+		ctx.Redirect(link)
+		return
+	}
+
+	title := ctx.Repo.Repository.Owner.Name + "/" + ctx.Repo.Repository.Name
+	if len(ctx.Repo.Repository.Description) > 0 {
+		title += ": " + ctx.Repo.Repository.Description
+	}
+	ctx.Data["Title"] = title
+
+	// Get Topics of this repo
+	prepareHomeSidebarRepoTopics(ctx)
+	if ctx.Written() {
+		return
+	}
+
+	// Get current entry user currently looking at.
+	entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
+	if err != nil {
+		HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err)
+		return
+	}
+
+	checkOutdatedBranch(ctx)
+
+	if entry.IsDir() {
+		prepareToRenderDirectory(ctx)
+	} else {
+		renderFile(ctx, entry)
+	}
+	if ctx.Written() {
+		return
+	}
+
+	if ctx.Doer != nil {
+		if err := ctx.Repo.Repository.GetBaseRepo(ctx); err != nil {
+			ctx.ServerError("GetBaseRepo", err)
+			return
+		}
+
+		opts := &git_model.FindRecentlyPushedNewBranchesOptions{
+			Repo:     ctx.Repo.Repository,
+			BaseRepo: ctx.Repo.Repository,
+		}
+		if ctx.Repo.Repository.IsFork {
+			opts.BaseRepo = ctx.Repo.Repository.BaseRepo
+		}
+
+		baseRepoPerm, err := access_model.GetUserRepoPermission(ctx, opts.BaseRepo, ctx.Doer)
+		if err != nil {
+			ctx.ServerError("GetUserRepoPermission", err)
+			return
+		}
+
+		if !opts.Repo.IsMirror && !opts.BaseRepo.IsMirror &&
+			opts.BaseRepo.UnitEnabled(ctx, unit_model.TypePullRequests) &&
+			baseRepoPerm.CanRead(unit_model.TypePullRequests) {
+			ctx.Data["RecentlyPushedNewBranches"], err = git_model.FindRecentlyPushedNewBranches(ctx, ctx.Doer, opts)
+			if err != nil {
+				log.Error("FindRecentlyPushedNewBranches failed: %v", err)
+			}
+		}
+	}
+
+	var treeNames, paths []string
+	branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
+	treeLink := branchLink
+	if ctx.Repo.TreePath != "" {
+		treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
+		treeNames = strings.Split(ctx.Repo.TreePath, "/")
+		for i := range treeNames {
+			paths = append(paths, strings.Join(treeNames[:i+1], "/"))
+		}
+		ctx.Data["HasParentPath"] = true
+		if len(paths)-2 >= 0 {
+			ctx.Data["ParentPath"] = "/" + paths[len(paths)-2]
+		}
+	}
+
+	isTreePathRoot := ctx.Repo.TreePath == ""
+	if isTreePathRoot {
+		prepareHomeSidebarLicenses(ctx)
+		if ctx.Written() {
+			return
+		}
+		prepareHomeSidebarCitationFile(ctx, entry)
+		if ctx.Written() {
+			return
+		}
+
+		prepareHomeSidebarLanguageStats(ctx)
+		if ctx.Written() {
+			return
+		}
+
+		prepareHomeSidebarLatestRelease(ctx)
+		if ctx.Written() {
+			return
+		}
+	}
+
+	ctx.Data["Paths"] = paths
+	ctx.Data["TreeLink"] = treeLink
+	ctx.Data["TreeNames"] = treeNames
+	ctx.Data["BranchLink"] = branchLink
+	ctx.HTML(http.StatusOK, tplRepoHome)
+}
+
+// Home render repository home page
+func Home(ctx *context.Context) {
+	if setting.Other.EnableFeed {
+		isFeed, _, showFeedType := feed.GetFeedType(ctx.PathParam(":reponame"), ctx.Req)
+		if isFeed {
+			switch {
+			case ctx.Link == fmt.Sprintf("%s.%s", ctx.Repo.RepoLink, showFeedType):
+				feed.ShowRepoFeed(ctx, ctx.Repo.Repository, showFeedType)
+			case ctx.Repo.TreePath == "":
+				feed.ShowBranchFeed(ctx, ctx.Repo.Repository, showFeedType)
+			case ctx.Repo.TreePath != "":
+				feed.ShowFileFeed(ctx, ctx.Repo.Repository, showFeedType)
+			}
+			return
+		}
+	}
+
+	checkHomeCodeViewable(ctx)
+	if ctx.Written() {
+		return
+	}
+
+	renderHomeCode(ctx)
+}
diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go
new file mode 100644
index 0000000000000..5bd39de96315d
--- /dev/null
+++ b/routers/web/repo/view_readme.go
@@ -0,0 +1,218 @@
+// Copyright 2024 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package repo
+
+import (
+	"bytes"
+	"encoding/base64"
+	"fmt"
+	"html/template"
+	"io"
+	"net/url"
+	"path"
+	"strings"
+
+	"code.gitea.io/gitea/models/renderhelper"
+	"code.gitea.io/gitea/modules/base"
+	"code.gitea.io/gitea/modules/charset"
+	"code.gitea.io/gitea/modules/git"
+	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/markup"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/services/context"
+)
+
+// locate a README for a tree in one of the supported paths.
+//
+// entries is passed to reduce calls to ListEntries(), so
+// this has precondition:
+//
+//	entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries()
+//
+// FIXME: There has to be a more efficient way of doing this
+func findReadmeFileInEntries(ctx *context.Context, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) {
+	// Create a list of extensions in priority order
+	// 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md
+	// 2. Txt files - e.g. README.txt
+	// 3. No extension - e.g. README
+	exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority
+	extCount := len(exts)
+	readmeFiles := make([]*git.TreeEntry, extCount+1)
+
+	docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/)
+	for _, entry := range entries {
+		if tryWellKnownDirs && entry.IsDir() {
+			// as a special case for the top-level repo introduction README,
+			// fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ...
+			// (note that docsEntries is ignored unless we are at the root)
+			lowerName := strings.ToLower(entry.Name())
+			switch lowerName {
+			case "docs":
+				if entry.Name() == "docs" || docsEntries[0] == nil {
+					docsEntries[0] = entry
+				}
+			case ".gitea":
+				if entry.Name() == ".gitea" || docsEntries[1] == nil {
+					docsEntries[1] = entry
+				}
+			case ".github":
+				if entry.Name() == ".github" || docsEntries[2] == nil {
+					docsEntries[2] = entry
+				}
+			}
+			continue
+		}
+		if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok {
+			log.Debug("Potential readme file: %s", entry.Name())
+			if readmeFiles[i] == nil || base.NaturalSortLess(readmeFiles[i].Name(), entry.Blob().Name()) {
+				if entry.IsLink() {
+					target, err := entry.FollowLinks()
+					if err != nil && !git.IsErrBadLink(err) {
+						return "", nil, err
+					} else if target != nil && (target.IsExecutable() || target.IsRegular()) {
+						readmeFiles[i] = entry
+					}
+				} else {
+					readmeFiles[i] = entry
+				}
+			}
+		}
+	}
+	var readmeFile *git.TreeEntry
+	for _, f := range readmeFiles {
+		if f != nil {
+			readmeFile = f
+			break
+		}
+	}
+
+	if ctx.Repo.TreePath == "" && readmeFile == nil {
+		for _, subTreeEntry := range docsEntries {
+			if subTreeEntry == nil {
+				continue
+			}
+			subTree := subTreeEntry.Tree()
+			if subTree == nil {
+				// this should be impossible; if subTreeEntry exists so should this.
+				continue
+			}
+			childEntries, err := subTree.ListEntries()
+			if err != nil {
+				return "", nil, err
+			}
+
+			subfolder, readmeFile, err := findReadmeFileInEntries(ctx, childEntries, false)
+			if err != nil && !git.IsErrNotExist(err) {
+				return "", nil, err
+			}
+			if readmeFile != nil {
+				return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil
+			}
+		}
+	}
+
+	return "", readmeFile, nil
+}
+
+// localizedExtensions prepends the provided language code with and without a
+// regional identifier to the provided extension.
+// Note: the language code will always be lower-cased, if a region is present it must be separated with a `-`
+// Note: ext should be prefixed with a `.`
+func localizedExtensions(ext, languageCode string) (localizedExts []string) {
+	if len(languageCode) < 1 {
+		return []string{ext}
+	}
+
+	lowerLangCode := "." + strings.ToLower(languageCode)
+
+	if strings.Contains(lowerLangCode, "-") {
+		underscoreLangCode := strings.ReplaceAll(lowerLangCode, "-", "_")
+		indexOfDash := strings.Index(lowerLangCode, "-")
+		// e.g. [.zh-cn.md, .zh_cn.md, .zh.md, _zh.md, .md]
+		return []string{lowerLangCode + ext, underscoreLangCode + ext, lowerLangCode[:indexOfDash] + ext, "_" + lowerLangCode[1:indexOfDash] + ext, ext}
+	}
+
+	// e.g. [.en.md, .md]
+	return []string{lowerLangCode + ext, ext}
+}
+
+func prepareToRenderReadmeFile(ctx *context.Context, subfolder string, readmeFile *git.TreeEntry) {
+	target := readmeFile
+	if readmeFile != nil && readmeFile.IsLink() {
+		target, _ = readmeFile.FollowLinks()
+	}
+	if target == nil {
+		// if findReadmeFile() failed and/or gave us a broken symlink (which it shouldn't)
+		// simply skip rendering the README
+		return
+	}
+
+	ctx.Data["RawFileLink"] = ""
+	ctx.Data["ReadmeInList"] = true
+	ctx.Data["ReadmeExist"] = true
+	ctx.Data["FileIsSymlink"] = readmeFile.IsLink()
+
+	buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, target.Blob())
+	if err != nil {
+		ctx.ServerError("getFileReader", err)
+		return
+	}
+	defer dataRc.Close()
+
+	ctx.Data["FileIsText"] = fInfo.isTextFile
+	ctx.Data["FileName"] = path.Join(subfolder, readmeFile.Name())
+	ctx.Data["FileSize"] = fInfo.fileSize
+	ctx.Data["IsLFSFile"] = fInfo.isLFSFile
+
+	if fInfo.isLFSFile {
+		filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.Name()))
+		ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.Link(), url.PathEscape(fInfo.lfsMeta.Oid), url.PathEscape(filenameBase64))
+	}
+
+	if !fInfo.isTextFile {
+		return
+	}
+
+	if fInfo.fileSize >= setting.UI.MaxDisplayFileSize {
+		// Pretend that this is a normal text file to display 'This file is too large to be shown'
+		ctx.Data["IsFileTooLarge"] = true
+		ctx.Data["IsTextFile"] = true
+		return
+	}
+
+	rd := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{})
+
+	if markupType := markup.DetectMarkupTypeByFileName(readmeFile.Name()); markupType != "" {
+		ctx.Data["IsMarkup"] = true
+		ctx.Data["MarkupType"] = markupType
+
+		rctx := renderhelper.NewRenderContextRepoFile(ctx, ctx.Repo.Repository, renderhelper.RepoFileOptions{
+			CurrentRefPath:  ctx.Repo.BranchNameSubURL(),
+			CurrentTreePath: path.Join(ctx.Repo.TreePath, subfolder),
+		}).
+			WithMarkupType(markupType).
+			WithRelativePath(path.Join(ctx.Repo.TreePath, subfolder, readmeFile.Name())) // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path).
+
+		ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, rctx, rd)
+		if err != nil {
+			log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.Name(), ctx.Repo.Repository, err)
+			delete(ctx.Data, "IsMarkup")
+		}
+	}
+
+	if ctx.Data["IsMarkup"] != true {
+		ctx.Data["IsPlainText"] = true
+		content, err := io.ReadAll(rd)
+		if err != nil {
+			log.Error("Read readme content failed: %v", err)
+		}
+		contentEscaped := template.HTMLEscapeString(util.UnsafeBytesToString(content))
+		ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlHTML(template.HTML(contentEscaped), ctx.Locale)
+	}
+
+	if !fInfo.isLFSFile && ctx.Repo.CanEnableEditor(ctx, ctx.Doer) {
+		ctx.Data["CanEditReadmeFile"] = true
+	}
+}
diff --git a/services/context/repo.go b/services/context/repo.go
index 1eafb7ca48bb4..cf328ca97b77c 100644
--- a/services/context/repo.go
+++ b/services/context/repo.go
@@ -396,13 +396,6 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
 	ctx.Repo.Repository = repo
 	ctx.Data["RepoName"] = ctx.Repo.Repository.Name
 	ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty
-
-	repoLicenses, err := repo_model.GetRepoLicenses(ctx, ctx.Repo.Repository)
-	if err != nil {
-		ctx.ServerError("GetRepoLicenses", err)
-		return
-	}
-	ctx.Data["DetectedRepoLicenses"] = repoLicenses.StringList()
 }
 
 // RepoAssignment returns a middleware to handle repository assignment
@@ -1036,7 +1029,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
 		ctx.Data["IsViewBranch"] = ctx.Repo.IsViewBranch
 		ctx.Data["IsViewTag"] = ctx.Repo.IsViewTag
 		ctx.Data["IsViewCommit"] = ctx.Repo.IsViewCommit
-		ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch()
+		ctx.Data["CanCreateBranch"] = ctx.Repo.CanCreateBranch() // only used by the branch selector dropdown: AllowCreateNewRef
 
 		ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
 		if err != nil {
diff --git a/templates/repo/home.tmpl b/templates/repo/home.tmpl
index 4b25915c278ab..63bf3eef0f5c3 100644
--- a/templates/repo/home.tmpl
+++ b/templates/repo/home.tmpl
@@ -3,35 +3,7 @@
 	{{template "repo/header" .}}
 	<div class="ui container {{if .IsBlame}}fluid padded{{end}}">
 		{{template "base/alert" .}}
-		{{template "repo/code/recently_pushed_new_branches" .}}
-		{{if and (not .HideRepoInfo) (not .IsBlame)}}
-		<div class="repo-description tw-break-anywhere">
-			{{- $description := .Repository.DescriptionHTML ctx -}}
-			{{if $description}}{{$description | RenderCodeBlock}}{{end}}
-			{{if .Repository.Website}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}
-		</div>
-		<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
-			{{/* it should match the code in issue-home.js */}}
-			{{range .Topics}}<a class="repo-topic ui large label" href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
-			{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>{{end}}
-		</div>
-		{{end}}
-		{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
-		<div class="ui form tw-hidden tw-flex tw-gap-2 tw-my-2" id="topic_edit">
-			<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
-				<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
-				{{range .Topics}}
-					{{/* keep the same layout as Fomantic UI generated labels */}}
-					<a class="ui label transition visible tw-cursor-default tw-inline-block" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
-				{{end}}
-				<div class="text"></div>
-			</div>
-			<div>
-				<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
-				<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
-			</div>
-		</div>
-		{{end}}
+
 		{{if .Repository.IsArchived}}
 			<div class="ui warning message tw-text-center">
 				{{if .Repository.ArchivedUnix.IsZero}}
@@ -41,134 +13,138 @@
 				{{end}}
 			</div>
 		{{end}}
-		{{template "repo/sub_menu" .}}
-		{{$n := len .TreeNames}}
-		{{$l := Eval $n "-" 1}}
-		{{$isHomepage := (eq $n 0)}}
-		<div class="repo-button-row" data-is-homepage="{{$isHomepage}}">
-			<div class="repo-button-row-left">
-				{{$branchDropdownCurrentRefType := "branch"}}
-				{{$branchDropdownCurrentRefShortName := .BranchName}}
-				{{if .IsViewTag}}
-					{{$branchDropdownCurrentRefType = "tag"}}
-					{{$branchDropdownCurrentRefShortName = .TagName}}
-				{{end}}
-				{{template "repo/branch_dropdown" dict
-					"Repository" .Repository
-					"ShowTabBranches" true
-					"ShowTabTags" true
-					"CurrentRefType" $branchDropdownCurrentRefType
-					"CurrentRefShortName" $branchDropdownCurrentRefShortName
-					"CurrentTreePath" .TreePath
-					"RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
-					"AllowCreateNewRef" .CanCreateBranch
-					"ShowViewAllRefsEntry" true
-				}}
-				{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
-					{{$cmpBranch := ""}}
-					{{if ne .Repository.ID .BaseRepo.ID}}
-						{{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
-					{{end}}
-					{{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
-					{{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
-					<a id="new-pull-request" role="button" class="ui compact basic button" href="{{$compareLink}}"
-						data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}">
-						{{svg "octicon-git-pull-request"}}
-					</a>
-				{{end}}
-				<!-- Show go to file and breadcrumbs if not on home page -->
-				{{if $isHomepage}}
-					<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
-				{{end}}
 
-				{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
-					<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
-						{{ctx.Locale.Tr "repo.editor.add_file"}}
-						{{svg "octicon-triangle-down" 14 "dropdown icon"}}
-						<div class="menu">
-							<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
-								{{ctx.Locale.Tr "repo.editor.new_file"}}
-							</a>
-							{{if .RepositoryUploadEnabled}}
-							<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
-								{{ctx.Locale.Tr "repo.editor.upload_file"}}
-							</a>
+		{{template "repo/code/recently_pushed_new_branches" .}}
+
+		{{$treeNamesLen := len .TreeNames}}
+		{{$isTreePathRoot := eq $treeNamesLen 0}}
+		{{$showSidebar := $isTreePathRoot}}
+		<div class="{{Iif $showSidebar "repo-grid-filelist-sidebar" "repo-grid-filelist-only"}}">
+			<div class="repo-home-filelist">
+				{{template "repo/sub_menu" .}}
+				<div class="repo-button-row">
+					<div class="repo-button-row-left">
+						{{$branchDropdownCurrentRefType := "branch"}}
+						{{$branchDropdownCurrentRefShortName := .BranchName}}
+						{{if .IsViewTag}}
+							{{$branchDropdownCurrentRefType = "tag"}}
+							{{$branchDropdownCurrentRefShortName = .TagName}}
+						{{end}}
+						{{template "repo/branch_dropdown" dict
+							"Repository" .Repository
+							"ShowTabBranches" true
+							"ShowTabTags" true
+							"CurrentRefType" $branchDropdownCurrentRefType
+							"CurrentRefShortName" $branchDropdownCurrentRefShortName
+							"CurrentTreePath" .TreePath
+							"RefLinkTemplate" "{RepoLink}/src/{RefType}/{RefShortName}/{TreePath}"
+							"AllowCreateNewRef" .CanCreateBranch
+							"ShowViewAllRefsEntry" true
+						}}
+						{{if and .CanCompareOrPull .IsViewBranch (not .Repository.IsArchived)}}
+							{{$cmpBranch := ""}}
+							{{if ne .Repository.ID .BaseRepo.ID}}
+								{{$cmpBranch = printf "%s/%s:" (.Repository.OwnerName|PathEscape) (.Repository.Name|PathEscape)}}
 							{{end}}
-							<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
-								{{ctx.Locale.Tr "repo.editor.patch"}}
+							{{$cmpBranch = print $cmpBranch (.BranchName|PathEscapeSegments)}}
+							{{$compareLink := printf "%s/compare/%s...%s" .BaseRepo.Link (.BaseRepo.DefaultBranch|PathEscapeSegments) $cmpBranch}}
+							<a id="new-pull-request" role="button" class="ui compact basic button" href="{{$compareLink}}"
+								data-tooltip-content="{{if .PullRequestCtx.Allowed}}{{ctx.Locale.Tr "repo.pulls.compare_changes"}}{{else}}{{ctx.Locale.Tr "action.compare_branch"}}{{end}}">
+								{{svg "octicon-git-pull-request"}}
 							</a>
-						</div>
-					</button>
-				{{end}}
+						{{end}}
 
-				{{if and $isHomepage (.Repository.IsTemplate)}}
-					<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
-						{{ctx.Locale.Tr "repo.use_template"}}
-					</a>
-				{{end}}
-				{{if $isHomepage}}
-					{{/* only show the "code search" on the repo home page, it only does global search,
-						so do not show it when viewing file or directory to avoid misleading users (it doesn't search in a directory) */}}
-					<form class="ignore-dirty tw-flex tw-flex-1" action="{{.RepoLink}}/search" method="get">
-						<div class="ui small action input tw-flex-1">
-							<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
-							{{template "shared/search/button"}}
-						</div>
-					</form>
-				{{else}}
-					<span class="breadcrumb repo-path tw-ml-1">
-						<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
-						{{- range $i, $v := .TreeNames -}}
-							<span class="breadcrumb-divider">/</span>
-							{{- if eq $i $l -}}
-								<span class="active section" title="{{$v}}">{{$v}}</span>
-								<button class="btn interact-fg tw-mx-1" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
-							{{- else -}}
-								{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
-							{{- end -}}
-						{{- end -}}
-					</span>
-				{{end}}
-			</div>
-			<div class="repo-button-row-right">
-				<!-- Only show clone panel in repository home page -->
-				{{if $isHomepage}}
-					<div class="clone-panel ui action tiny input">
-						{{template "repo/clone_buttons" .}}
-						<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
-							{{svg "octicon-kebab-horizontal"}}
-							<div class="menu">
-								{{if not $.DisableDownloadSourceArchives}}
-									<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
-									<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
-									<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
-								{{end}}
-								{{if .CitiationExist}}
-									<a class="item" id="cite-repo-button">{{svg "octicon-cross-reference" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}</a>
-								{{end}}
-								{{range .OpenWithEditorApps}}
-									<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
-								{{end}}
+						<!-- Show go to file if on home page -->
+						{{if $isTreePathRoot}}
+							<a href="{{.Repository.Link}}/find/{{.BranchNameSubURL}}" class="ui compact basic button">{{ctx.Locale.Tr "repo.find_file.go_to_file"}}</a>
+						{{end}}
+
+						{{if and .CanWriteCode .IsViewBranch (not .Repository.IsMirror) (not .Repository.IsArchived) (not .IsViewFile)}}
+							<button class="ui dropdown basic compact jump button"{{if not .Repository.CanEnableEditor}} disabled{{end}}>
+								{{ctx.Locale.Tr "repo.editor.add_file"}}
+								{{svg "octicon-triangle-down" 14 "dropdown icon"}}
+								<div class="menu">
+									<a class="item" href="{{.RepoLink}}/_new/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
+										{{ctx.Locale.Tr "repo.editor.new_file"}}
+									</a>
+									{{if .RepositoryUploadEnabled}}
+									<a class="item" href="{{.RepoLink}}/_upload/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
+										{{ctx.Locale.Tr "repo.editor.upload_file"}}
+									</a>
+									{{end}}
+									<a class="item" href="{{.RepoLink}}/_diffpatch/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">
+										{{ctx.Locale.Tr "repo.editor.patch"}}
+									</a>
+								</div>
+							</button>
+						{{end}}
+
+						{{if and $isTreePathRoot .Repository.IsTemplate}}
+							<a role="button" class="ui primary compact button" href="{{AppSubUrl}}/repo/create?template_id={{.Repository.ID}}">
+								{{ctx.Locale.Tr "repo.use_template"}}
+							</a>
+						{{end}}
+
+						{{if not $isTreePathRoot}}
+							{{$treeNameIdxLast := Eval $treeNamesLen "-" 1}}
+							<span class="breadcrumb repo-path tw-ml-1">
+								<a class="section" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}" title="{{.Repository.Name}}">{{StringUtils.EllipsisString .Repository.Name 30}}</a>
+								{{- range $i, $v := .TreeNames -}}
+									<span class="breadcrumb-divider">/</span>
+									{{- if eq $i $treeNameIdxLast -}}
+										<span class="active section" title="{{$v}}">{{$v}}</span>
+										<button class="btn interact-fg tw-mx-1" data-clipboard-text="{{$.TreePath}}" data-tooltip-content="{{ctx.Locale.Tr "copy_path"}}">{{svg "octicon-copy" 14}}</button>
+									{{- else -}}
+										{{$p := index $.Paths $i}}<span class="section"><a href="{{$.BranchLink}}/{{PathEscapeSegments $p}}" title="{{$v}}">{{$v}}</a></span>
+									{{- end -}}
+								{{- end -}}
+							</span>
+						{{end}}
+					</div>
+
+					<div class="repo-button-row-right">
+						<!-- Only show clone panel in repository home page -->
+						{{if $isTreePathRoot}}
+							<div class="clone-panel ui action tiny input">
+								{{template "repo/clone_buttons" .}}
+								<button class="ui small jump dropdown icon button" data-tooltip-content="{{ctx.Locale.Tr "repo.more_operations"}}">
+									{{svg "octicon-kebab-horizontal"}}
+									<div class="menu">
+										{{if not $.DisableDownloadSourceArchives}}
+											<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.zip" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_zip"}}</a>
+											<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.tar.gz" rel="nofollow">{{svg "octicon-file-zip" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_tar"}}</a>
+											<a class="item archive-link" href="{{$.RepoLink}}/archive/{{PathEscapeSegments $.RefName}}.bundle" rel="nofollow">{{svg "octicon-package" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.download_bundle"}}</a>
+										{{end}}
+										{{range .OpenWithEditorApps}}
+											<a class="item js-clone-url-editor" data-href-template="{{.OpenURL}}">{{.IconHTML}}{{ctx.Locale.Tr "repo.open_with_editor" .DisplayName}}</a>
+										{{end}}
+									</div>
+								</button>
+								{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
 							</div>
-						</button>
-						{{template "repo/clone_script" .}}{{/* the script will update `.js-clone-url` and related elements */}}
+							{{template "repo/cite/cite_modal" .}}
+						{{end}}
+						{{if and (not $isTreePathRoot) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
+							<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
+								{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
+							</a>
+						{{end}}
 					</div>
-					{{template "repo/cite/cite_modal" .}}
-				{{end}}
-				{{if and (not $isHomepage) (not .IsViewFile) (not .IsBlame)}}{{/* IsViewDirectory (not home), TODO: split the templates, avoid using "if" tricks */}}
-					<a class="ui button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">
-						{{svg "octicon-history" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.file_history"}}
-					</a>
+				</div>
+				{{if .IsViewFile}}
+					{{template "repo/view_file" .}}
+				{{else if .IsBlame}}
+					{{template "repo/blame" .}}
+				{{else}}{{/* IsViewDirectory */}}
+					{{template "repo/view_list" .}}
 				{{end}}
 			</div>
+
+			{{if $showSidebar}}
+				<div class="repo-home-sidebar-top">{{template "repo/home_sidebar_top" .}}</div>
+				<div class="repo-home-sidebar-bottom">{{template "repo/home_sidebar_bottom" .}}</div>
+			{{end}}
 		</div>
-		{{if .IsViewFile}}
-			{{template "repo/view_file" .}}
-		{{else if .IsBlame}}
-			{{template "repo/blame" .}}
-		{{else}}{{/* IsViewDirectory */}}
-			{{template "repo/view_list" .}}
-		{{end}}
 	</div>
 </div>
 {{template "base/footer" .}}
diff --git a/templates/repo/home_sidebar_bottom.tmpl b/templates/repo/home_sidebar_bottom.tmpl
new file mode 100644
index 0000000000000..57b4a95ddcc43
--- /dev/null
+++ b/templates/repo/home_sidebar_bottom.tmpl
@@ -0,0 +1,59 @@
+<div class="flex-list">
+	{{if .LatestRelease}}
+	<div class="flex-item">
+		<div class="flex-item-main">
+			<div class="flex-item-title">
+				<a class="item muted" href="{{.Link}}/releases">
+					{{ctx.Locale.Tr "repo.releases"}}
+					<span class="ui small label">{{.NumReleases}}</span>
+				</a>
+			</div>
+			<div class="flex-item">
+				<div class="flex-item-icon">
+					{{svg "octicon-tag" 16}}
+				</div>
+				<div class="flex-item-main">
+					<div class="flex-item-header">
+						<div class="flex-item-title tw-gap-2">
+							<a class="gt-ellipsis muted" href="{{.LatestRelease.Link}}" title="{{.LatestRelease.Title}}">{{.LatestRelease.Title}}</a>
+							<span class="ui basic green label tw-h-100">{{ctx.Locale.Tr "latest"}}</span>
+						</div>
+					</div>
+					<div class="flex-item-body">
+						<span class="time">{{DateUtils.TimeSince .LatestRelease.CreatedUnix}}</span>
+					</div>
+				</div>
+			</div>
+		</div>
+	</div>
+	{{end}}
+
+	{{if and (not .IsEmptyRepo) .LanguageStats}}
+	<div class="flex-item">
+		<div class="flex-item-main">
+			<div class="flex-item-title">
+				{{ctx.Locale.Tr "repo.repo_lang"}}
+			</div>
+
+			<div class="flex-item-body">
+				<div class="language-stats">
+					{{range .LanguageStats}}
+						<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-placement="top" data-tooltip-content={{.Language}} data-tooltip-follow-cursor="horizontal"></div>
+					{{end}}
+				</div>
+				<div class="language-stats-details">
+					{{range .LanguageStats}}
+						<div class="item">
+							<i class="color-icon" style="background-color: {{.Color}}"></i>
+							<span class="tw-font-semibold">
+								{{Iif (eq .Language "other") (ctx.Locale.Tr "repo.language_other") .Language}}
+							</span>
+							{{.Percentage}}%
+						</div>
+					{{end}}
+				</div>
+			</div>
+		</div>
+	</div>
+	{{end}}
+</div>
diff --git a/templates/repo/home_sidebar_top.tmpl b/templates/repo/home_sidebar_top.tmpl
new file mode 100644
index 0000000000000..d36c5b0433b6e
--- /dev/null
+++ b/templates/repo/home_sidebar_top.tmpl
@@ -0,0 +1,67 @@
+<form class="ignore-dirty tw-flex tw-flex-1 tw-mt-1" action="{{.RepoLink}}/search" method="get">
+	<div class="ui small action input tw-flex-1">
+		<input name="q" size="10" placeholder="{{ctx.Locale.Tr "search.code_kind"}}">
+		{{template "shared/search/button"}}
+	</div>
+</form>
+
+<div class="flex-list">
+	<div class="flex-item">
+		<div class="flex-item-main">
+			<div class="flex-item-title">
+				{{ctx.Locale.Tr "repo.repo_desc"}}
+			</div>
+			{{if and (not .HideRepoInfo) (not .IsBlame)}}
+				<div class="flex-item-body repo-description tw-break-anywhere tw-gap-2 tw-mt-2">
+					{{- $description := .Repository.DescriptionHTML ctx -}}
+					{{if $description}}{{$description | RenderCodeBlock}}{{else}}{{ctx.Locale.Tr "repo.repo_no_desc"}}{{end}}
+					{{if .Repository.Website}}{{svg "octicon-link"}}<a href="{{.Repository.Website}}">{{.Repository.Website}}</a>{{end}}
+				</div>
+				<div class="tw-flex tw-items-center tw-flex-wrap tw-gap-2 tw-my-2" id="repo-topics">
+					{{/* !!!! it SHOULD and MUST match the code in issue-home.js */}}
+					{{range .Topics}}<a class="repo-topic ui large label gt-ellipsis" title={{.Name}} href="{{AppSubUrl}}/explore/repos?q={{.Name}}&topic=1">{{.Name}}</a>{{end}}
+				</div>
+				{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
+					<button id="manage_topic" class="btn interact-fg tw-text-12">{{ctx.Locale.Tr "repo.topic.manage_topics"}}</button>
+				{{end}}
+			{{end}}
+			{{if and .Permission.IsAdmin (not .Repository.IsArchived)}}
+				<div class="ui form tw-hidden flex-item-body tw-gap-2 tw-my-2" id="topic_edit">
+					<div class="ui fluid multiple search selection dropdown tw-flex-wrap tw-flex-1">
+						<input type="hidden" name="topics" value="{{range $i, $v := .Topics}}{{.Name}}{{if Eval $i "+" 1 "<" (len $.Topics)}},{{end}}{{end}}">
+						{{range .Topics}}
+							{{/* keep the same layout as Fomantic UI generated labels */}}
+							<a class="ui label transition visible tw-cursor-default tw-inline-block repo-topic" data-value="{{.Name}}">{{.Name}}{{svg "octicon-x" 16 "delete icon"}}</a>
+						{{end}}
+						<div class="text"></div>
+					</div>
+					<div>
+						<button class="ui primary button" id="save_topic" data-link="{{.RepoLink}}/topics">{{ctx.Locale.Tr "save"}}</button>
+						<button class="ui basic button" id="cancel_topic_edit">{{ctx.Locale.Tr "cancel"}}</button>
+					</div>
+				</div>
+			{{end}}
+			{{if .ReadmeExist}}
+				<div class="flex-item-body tw-mt-2">
+					<a class="tw-flex tw-items-center tw-gap-2 muted" href="{{.TreeLink}}/{{.FileName}}">
+						{{svg "octicon-book"}}{{ctx.Locale.Tr "readme"}}
+					</a>
+				</div>
+			{{end}}
+			{{if .DetectedRepoLicenses}}
+				<div class="flex-item-body">
+					<a class="tw-flex tw-items-center tw-gap-2 muted" href="{{.RepoLink}}/src/{{.Repository.DefaultBranch}}/{{PathEscapeSegments .LicenseFileName}}" title="{{StringUtils.Join .DetectedRepoLicenses ", "}}">
+						{{svg "octicon-law"}}{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}
+					</a>
+				</div>
+			{{end}}
+			{{if .CitiationExist}}
+				<div class="flex-item-body">
+					<a class="tw-flex tw-items-center tw-gap-2 muted" id="cite-repo-button">
+						{{svg "octicon-cross-reference"}}{{ctx.Locale.Tr "repo.cite_this_repo"}}
+					</a>
+				</div>
+			{{end}}
+		</div>
+	</div>
+</div>
diff --git a/templates/repo/sub_menu.tmpl b/templates/repo/sub_menu.tmpl
index 6f53acd31ece4..ccb98b94ad2f8 100644
--- a/templates/repo/sub_menu.tmpl
+++ b/templates/repo/sub_menu.tmpl
@@ -13,11 +13,6 @@
 					{{svg "octicon-tag"}} <b>{{ctx.Locale.PrettyNumber .NumTags}}</b> {{ctx.Locale.TrN .NumTags "repo.tag" "repo.tags"}}
 				</a>
 			{{end}}
-			{{if .DetectedRepoLicenses}}
-				<a class="item muted" href="{{.RepoLink}}/src/{{.Repository.DefaultBranch}}/{{PathEscapeSegments .LicenseFileName}}" data-tooltip-placement="top" data-tooltip-content="{{StringUtils.Join .DetectedRepoLicenses ", "}}">
-					{{svg "octicon-law"}} <b>{{if eq (len .DetectedRepoLicenses) 1}}{{index .DetectedRepoLicenses 0}}{{else}}{{ctx.Locale.Tr "repo.multiple_licenses"}}{{end}}</b>
-				</a>
-			{{end}}
 			<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-placement="top" data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
 				{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}}
 				{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}}
@@ -25,27 +20,5 @@
 			</span>
 		{{end}}
 	</div>
-	{{if and (.Permission.CanRead ctx.Consts.RepoUnitTypeCode) (not .IsEmptyRepo) .LanguageStats}}
-	<div class="ui segment sub-menu language-stats-details tw-hidden">
-		{{range .LanguageStats}}
-		<div class="item">
-			<i class="color-icon" style="background-color: {{.Color}}"></i>
-			<span class="tw-font-semibold">
-				{{if eq .Language "other"}}
-					{{ctx.Locale.Tr "repo.language_other"}}
-				{{else}}
-					{{.Language}}
-				{{end}}
-			</span>
-			{{.Percentage}}%
-		</div>
-		{{end}}
-	</div>
-	<a class="ui segment language-stats show-panel toggle" data-panel=".repository-summary > .sub-menu">
-		{{range .LanguageStats}}
-		<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-placement="top" data-tooltip-content={{.Language}} data-tooltip-follow-cursor="horizontal"></div>
-		{{end}}
-	</a>
-	{{end}}
 </div>
 {{end}}
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index c4d61edad8b36..3edfbb347456f 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -1,4 +1,4 @@
-<table id="repo-files-table" class="ui single line table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
+<table id="repo-files-table" class="ui single line fixed table tw-mt-0" {{if .HasFilesWithoutLatestCommit}}hx-indicator="tr.notready td.message span" hx-trigger="load" hx-swap="morph" hx-post="{{.LastCommitLoaderURL}}"{{end}}>
 	<thead>
 		<tr class="commit-list">
 			<th class="tw-overflow-hidden" colspan="2">
diff --git a/web_src/css/index.css b/web_src/css/index.css
index 174a4a9cbc392..158ae42d3e325 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -65,6 +65,7 @@
 @import "./repo/linebutton.css";
 @import "./repo/wiki.css";
 @import "./repo/header.css";
+@import "./repo/home.css";
 @import "./repo/reactions.css";
 
 @import "./editor/fileeditor.css";
diff --git a/web_src/css/repo.css b/web_src/css/repo.css
index b40859975c144..3eebc0c477dc1 100644
--- a/web_src/css/repo.css
+++ b/web_src/css/repo.css
@@ -422,14 +422,6 @@ td .commit-summary {
   border-radius: 0 0 var(--border-radius) var(--border-radius);
 }
 
-.repository.file.list .sidebar {
-  padding-left: 0;
-}
-
-.repository.file.list .sidebar .svg {
-  width: 16px;
-}
-
 .repo-editor-header {
   width: 100%;
 }
@@ -1822,16 +1814,6 @@ td .commit-summary {
   background: var(--color-secondary);
 }
 
-.repository .repository-summary .segment.language-stats {
-  display: flex;
-  gap: 2px;
-  padding: 0;
-  height: 10px;
-  white-space: nowrap;
-  border-radius: 0 0 3px 3px !important;
-  overflow: hidden;
-}
-
 #cite-repo-modal #citation-panel {
   display: flex;
   width: 100%;
@@ -2172,11 +2154,7 @@ td .commit-summary {
   justify-content: flex-end;
 }
 
-.repo-button-row[data-is-homepage="false"] .repo-button-row-right {
-  flex-grow: 0;
-}
-
-@media (max-width: 991px) {
+@media (max-width: 1200px) {
   .repository:not(.wiki) .repo-button-row {
     flex-direction: column;
     align-items: stretch;
@@ -2302,6 +2280,7 @@ tbody.commit-list {
   font-weight: var(--font-weight-normal);
   cursor: pointer;
   margin: 0;
+  display: inline-block !important;
 }
 
 #new-dependency-drop-list.ui.selection.dropdown {
@@ -2820,9 +2799,9 @@ tbody.commit-list {
 /* FIXME: These media selectors are not ideal (just keep them from old code).
     There are many different pages, some need the max-width while some others don't,
     they should be tested and improved in the future. */
-@media (min-width: 768px) and (max-width: 991.98px) {
+@media (min-width: 768px) and (max-width: 1235px) {
   .branch-selector-dropdown .branch-dropdown-button {
-    max-width: 185px;
+    max-width: 301px;
   }
 }
 
diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css
new file mode 100644
index 0000000000000..fd8fac27e27b4
--- /dev/null
+++ b/web_src/css/repo/home.css
@@ -0,0 +1,77 @@
+.repo-grid-filelist-sidebar {
+  display: grid;
+  grid-template-columns: auto 300px;
+  grid-template-rows: auto auto 1fr;
+}
+
+.repo-grid-filelist-sidebar .repo-home-filelist {
+  min-width: 0;
+  grid-column: 1;
+  grid-row: 1 / 4;
+}
+
+.repo-grid-filelist-sidebar .repo-home-sidebar-top {
+  grid-column: 2;
+  grid-row: 1;
+  padding-left: 1em;
+}
+.repo-grid-filelist-sidebar .repo-home-sidebar-bottom {
+  grid-column: 2;
+  grid-row: 2;
+  padding-left: 1em;
+}
+.repo-home-sidebar-bottom > :first-child {
+  border-top: 1px solid var(--color-secondary); /* same to .flex-list > .flex-item + .flex-item */
+}
+
+@media (max-width: 767.98px) {
+  .repo-grid-filelist-sidebar {
+    grid-template-columns: 100%;
+    grid-template-rows: auto auto auto;
+  }
+  .repo-grid-filelist-sidebar .repo-home-filelist {
+    grid-column: 1;
+    grid-row: 2;
+  }
+  .repo-grid-filelist-sidebar .repo-home-sidebar-top {
+    grid-column: 1;
+    grid-row: 1;
+    padding-left: 0;
+  }
+  .repo-grid-filelist-sidebar .repo-home-sidebar-bottom {
+    grid-column: 1;
+    grid-row: 3;
+    padding-left: 0;
+  }
+  .repo-home-sidebar-bottom > :first-child {
+    border-top: 0;
+  }
+}
+
+.language-stats {
+  display: flex;
+  gap: 2px;
+  padding: 0;
+  height: 10px;
+  white-space: nowrap;
+  border-radius: 5px;
+  overflow: hidden;
+  width: 100%;
+  margin-top: 1rem;
+  margin-bottom: 5px;
+}
+
+.language-stats-details {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.language-stats-details .item {
+  height: 30px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 0.25em;
+  padding: 0 0.5em; /* make the UI look better for narrow (mobile) view */
+  text-decoration: none;
+}
diff --git a/web_src/js/features/citation.ts b/web_src/js/features/citation.ts
index 8fc6beabfb2d8..fc5bb38f0aeca 100644
--- a/web_src/js/features/citation.ts
+++ b/web_src/js/features/citation.ts
@@ -41,35 +41,28 @@ export async function initCitationFileCopyContent() {
     citationCopyApa.classList.toggle('primary', !isBibtex);
   };
 
-  document.querySelector('#cite-repo-button')?.addEventListener('click', async (e: MouseEvent & {target: HTMLAnchorElement}) => {
-    const dropdownBtn = e.target.closest('.ui.dropdown.button');
-    dropdownBtn.classList.add('is-loading');
-
+  document.querySelector('#cite-repo-button')?.addEventListener('click', async () => {
     try {
-      try {
-        await initInputCitationValue(citationCopyApa, citationCopyBibtex);
-      } catch (e) {
-        console.error(`initCitationFileCopyContent error: ${e}`, e);
-        return;
-      }
-      updateUi();
+      await initInputCitationValue(citationCopyApa, citationCopyBibtex);
+    } catch (e) {
+      console.error(`initCitationFileCopyContent error: ${e}`, e);
+      return;
+    }
+    updateUi();
 
-      citationCopyApa.addEventListener('click', () => {
-        localStorage.setItem('citation-copy-format', 'apa');
-        updateUi();
-      });
+    citationCopyApa.addEventListener('click', () => {
+      localStorage.setItem('citation-copy-format', 'apa');
+      updateUi();
+    });
 
-      citationCopyBibtex.addEventListener('click', () => {
-        localStorage.setItem('citation-copy-format', 'bibtex');
-        updateUi();
-      });
+    citationCopyBibtex.addEventListener('click', () => {
+      localStorage.setItem('citation-copy-format', 'bibtex');
+      updateUi();
+    });
 
-      inputContent.addEventListener('click', () => {
-        inputContent.select();
-      });
-    } finally {
-      dropdownBtn.classList.remove('is-loading');
-    }
+    inputContent.addEventListener('click', () => {
+      inputContent.select();
+    });
 
     fomanticQuery('#cite-repo-modal').modal('show');
   });
diff --git a/web_src/js/features/repo-home.ts b/web_src/js/features/repo-home.ts
index a65a1815d21fa..df52b87f5ae13 100644
--- a/web_src/js/features/repo-home.ts
+++ b/web_src/js/features/repo-home.ts
@@ -7,7 +7,7 @@ import {fomanticQuery} from '../modules/fomantic/base.ts';
 const {appSubUrl} = window.config;
 
 export function initRepoTopicBar() {
-  const mgrBtn = document.querySelector('#manage_topic');
+  const mgrBtn = document.querySelector<HTMLButtonElement>('#manage_topic');
   if (!mgrBtn) return;
 
   const editDiv = document.querySelector('#topic_edit');
@@ -18,7 +18,7 @@ export function initRepoTopicBar() {
   mgrBtn.addEventListener('click', () => {
     hideElem(viewDiv);
     showElem(editDiv);
-    topicDropdown.querySelector('input.search').focus();
+    topicDropdown.querySelector<HTMLInputElement>('input.search').focus();
   });
 
   document.querySelector('#cancel_topic_edit').addEventListener('click', () => {
@@ -28,9 +28,9 @@ export function initRepoTopicBar() {
     mgrBtn.focus();
   });
 
-  document.querySelector('#save_topic').addEventListener('click', async (e) => {
+  document.querySelector('#save_topic').addEventListener('click', async (e: MouseEvent & {target: HTMLButtonElement}) => {
     lastErrorToast?.hideToast();
-    const topics = editDiv.querySelector('input[name=topics]').value;
+    const topics = editDiv.querySelector<HTMLInputElement>('input[name=topics]').value;
 
     const data = new FormData();
     data.append('topics', topics);
@@ -45,12 +45,13 @@ export function initRepoTopicBar() {
           const topicArray = topics.split(',');
           topicArray.sort();
           for (const topic of topicArray) {
-            // it should match the code in repo/home.tmpl
+            // TODO: sort items in topicDropdown, or items in edit div will have different order to the items in view div
+            // !!!! it SHOULD and MUST match the code in "home_sidebar_top.tmpl" !!!!
             const link = document.createElement('a');
-            link.classList.add('repo-topic', 'ui', 'large', 'label');
+            link.classList.add('repo-topic', 'ui', 'large', 'label', 'gt-ellipsis');
             link.href = `${appSubUrl}/explore/repos?q=${encodeURIComponent(topic)}&topic=1`;
             link.textContent = topic;
-            mgrBtn.parentNode.insertBefore(link, mgrBtn); // insert all new topics before manage button
+            viewDiv.append(link);
           }
         }
         hideElem(editDiv);