Repository: gohugoio/hugo
Pull request #13603 (2025-04-13 10:24:30)

Typo fixes
To help find typos I used this from root:

`npx cspell \"**/*.*\" --no-progress --quiet`

with config:

```json
{
  "version": "0.2",
  "allowCompoundWords": true,
  "flagWords": [
    "alot",
    "hte",
    "teh"
  ],
  "ignorePaths": [
    "**/*.ics"
  ],
  "ignoreRegExpList": [
    "# cspell: ignore everything after a right arrow",
    "\\s+→\\s+.+"
  ],
  "language": "de,en,en-US,es,fr,it,lorem",
  "words": [
    "# ----------------------------------------------------------------------",
    "# cspell: ignore foreign language words",
    "# cspell: ignore miscellaneous",
    "# cspell: ignore operating systems and software packages",
    "# cspell: ignore proper nouns",
    "abcdefghi",
    "abcdefghijk",
    "abcdefghis",
    "abcdefgs",
    "abcdefs",
    "ABNF",
    "absurlified",
    "absurlify",
    "acfg",
    "Afero",
    "alloc",
    "Anchorize",
    "Andrey",
    "antialiasing",
    "argsv",
    "asciidoctor",
    "attrtp",
    "bcfg",
    "benchcmp",
    "bepsays",
    "bezpieczeństwo",
    "bgcolor",
    "Bjørn",
    "blogm",
    "bofi",
    "boolor",
    "brotli",
    "bydate",
    "bylength",
    "byparam",
    "bypubdate",
    "bytype",
    "canonify",
    "cascadeparam",
    "ccolor",
    "cerr",
    "chaseadamsio",
    "choco",
    "CLOUDFRONTDISTRIBUTIONID",
    "Cmds",
    "codeowners",
    "Commentf",
    "composability",
    "concatr",
    "configurators",
    "confmu",
    "contentmemw",
    "Contentr",
    "contentrc",
    "corejs",
    "CPATH",
    "curr",
    "dcfg",
    "Debugf",
    "debugl",
    "defang",
    "defaultv",
    "deindent",
    "deletefd",
    "didn",
    "disqus",
    "Dmitry",
    "dname",
    "doas",
    "dokumentation",
    "dostounix",
    "downscale",
    "downscaled",
    "downscaling",
    "dring",
    "dynacache",
    "Eliott",
    "Emojify",
    "émotion",
    "envtags",
    "eopkg",
    "Eqer",
    "errch",
    "errorf",
    "Erroridf",
    "errorl",
    "Errorln",
    "errorsw",
    "esbuild",
    "exif",
    "extj",
    "ferr",
    "ferrors",
    "ferrs",
    "filefs",
    "filterby",
    "fims",
    "Flexi",
    "fmcfg",
    "fname",
    "Français",
    "fromt",
    "fromv",
    "fromvs",
    "fset",
    "fsnotify",
    "fsnotity",
    "gcaws",
    "geolocalized",
    "getenv",
    "gfilters",
    "giph",
    "giphy",
    "gitee",
    "givenv",
    "goarch",
    "gobench",
    "GOCACHE",
    "godartsassv",
    "GOEXE",
    "gofmt",
    "gohtml",
    "gohugo",
    "gohugoio",
    "goimports",
    "goldmark",
    "golibsass",
    "golint",
    "gomod",
    "gomods",
    "gotmpl",
    "graphb",
    "grayscale",
    "Gregor",
    "greppable",
    "Groot",
    "Gruber",
    "gtag",
    "gzipped",
    "hcontext",
    "hexec",
    "hglob",
    "hreflect",
    "hset",
    "htesting",
    "htext",
    "htime",
    "hugio",
    "hugofs",
    "icfg",
    "Idents",
    "idprefix",
    "idseparator",
    "iface",
    "imgconfig",
    "imgs",
    "indirected",
    "Infof",
    "infol",
    "Infoln",
    "infow",
    "inor",
    "instgrm",
    "intelli",
    "iofs",
    "isatty",
    "ishex",
    "istemp",
    "Jaco",
    "jdoe",
    "jmespath",
    "jmooring",
    "JSHTML",
    "Jsonify",
    "jsons",
    "KaTeX",
    "Keyer",
    "keyt",
    "kubuntu",
    "ldquo",
    "libros",
    "lidx",
    "Linkify",
    "lofi",
    "logg",
    "logln",
    "lrport",
    "lubuntu",
    "Lundmark",
    "Markdownify",
    "marshal",
    "marshaling",
    "MathJax",
    "mathrm",
    "mconf",
    "mdash",
    "memfs",
    "metaw",
    "miesiąc",
    "miesięcy",
    "milli",
    "minifier",
    "minifiers",
    "misérables",
    "Mkcert",
    "monokai",
    "moolor",
    "multihost",
    "myblog",
    "mybucket",
    "mybundle",
    "myclass",
    "myconfigdir",
    "mydata",
    "mydeployment",
    "mydir",
    "myenv",
    "myexistingsite",
    "myhugosite",
    "myjekyllsite",
    "myjsoncontent",
    "mymodule",
    "myparam",
    "myposts",
    "mysection",
    "mysite",
    "mysource",
    "mystatic",
    "mytag",
    "mytemplate",
    "mytheme",
    "mytomlcontent",
    "myworkingdir",
    "myyamlcontent",
    "Netravali",
    "Neue",
    "newfd",
    "newp",
    "nfilename",
    "nlocker",
    "Nobase",
    "noclasses",
    "nofilefilename",
    "nofileslug",
    "nofileslugs",
    "nofiletitle",
    "Noll",
    "noopener",
    "noshift",
    "nosql",
    "nread",
    "NUMWORKERMULTIPLIER",
    "Octopress",
    "oklch",
    "onclick",
    "optionsm",
    "optsm",
    "optsv",
    "osfs",
    "pandoc",
    "Pastorius",
    "pathc",
    "Pathify",
    "pcfg",
    "pconf",
    "pdate",
    "performantly",
    "pkcs",
    "pkeys",
    "pkgin",
    "Plainify",
    "preconfigured",
    "prerendering",
    "printm",
    "projekt",
    "proto",
    "publicw",
    "publishw",
    "Querify",
    "Randomising",
    "rbylength",
    "rbypubdate",
    "rbysection",
    "RCDATA",
    "rclone",
    "rcsources",
    "rdquo",
    "Rebuilder",
    "receiverv",
    "redir",
    "redirection",
    "redirections",
    "regexs",
    "régime",
    "Resizer",
    "resolvedm",
    "resultch",
    "resultv",
    "Resumé",
    "resv",
    "rgba",
    "Roboto",
    "rootv",
    "rsmith",
    "Samsa",
    "Satoshi",
    "sectnocontent",
    "sectnofrontmatter",
    "Sedykh",
    "Segoe",
    "seqv",
    "Serveur",
    "setm",
    "shortcode",
    "shortcodes",
    "shouldn",
    "smap",
    "SMSI",
    "spath",
    "stringifier",
    "struct",
    "stuttery",
    "stype",
    "subexpression",
    "subexpressions",
    "suppressable",
    "Syncer",
    "tableofcontents",
    "Tarantsov",
    "tbody",
    "tctx",
    "tdewolff",
    "Templ",
    "templating",
    "tfoot",
    "THEAD",
    "themeconfigdirparam",
    "tinygo",
    "tjones",
    "tmpbucketdir",
    "tmpfsdir",
    "tmpl",
    "toclevels",
    "Tocs",
    "tocss",
    "tomlcontent",
    "tplimpl",
    "tracel",
    "transpile",
    "transpilerv",
    "transpiles",
    "tsti",
    "Ufour",
    "unaddressable",
    "uncloned",
    "uncomparable",
    "undelimited",
    "Unmarshable",
    "unmarshal",
    "unmarshaled",
    "unmarshaling",
    "unmarshall",
    "unmarshals",
    "unnormalized",
    "unnudged",
    "unorderable",
    "Unported",
    "unpublishdate",
    "unrendered",
    "Unsharp",
    "Unwrapv",
    "updatefd",
    "upgrader",
    "urlize",
    "Uthree",
    "vals",
    "varn",
    "vfunc",
    "Warnf",
    "Warnidf",
    "warnl",
    "Warnln",
    "WEBM",
    "Webp",
    "WHATWG",
    "withc",
    "xfeff",
    "xubuntu",
    "yamlcontent",
    "Yukihiro",
    "Zeroer",
    "zgotmplz",
    "zoolor",
    "zoop"
  ]
}
```


Patch:
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
index 4b3feaa14..b0a2f9fc4 100644
--- a/common/paths/pathparser.go
+++ b/common/paths/pathparser.go
@@ -124,14 +124,15 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
 	if p.posContainerHigh != -1 {
 		return
 	}
-	mayHaveLang := pp.LanguageIndex != nil
+	mayHaveLang := p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
 	mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
 	mayHaveOutputFormat := component == files.ComponentFolderLayouts
-	mayHaveKind := mayHaveOutputFormat
+	mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
+	mayHaveLayout := component == files.ComponentFolderLayouts
 
 	var found bool
 	var high int
-	if len(p.identifiers) > 0 {
+	if len(p.identifiersKnown) > 0 {
 		high = lastDot
 	} else {
 		high = len(p.s)
@@ -139,9 +140,9 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
 	id := types.LowHigh[string]{Low: i + 1, High: high}
 	sid := p.s[id.Low:id.High]
 
-	if len(p.identifiers) == 0 {
+	if len(p.identifiersKnown) == 0 {
 		// The first is always the extension.
-		p.identifiers = append(p.identifiers, id)
+		p.identifiersKnown = append(p.identifiersKnown, id)
 		found = true
 
 		// May also be the output format.
@@ -164,8 +165,8 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
 			}
 			found = langFound
 			if langFound {
-				p.identifiers = append(p.identifiers, id)
-				p.posIdentifierLanguage = len(p.identifiers) - 1
+				p.identifiersKnown = append(p.identifiersKnown, id)
+				p.posIdentifierLanguage = len(p.identifiersKnown) - 1
 
 			}
 		}
@@ -177,28 +178,33 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
 			// false positives on the form css.html.
 			if pp.IsOutputFormat(sid, p.Ext()) {
 				found = true
-				p.identifiers = append(p.identifiers, id)
-				p.posIdentifierOutputFormat = len(p.identifiers) - 1
+				p.identifiersKnown = append(p.identifiersKnown, id)
+				p.posIdentifierOutputFormat = len(p.identifiersKnown) - 1
 			}
 		}
 
 		if !found && mayHaveKind {
 			if kinds.GetKindMain(sid) != "" {
 				found = true
-				p.identifiers = append(p.identifiers, id)
-				p.posIdentifierKind = len(p.identifiers) - 1
+				p.identifiersKnown = append(p.identifiersKnown, id)
+				p.posIdentifierKind = len(p.identifiersKnown) - 1
 			}
 		}
 
 		if !found && sid == identifierBaseof {
 			found = true
-			p.identifiers = append(p.identifiers, id)
-			p.posIdentifierBaseof = len(p.identifiers) - 1
+			p.identifiersKnown = append(p.identifiersKnown, id)
+			p.posIdentifierBaseof = len(p.identifiersKnown) - 1
+		}
+
+		if !found && mayHaveLayout {
+			p.identifiersKnown = append(p.identifiersKnown, id)
+			p.posIdentifierLayout = len(p.identifiersKnown) - 1
+			found = true
 		}
 
 		if !found {
-			p.identifiers = append(p.identifiers, id)
-			p.identifiersUnknown = append(p.identifiersUnknown, len(p.identifiers)-1)
+			p.identifiersUnknown = append(p.identifiersUnknown, id)
 		}
 
 	}
@@ -252,13 +258,13 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
 		}
 	}
 
-	if len(p.identifiers) > 0 {
+	if len(p.identifiersKnown) > 0 {
 		isContentComponent := p.component == files.ComponentFolderContent || p.component == files.ComponentFolderArchetypes
 		isContent := isContentComponent && pp.IsContentExt(p.Ext())
-		id := p.identifiers[len(p.identifiers)-1]
+		id := p.identifiersKnown[len(p.identifiersKnown)-1]
 
-		if id.High > p.posContainerHigh {
-			b := p.s[p.posContainerHigh:id.High]
+		if id.Low > p.posContainerHigh {
+			b := p.s[p.posContainerHigh : id.Low-1]
 			if isContent {
 				switch b {
 				case "index":
@@ -294,6 +300,16 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
 		}
 	}
 
+	if p.pathType == TypeShortcode && p.posIdentifierLayout != -1 {
+		// myshortcode or myshortcode.html, no layout.
+		if len(p.identifiersKnown) <= 2 {
+			p.posIdentifierLayout = -1
+		} else {
+			// First is always the name.
+			p.posIdentifierLayout--
+		}
+	}
+
 	return p, nil
 }
 
@@ -350,13 +366,14 @@ type Path struct {
 	component string
 	pathType  Type
 
-	identifiers []types.LowHigh[string]
+	identifiersKnown   []types.LowHigh[string]
+	identifiersUnknown []types.LowHigh[string]
 
 	posIdentifierLanguage     int
 	posIdentifierOutputFormat int
 	posIdentifierKind         int
+	posIdentifierLayout       int
 	posIdentifierBaseof       int
-	identifiersUnknown        []int
 	disabled                  bool
 
 	trimLeadingSlash bool
@@ -388,10 +405,11 @@ func (p *Path) reset() {
 	p.posSectionHigh = -1
 	p.component = ""
 	p.pathType = 0
-	p.identifiers = p.identifiers[:0]
+	p.identifiersKnown = p.identifiersKnown[:0]
 	p.posIdentifierLanguage = -1
 	p.posIdentifierOutputFormat = -1
 	p.posIdentifierKind = -1
+	p.posIdentifierLayout = -1
 	p.posIdentifierBaseof = -1
 	p.disabled = false
 	p.trimLeadingSlash = false
@@ -479,7 +497,7 @@ func (p *Path) Name() string {
 // Name returns the last element of path without any extension.
 func (p *Path) NameNoExt() string {
 	if i := p.identifierIndex(0); i != -1 {
-		return p.s[p.posContainerHigh : p.identifiers[i].Low-1]
+		return p.s[p.posContainerHigh : p.identifiersKnown[i].Low-1]
 	}
 	return p.s[p.posContainerHigh:]
 }
@@ -491,7 +509,7 @@ func (p *Path) NameNoLang() string {
 		return p.Name()
 	}
 
-	return p.s[p.posContainerHigh:p.identifiers[i].Low-1] + p.s[p.identifiers[i].High:]
+	return p.s[p.posContainerHigh:p.identifiersKnown[i].Low-1] + p.s[p.identifiersKnown[i].High:]
 }
 
 // BaseNameNoIdentifier returns the logical base name for a resource without any identifier (e.g. no extension).
@@ -510,15 +528,15 @@ func (p *Path) NameNoIdentifier() string {
 }
 
 func (p *Path) nameLowHigh() types.LowHigh[string] {
-	if len(p.identifiers) > 0 {
-		lastID := p.identifiers[len(p.identifiers)-1]
+	if len(p.identifiersKnown) > 0 {
+		lastID := p.identifiersKnown[len(p.identifiersKnown)-1]
 		if p.posContainerHigh == lastID.Low {
 			// The last identifier is the name.
 			return lastID
 		}
 		return types.LowHigh[string]{
 			Low:  p.posContainerHigh,
-			High: p.identifiers[len(p.identifiers)-1].Low - 1,
+			High: p.identifiersKnown[len(p.identifiersKnown)-1].Low - 1,
 		}
 	}
 	return types.LowHigh[string]{
@@ -566,7 +584,7 @@ func (p *Path) PathNoIdentifier() string {
 
 // PathBeforeLangAndOutputFormatAndExt returns the path up to the first identifier that is not a language or output format.
 func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
-	if len(p.identifiers) == 0 {
+	if len(p.identifiersKnown) == 0 {
 		return p.norm(p.s)
 	}
 	i := p.identifierIndex(0)
@@ -582,7 +600,7 @@ func (p *Path) PathBeforeLangAndOutputFormatAndExt() string {
 		return p.norm(p.s)
 	}
 
-	id := p.identifiers[i]
+	id := p.identifiersKnown[i]
 	return p.norm(p.s[:id.Low-1])
 }
 
@@ -633,11 +651,11 @@ func (p *Path) BaseNoLeadingSlash() string {
 }
 
 func (p *Path) base(preserveExt, isBundle bool) string {
-	if len(p.identifiers) == 0 {
+	if len(p.identifiersKnown) == 0 {
 		return p.norm(p.s)
 	}
 
-	if preserveExt && len(p.identifiers) == 1 {
+	if preserveExt && len(p.identifiersKnown) == 1 {
 		// Preserve extension.
 		return p.norm(p.s)
 	}
@@ -659,7 +677,7 @@ func (p *Path) base(preserveExt, isBundle bool) string {
 	}
 
 	// For txt files etc. we want to preserve the extension.
-	id := p.identifiers[0]
+	id := p.identifiersKnown[0]
 
 	return p.norm(p.s[:high] + p.s[id.Low-1:id.High])
 }
@@ -676,6 +694,10 @@ func (p *Path) Kind() string {
 	return p.identifierAsString(p.posIdentifierKind)
 }
 
+func (p *Path) Layout() string {
+	return p.identifierAsString(p.posIdentifierLayout)
+}
+
 func (p *Path) Lang() string {
 	return p.identifierAsString(p.posIdentifierLanguage)
 }
@@ -689,8 +711,8 @@ func (p *Path) Disabled() bool {
 }
 
 func (p *Path) Identifiers() []string {
-	ids := make([]string, len(p.identifiers))
-	for i, id := range p.identifiers {
+	ids := make([]string, len(p.identifiersKnown))
+	for i, id := range p.identifiersKnown {
 		ids[i] = p.s[id.Low:id.High]
 	}
 	return ids
@@ -699,7 +721,7 @@ func (p *Path) Identifiers() []string {
 func (p *Path) IdentifiersUnknown() []string {
 	ids := make([]string, len(p.identifiersUnknown))
 	for i, id := range p.identifiersUnknown {
-		ids[i] = p.s[p.identifiers[id].Low:p.identifiers[id].High]
+		ids[i] = p.s[id.Low:id.High]
 	}
 	return ids
 }
@@ -724,7 +746,7 @@ func (p *Path) IsContentData() bool {
 	return p.pathType == TypeContentData
 }
 
-func (p Path) ForBundleType(t Type) *Path {
+func (p Path) ForType(t Type) *Path {
 	p.pathType = t
 	return &p
 }
@@ -735,12 +757,12 @@ func (p *Path) identifierAsString(i int) string {
 		return ""
 	}
 
-	id := p.identifiers[i]
+	id := p.identifiersKnown[i]
 	return p.s[id.Low:id.High]
 }
 
 func (p *Path) identifierIndex(i int) int {
-	if i < 0 || i >= len(p.identifiers) {
+	if i < 0 || i >= len(p.identifiersKnown) {
 		return -1
 	}
 	return i
diff --git a/config/allconfig/load.go b/config/allconfig/load.go
index f224009ac..9289cf294 100644
--- a/config/allconfig/load.go
+++ b/config/allconfig/load.go
@@ -213,7 +213,7 @@ func (l configLoader) applyDefaultConfig() error {
 		"disableAliases":                       false,
 		"debug":                                false,
 		"disableFastRender":                    false,
-		"timeout":                              "30s",
+		"timeout":                              "60s",
 		"timeZone":                             "",
 		"enableInlineShortcodes":               false,
 	}
diff --git a/config/configProvider.go b/config/configProvider.go
index 5bda2c55a..c21342dce 100644
--- a/config/configProvider.go
+++ b/config/configProvider.go
@@ -76,7 +76,7 @@ type AllProvider interface {
 }
 
 // We cannot import the media package as that would create a circular dependency.
-// This interface defineds a sub set of what media.ContentTypes provides.
+// This interface defines a subset of what media.ContentTypes provides.
 type ContentTypesProvider interface {
 	IsContentSuffix(suffix string) bool
 	IsContentFile(filename string) bool
diff --git a/go.mod b/go.mod
index 9423c6f68..fba76335f 100644
--- a/go.mod
+++ b/go.mod
@@ -2,7 +2,7 @@ module github.com/gohugoio/hugo
 
 require (
 	github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69
-	github.com/alecthomas/chroma/v2 v2.16.0
+	github.com/alecthomas/chroma/v2 v2.17.0
 	github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c
 	github.com/aws/aws-sdk-go-v2 v1.36.1
 	github.com/aws/aws-sdk-go-v2/service/cloudfront v1.44.10
@@ -15,7 +15,7 @@ require (
 	github.com/bep/goportabletext v0.1.0
 	github.com/bep/gowebp v0.3.0
 	github.com/bep/helpers v0.5.0
-	github.com/bep/imagemeta v0.11.0
+	github.com/bep/imagemeta v0.12.0
 	github.com/bep/lazycache v0.8.0
 	github.com/bep/logg v0.4.0
 	github.com/bep/mclib v1.20400.20402
@@ -26,7 +26,7 @@ require (
 	github.com/clbanning/mxj/v2 v2.7.0
 	github.com/disintegration/gift v1.2.1
 	github.com/dustin/go-humanize v1.0.1
-	github.com/evanw/esbuild v0.25.2
+	github.com/evanw/esbuild v0.25.3
 	github.com/fatih/color v1.18.0
 	github.com/fortytw2/leaktest v1.3.0
 	github.com/frankban/quicktest v1.14.6
@@ -69,8 +69,8 @@ require (
 	github.com/tdewolff/minify/v2 v2.20.37
 	github.com/tdewolff/parse/v2 v2.7.15
 	github.com/tetratelabs/wazero v1.9.0
-	github.com/yuin/goldmark v1.7.8
-	github.com/yuin/goldmark-emoji v1.0.5
+	github.com/yuin/goldmark v1.7.10
+	github.com/yuin/goldmark-emoji v1.0.6
 	go.uber.org/automaxprocs v1.5.3
 	gocloud.dev v0.40.0
 	golang.org/x/exp v0.0.0-20221031165847-c99f073a8326
diff --git a/go.sum b/go.sum
index 5712679f0..b432e1d6b 100644
--- a/go.sum
+++ b/go.sum
@@ -73,8 +73,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
 github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
-github.com/alecthomas/chroma/v2 v2.16.0 h1:QC5ZMizk67+HzxFDjQ4ASjni5kWBTGiigRG1u23IGvA=
-github.com/alecthomas/chroma/v2 v2.16.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
+github.com/alecthomas/chroma/v2 v2.17.0 h1:3r2Cgk+nXNICMBxIFGnTRTbQFUwMiLisW+9uos0TtUI=
+github.com/alecthomas/chroma/v2 v2.17.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
 github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
 github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
 github.com/armon/go-radix v1.0.1-0.20221118154546-54df44f2176c h1:651/eoCRnQ7YtSjAnSzRucrJz+3iGEFt+ysraELS81M=
@@ -139,8 +139,8 @@ github.com/bep/gowebp v0.3.0 h1:MhmMrcf88pUY7/PsEhMgEP0T6fDUnRTMpN8OclDrbrY=
 github.com/bep/gowebp v0.3.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
 github.com/bep/helpers v0.5.0 h1:rneezhnG7GzLFlsEWO/EnleaBRuluBDGFimalO6Y50o=
 github.com/bep/helpers v0.5.0/go.mod h1:dSqCzIvHbzsk5YOesp1M7sKAq5xUcvANsRoKdawxH4Q=
-github.com/bep/imagemeta v0.11.0 h1:jL92HhL1H70NC+f8OVVn5D/nC3FmdxTnM3R+csj54mE=
-github.com/bep/imagemeta v0.11.0/go.mod h1:23AF6O+4fUi9avjiydpKLStUNtJr5hJB4rarG18JpN8=
+github.com/bep/imagemeta v0.12.0 h1:ARf+igs5B7pf079LrqRnwzQ/wEB8Q9v4NSDRZO1/F5k=
+github.com/bep/imagemeta v0.12.0/go.mod h1:23AF6O+4fUi9avjiydpKLStUNtJr5hJB4rarG18JpN8=
 github.com/bep/lazycache v0.8.0 h1:lE5frnRjxaOFbkPZ1YL6nijzOPPz6zeXasJq8WpG4L8=
 github.com/bep/lazycache v0.8.0/go.mod h1:BQ5WZepss7Ko91CGdWz8GQZi/fFnCcyWupv8gyTeKwk=
 github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
@@ -186,8 +186,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
 github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
-github.com/evanw/esbuild v0.25.2 h1:ublSEmZSjzOc6jLO1OTQy/vHc1wiqyDF4oB3hz5sM6s=
-github.com/evanw/esbuild v0.25.2/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
+github.com/evanw/esbuild v0.25.3 h1:4JKyUsm/nHDhpxis4IyWXAi8GiyTwG1WdEp6OhGVE8U=
+github.com/evanw/esbuild v0.25.3/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48=
 github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
 github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@@ -466,11 +466,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
-github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
-github.com/yuin/goldmark-emoji v1.0.5 h1:EMVWyCGPlXJfUXBXpuMu+ii3TIaxbVBnEX9uaDC4cIk=
-github.com/yuin/goldmark-emoji v1.0.5/go.mod h1:tTkZEbwu5wkPmgTcitqddVxY9osFZiavD+r4AzQrh1U=
+github.com/yuin/goldmark v1.7.10 h1:S+LrtBjRmqMac2UdtB6yyCEJm+UILZ2fefI4p7o0QpI=
+github.com/yuin/goldmark v1.7.10/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
+github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
+github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
diff --git a/hugolib/alias.go b/hugolib/alias.go
index 0bb3165c7..7b252c613 100644
--- a/hugolib/alias.go
+++ b/hugolib/alias.go
@@ -56,6 +56,7 @@ func (a aliasHandler) renderAlias(permalink string, p page.Page) (io.Reader, err
 	templateDesc.LayoutFromUser = ""
 	templateDesc.Kind = ""
 	templateDesc.OutputFormat = output.AliasHTMLFormat.Name
+	templateDesc.MediaType = output.AliasHTMLFormat.MediaType.Type
 
 	q := tplimpl.TemplateQuery{
 		Path:     base,
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index b501cd9ea..c51b4a17a 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -180,7 +180,7 @@ func (t *pageTrees) collectAndMarkStaleIdentities(p *paths.Path) []identity.Iden
 
 	if p.Component() == files.ComponentFolderContent {
 		// It may also be a bundled content resource.
-		key := p.ForBundleType(paths.TypeContentResource).Base()
+		key := p.ForType(paths.TypeContentResource).Base()
 		tree = t.treeResources
 		nCount = 0
 		tree.ForEeachInDimension(key, doctree.DimensionLanguage.Index(),
diff --git a/hugolib/filesystems/basefs.go b/hugolib/filesystems/basefs.go
index 3e9b92087..b32b8796f 100644
--- a/hugolib/filesystems/basefs.go
+++ b/hugolib/filesystems/basefs.go
@@ -397,7 +397,7 @@ func (d *SourceFilesystem) mounts() []hugofs.FileMetaInfo {
 	})
 
 	// Filter out any mounts not belonging to this filesystem.
-	// TODO(bep) I think this is superflous.
+	// TODO(bep) I think this is superfluous.
 	n := 0
 	for _, mm := range m {
 		if mm.Meta().Component == d.Name {
diff --git a/hugolib/page__content.go b/hugolib/page__content.go
index 5f7d6f930..20abb7884 100644
--- a/hugolib/page__content.go
+++ b/hugolib/page__content.go
@@ -850,7 +850,7 @@ func (c *cachedContentScope) contentPlain(ctx context.Context) (contentPlainPlai
 	})
 	if err != nil {
 		if herrors.IsTimeoutError(err) {
-			err = fmt.Errorf("timed out rendering the page content. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file: %w", err)
+			err = fmt.Errorf("timed out rendering the page content. Extend the `timeout` limit in your Hugo config file: %w", err)
 		}
 		return contentPlainPlainWords{}, err
 	}
diff --git a/hugolib/shortcode.go b/hugolib/shortcode.go
index 3ac0940e2..cc8a145d9 100644
--- a/hugolib/shortcode.go
+++ b/hugolib/shortcode.go
@@ -677,13 +677,7 @@ Loop:
 
 			// Used to check if the template expects inner content,
 			// so just pick one arbitrarily with the same name.
-			q := tplimpl.TemplateQuery{
-				Path:     "",
-				Name:     sc.name,
-				Category: tplimpl.CategoryShortcode,
-				Consider: nil,
-			}
-			templ := s.s.TemplateStore.LookupShortcode(q)
+			templ := s.s.TemplateStore.LookupShortcodeByName(sc.name)
 			if templ == nil {
 				return nil, fmt.Errorf("%s: template for shortcode %q not found", errorPrefix, sc.name)
 			}
diff --git a/hugoreleaser.env b/hugoreleaser.env
index 3d1adbf08..901312a41 100644
--- a/hugoreleaser.env
+++ b/hugoreleaser.env
@@ -1,7 +1,11 @@
 # Release env. 
 # These will be replaced by script before release.
-HUGORELEASER_TAG=v0.146.3
-HUGORELEASER_COMMITISH=05ef8b713a3c091bfca7a3543ed016c64b3c6f88
+HUGORELEASER_TAG=v0.146.7
+HUGORELEASER_COMMITISH=1ad3d39dc4693434505fc81f91eed57333017e93
+
+
+
+
 
 
 
diff --git a/parser/metadecoders/decoder.go b/parser/metadecoders/decoder.go
index 1655ea513..419fbf4d2 100644
--- a/parser/metadecoders/decoder.go
+++ b/parser/metadecoders/decoder.go
@@ -36,16 +36,22 @@ import (
 
 // Decoder provides some configuration options for the decoders.
 type Decoder struct {
-	// Delimiter is the field delimiter used in the CSV decoder. It defaults to ','.
+	// Delimiter is the field delimiter. Used in the CSV decoder. Default is
+	// ','.
 	Delimiter rune
 
-	// Comment, if not 0, is the comment character used in the CSV decoder. Lines beginning with the
-	// Comment character without preceding whitespace are ignored.
+	// Comment, if not 0, is the comment character. Lines beginning with the
+	// Comment character without preceding whitespace are ignored. Used in the
+	// CSV decoder.
 	Comment rune
 
 	// If true, a quote may appear in an unquoted field and a non-doubled quote
-	// may appear in a quoted field. It defaults to false.
+	// may appear in a quoted field. Used in the CSV decoder. Default is false.
 	LazyQuotes bool
+
+	// The target data type, either slice or map. Used in the CSV decoder.
+	// Default is slice.
+	TargetType string
 }
 
 // OptionsKey is used in cache keys.
@@ -54,12 +60,14 @@ func (d Decoder) OptionsKey() string {
 	sb.WriteRune(d.Delimiter)
 	sb.WriteRune(d.Comment)
 	sb.WriteString(strconv.FormatBool(d.LazyQuotes))
+	sb.WriteString(d.TargetType)
 	return sb.String()
 }
 
 // Default is a Decoder in its default configuration.
 var Default = Decoder{
-	Delimiter: ',',
+	Delimiter:  ',',
+	TargetType: "slice",
 }
 
 // UnmarshalToMap will unmarshall data in format f into a new map. This is
@@ -122,7 +130,14 @@ func (d Decoder) Unmarshal(data []byte, f Format) (any, error) {
 	if len(data) == 0 {
 		switch f {
 		case CSV:
-			return make([][]string, 0), nil
+			switch d.TargetType {
+			case "map":
+				return make(map[string]any), nil
+			case "slice":
+				return make([][]string, 0), nil
+			default:
+				return nil, fmt.Errorf("invalid targetType: expected either slice or map, received %s", d.TargetType)
+			}
 		default:
 			return make(map[string]any), nil
 		}
@@ -232,10 +247,36 @@ func (d Decoder) unmarshalCSV(data []byte, v any) error {
 
 	switch vv := v.(type) {
 	case *any:
-		*vv = records
-	default:
-		return fmt.Errorf("CSV cannot be unmarshaled into %T", v)
+		switch d.TargetType {
+		case "map":
+			if len(records) < 2 {
+				return fmt.Errorf("cannot unmarshal CSV into %T: expected at least a header row and one data row", v)
+			}
+
+			seen := make(map[string]bool, len(records[0]))
+			for _, fieldName := range records[0] {
+				if seen[fieldName] {
+					return fmt.Errorf("cannot unmarshal CSV into %T: header row contains duplicate field names", v)
+				}
+				seen[fieldName] = true
+			}
 
+			sm := make([]map[string]string, len(records)-1)
+			for i, record := range records[1:] {
+				m := make(map[string]string, len(records[0]))
+				for j, col := range record {
+					m[records[0][j]] = col
+				}
+				sm[i] = m
+			}
+			*vv = sm
+		case "slice":
+			*vv = records
+		default:
+			return fmt.Errorf("cannot unmarshal CSV into %T: invalid targetType: expected either slice or map, received %s", v, d.TargetType)
+		}
+	default:
+		return fmt.Errorf("cannot unmarshal CSV into %T", v)
 	}
 
 	return nil
diff --git a/tpl/collections/where.go b/tpl/collections/where.go
index b15cfe781..ee49d0bbb 100644
--- a/tpl/collections/where.go
+++ b/tpl/collections/where.go
@@ -138,6 +138,9 @@ func (ns *Namespace) checkCondition(v, mv reflect.Value, op string) (bool, error
 		}
 
 		if mv.Len() == 0 {
+			if op == "not in" {
+				return true, nil
+			}
 			return false, nil
 		}
 
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index b9ef4b244..19882e36a 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -24,13 +24,13 @@ import (
 	"time"
 
 	"github.com/bep/lazycache"
-
 	"github.com/gohugoio/hugo/common/constants"
 	"github.com/gohugoio/hugo/common/hashing"
 	"github.com/gohugoio/hugo/identity"
+	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
 
 	"github.com/gohugoio/hugo/tpl"
-	texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate"
+	"github.com/gohugoio/hugo/tpl/tplimpl"
 
 	bp "github.com/gohugoio/hugo/bufferpool"
 	"github.com/gohugoio/hugo/deps"
@@ -109,7 +109,7 @@ func (c *contextWrapper) Set(in any) string {
 // A string if the partial is a text/template, or template.HTML when html/template.
 // Note that ctx is provided by Hugo, not the end user.
 func (ns *Namespace) Include(ctx context.Context, name string, contextList ...any) (any, error) {
-	res := ns.includWithTimeout(ctx, name, contextList...)
+	res := ns.include(ctx, name, contextList...)
 	if res.err != nil {
 		return nil, res.err
 	}
@@ -121,49 +121,36 @@ func (ns *Namespace) Include(ctx context.Context, name string, contextList ...an
 	return res.result, nil
 }
 
-func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataList ...any) includeResult {
+func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) includeResult {
+	v, err := ns.lookup(name)
+	if err != nil {
+		return includeResult{err: err}
+	}
+	return ns.doInclude(ctx, v, dataList...)
+}
+
+func (ns *Namespace) lookup(name string) (*tplimpl.TemplInfo, error) {
 	if strings.HasPrefix(name, "partials/") {
 		// This is most likely not what the user intended.
 		// This worked before Hugo 0.146.0.
 		ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Partial name %q starting with 'partials/' (as in {{ partial \"%s\"}}) is most likely not what you want. Before 0.146.0 we did a double lookup in this situation.", name, name)
 	}
-	// Create a new context with a timeout not connected to the incoming context.
-	timeoutCtx, cancel := context.WithTimeout(context.Background(), ns.deps.Conf.Timeout())
-	defer cancel()
-
-	res := make(chan includeResult, 1)
-
-	go func() {
-		res <- ns.include(ctx, name, dataList...)
-	}()
-
-	select {
-	case r := <-res:
-		return r
-	case <-timeoutCtx.Done():
-		err := timeoutCtx.Err()
-		if err == context.DeadlineExceeded {
-			//lint:ignore ST1005 end user message.
-			err = fmt.Errorf("partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting.", name, ns.deps.Conf.Timeout())
-		}
-		return includeResult{err: err}
+	v := ns.deps.TemplateStore.LookupPartial(name)
+	if v == nil {
+		return nil, fmt.Errorf("partial %q not found", name)
 	}
+	return v, nil
 }
 
 // include is a helper function that lookups and executes the named partial.
 // Returns the final template name and the rendered output.
-func (ns *Namespace) include(ctx context.Context, name string, dataList ...any) includeResult {
+func (ns *Namespace) doInclude(ctx context.Context, templ *tplimpl.TemplInfo, dataList ...any) includeResult {
 	var data any
 	if len(dataList) > 0 {
 		data = dataList[0]
 	}
-	v := ns.deps.TemplateStore.LookupPartial(name)
-	if v == nil {
-		return includeResult{err: fmt.Errorf("partial %q not found", name)}
-	}
 
-	templ := v
-	info := v.ParseInfo
+	info := templ.ParseInfo
 
 	var w io.Writer
 
@@ -212,6 +199,20 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
 		Variants: variants,
 	}
 	depsManagerIn := tpl.Context.GetDependencyManagerInCurrentScope(ctx)
+	ti, err := ns.lookup(name)
+	if err != nil {
+		return nil, err
+	}
+
+	if parent := tpl.Context.CurrentTemplate.Get(ctx); parent != nil {
+		for parent != nil {
+			if parent.CurrentTemplateInfoOps == ti {
+				// This will deadlock if we continue.
+				return nil, fmt.Errorf("circular call stack detected in partial %q", ti.Filename())
+			}
+			parent = parent.Parent
+		}
+	}
 
 	r, found, err := ns.cachedPartials.cache.GetOrCreate(key.Key(), func(string) (includeResult, error) {
 		var depsManagerShared identity.Manager
@@ -221,7 +222,7 @@ func (ns *Namespace) IncludeCached(ctx context.Context, name string, context any
 			depsManagerShared = identity.NewManager("partials")
 			ctx = tpl.Context.DependencyManagerScopedProvider.Set(ctx, depsManagerShared.(identity.DependencyManagerScopedProvider))
 		}
-		r := ns.includWithTimeout(ctx, key.Name, context)
+		r := ns.doInclude(ctx, ti, context)
 		if ns.deps.Conf.Watching() {
 			r.mangager = depsManagerShared
 		}
diff --git a/tpl/template.go b/tpl/template.go
index f69ae2210..877422123 100644
--- a/tpl/template.go
+++ b/tpl/template.go
@@ -160,6 +160,7 @@ type CurrentTemplateInfoCommonOps interface {
 // CurrentTemplateInfo as returned in templates.Current.
 type CurrentTemplateInfo struct {
 	Parent *CurrentTemplateInfo
+	Level  int
 	CurrentTemplateInfoOps
 }
 
diff --git a/tpl/tplimpl/embedded/templates/_shortcodes/instagram.html b/tpl/tplimpl/embedded/templates/_shortcodes/instagram.html
index 17188c6fd..804038e7d 100644
--- a/tpl/tplimpl/embedded/templates/_shortcodes/instagram.html
+++ b/tpl/tplimpl/embedded/templates/_shortcodes/instagram.html
@@ -3,7 +3,7 @@
   {{- with .Get 0 -}}
     {{- template "render-instagram" (dict "id" . "pc" $pc) -}}
   {{- else -}}
-    {{- errorf "The %q shortocde requires a single positional parameter, the ID of the Instagram post. See %s" .Name .Position -}}
+    {{- errorf "The %q shortcode requires a single positional parameter, the ID of the Instagram post. See %s" .Name .Position -}}
   {{- end -}}
 {{- end -}}
 
diff --git a/tpl/tplimpl/embedded/templates/_shortcodes/vimeo_simple.html b/tpl/tplimpl/embedded/templates/_shortcodes/vimeo_simple.html
index 11f19b1f6..86a6dfc9f 100644
--- a/tpl/tplimpl/embedded/templates/_shortcodes/vimeo_simple.html
+++ b/tpl/tplimpl/embedded/templates/_shortcodes/vimeo_simple.html
@@ -6,14 +6,14 @@
       {{- $ctx = merge $ctx (dict "id" . "class" ($.Get "class")) -}}
       {{- template "render-vimeo" $ctx -}}
     {{- else -}}
-      {{- errorf "The %q shortocde requires a single named parameter, the ID of the Vimeo video. See %s" .Name .Position -}}
+      {{- errorf "The %q shortcode requires a single named parameter, the ID of the Vimeo video. See %s" .Name .Position -}}
     {{- end -}}
   {{- else -}}
     {{- with .Get 0 -}}
       {{- $ctx = merge $ctx (dict "id" . "class" ($.Get 1)) -}}
       {{- template "render-vimeo" $ctx -}}
     {{- else -}}
-      {{- errorf "The %q shortocde requires a single positional parameter, the ID of the Vimeo video. See %s" .Name .Position -}}
+      {{- errorf "The %q shortcode requires a single positional parameter, the ID of the Vimeo video. See %s" .Name .Position -}}
     {{- end -}}
   {{- end -}}
 {{- end -}}
diff --git a/tpl/tplimpl/templatedescriptor.go b/tpl/tplimpl/templatedescriptor.go
index 8e4390fae..f65ad3943 100644
--- a/tpl/tplimpl/templatedescriptor.go
+++ b/tpl/tplimpl/templatedescriptor.go
@@ -64,7 +64,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool
 		return weightNoMatch
 	}
 
-	w := this.doCompare(category, isEmbedded, other)
+	w := this.doCompare(category, isEmbedded, s.opts.DefaultContentLanguage, other)
 
 	if w.w1 <= 0 {
 		if category == CategoryMarkup && (this.Variant1 == other.Variant1) && (this.Variant2 == other.Variant2 || this.Variant2 != "" && other.Variant2 == "") {
@@ -82,7 +82,7 @@ func (s descriptorHandler) compareDescriptors(category Category, isEmbedded bool
 }
 
 //lint:ignore ST1006 this vs other makes it easier to reason about.
-func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, other TemplateDescriptor) weight {
+func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, defaultContentLanguage string, other TemplateDescriptor) weight {
 	w := weightNoMatch
 
 	// HTML in plain text is OK, but not the other way around.
@@ -94,14 +94,10 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 	}
 
 	if other.LayoutFromTemplate != "" && other.LayoutFromTemplate != layoutAll {
-		if this.LayoutFromUser == "" {
+		if this.LayoutFromUser == "" || this.LayoutFromUser != other.LayoutFromTemplate {
 			if other.LayoutFromTemplate != this.LayoutFromTemplate {
 				return w
 			}
-		} else if isLayoutStandard(this.LayoutFromUser) {
-			if other.LayoutFromTemplate != this.LayoutFromUser {
-				return w
-			}
 		}
 	}
 
@@ -128,6 +124,10 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		// Continue.
 	}
 
+	if other.MediaType != this.MediaType {
+		return w
+	}
+
 	// One example of variant1 and 2 is for render codeblocks:
 	// variant1=codeblock, variant2=go (language).
 	if other.Variant1 != "" && other.Variant1 != this.Variant1 {
@@ -146,14 +146,15 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 	}
 
 	const (
-		weightKind           = 3 // page, home, section, taxonomy, term (and only those)
-		weightcustomLayout   = 4 // custom layout (mylayout, set in e.g. front matter)
-		weightLayoutStandard = 2 // standard layouts (single,list,all)
-		weightOutputFormat   = 2 // a configured output format (e.g. rss, html, json)
+		weightKind           = 5 // page, home, section, taxonomy, term (and only those)
+		weightcustomLayout   = 6 // custom layout (mylayout, set in e.g. front matter)
+		weightLayoutStandard = 4 // standard layouts (single,list)
+		weightLayoutAll      = 2 // the "all" layout
+		weightOutputFormat   = 4 // a configured output format (e.g. rss, html, json)
 		weightMediaType      = 1 // a configured media type (e.g. text/html, text/plain)
 		weightLang           = 1 // a configured language (e.g. en, nn, fr, ...)
-		weightVariant1       = 4 // currently used for render hooks, e.g. "link", "image"
-		weightVariant2       = 2 // currently used for render hooks, e.g. the language "go" in code blocks.
+		weightVariant1       = 6 // currently used for render hooks, e.g. "link", "image"
+		weightVariant2       = 4 // currently used for render hooks, e.g. the language "go" in code blocks.
 
 		// We will use the values for group 2 and 3
 		// if the distance up to the template is shorter than
@@ -177,10 +178,12 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		w.w2 = weight2Group1
 	}
 
-	if this.LayoutFromUser == "" && other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate || other.LayoutFromTemplate == layoutAll) {
+	if other.LayoutFromTemplate != "" && (other.LayoutFromTemplate == this.LayoutFromTemplate) {
 		w.w1 += weightLayoutStandard
 		w.w2 = weight2Group1
-
+	} else if other.LayoutFromTemplate == layoutAll {
+		w.w1 += weightLayoutAll
+		w.w2 = weight2Group1
 	}
 
 	// LayoutCustom is only set in this (usually from Page.Layout).
@@ -189,7 +192,7 @@ func (this TemplateDescriptor) doCompare(category Category, isEmbedded bool, oth
 		w.w2 = weight2Group2
 	}
 
-	if other.Lang != "" && other.Lang == this.Lang {
+	if (other.Lang != "" && other.Lang == this.Lang) || (other.Lang == "" && this.Lang == defaultContentLanguage) {
 		w.w1 += weightLang
 		w.w3 += weight3
 	}
diff --git a/tpl/tplimpl/templates.go b/tpl/tplimpl/templates.go
index 4c3ad3be1..19de48e38 100644
--- a/tpl/tplimpl/templates.go
+++ b/tpl/tplimpl/templates.go
@@ -44,7 +44,16 @@ var embeddedTemplatesAliases = map[string][]string{
 	"_shortcodes/twitter.html": {"_shortcodes/tweet.html"},
 }
 
-func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
+func (s *TemplateStore) parseTemplate(ti *TemplInfo) error {
+	err := s.tns.doParseTemplate(ti)
+	if err != nil {
+		return s.addFileContext(ti, "parse of template failed", err)
+	}
+
+	return err
+}
+
+func (t *templateNamespace) doParseTemplate(ti *TemplInfo) error {
 	if !ti.noBaseOf || ti.category == CategoryBaseof {
 		// Delay parsing until we have the base template.
 		return nil
@@ -99,7 +108,14 @@ func (t *templateNamespace) parseTemplate(ti *TemplInfo) error {
 					return err
 				}
 			}
+		}
 
+		// Issue #13599.
+		if ti.category == CategoryPartial && ti.Fi != nil && ti.Fi.Meta().PathInfo.Section() == "partials" {
+			aliasName := strings.TrimPrefix(name, "_")
+			if _, err := prototype.AddParseTree(aliasName, templ.(*htmltemplate.Template).Tree); err != nil {
+				return err
+			}
 		}
 	}
 
diff --git a/tpl/tplimpl/templatetransform.go b/tpl/tplimpl/templatetransform.go
index cba4c6584..eca9fdad1 100644
--- a/tpl/tplimpl/templatetransform.go
+++ b/tpl/tplimpl/templatetransform.go
@@ -175,6 +175,9 @@ func (c *templateTransformContext) applyTransformations(n parse.Node) (bool, err
 		}
 
 	case *parse.CommandNode:
+		if x == nil {
+			return true, nil
+		}
 		c.collectInner(x)
 		keep := c.collectReturnNode(x)
 


Test patch:
diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
index fd1590c73..a6194e756 100644
--- a/common/paths/pathparser_test.go
+++ b/common/paths/pathparser_test.go
@@ -171,22 +171,25 @@ func TestParse(t *testing.T) {
 			"/a/b.a.b.no.txt",
 			func(c *qt.C, p *Path) {
 				c.Assert(p.Name(), qt.Equals, "b.a.b.no.txt")
-				c.Assert(p.NameNoIdentifier(), qt.Equals, "b")
+				c.Assert(p.NameNoIdentifier(), qt.Equals, "b.a.b")
 				c.Assert(p.NameNoLang(), qt.Equals, "b.a.b.txt")
-				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "b", "a", "b"})
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
 				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"b", "a", "b"})
-				c.Assert(p.Base(), qt.Equals, "/a/b.txt")
-				c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.txt")
+				c.Assert(p.Base(), qt.Equals, "/a/b.a.b.txt")
+				c.Assert(p.BaseNoLeadingSlash(), qt.Equals, "a/b.a.b.txt")
 				c.Assert(p.Path(), qt.Equals, "/a/b.a.b.no.txt")
-				c.Assert(p.PathNoLang(), qt.Equals, "/a/b.txt")
+				c.Assert(p.PathNoLang(), qt.Equals, "/a/b.a.b.txt")
 				c.Assert(p.Ext(), qt.Equals, "txt")
-				c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b")
+				c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b.a.b")
 			},
 		},
 		{
 			"Home branch cundle",
 			"/_index.md",
 			func(c *qt.C, p *Path) {
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
+				c.Assert(p.IsBranchBundle(), qt.IsTrue)
+				c.Assert(p.IsBundle(), qt.IsTrue)
 				c.Assert(p.Base(), qt.Equals, "/")
 				c.Assert(p.BaseReTyped("foo"), qt.Equals, "/foo")
 				c.Assert(p.Path(), qt.Equals, "/_index.md")
@@ -206,7 +209,8 @@ func TestParse(t *testing.T) {
 				c.Assert(p.ContainerDir(), qt.Equals, "")
 				c.Assert(p.Dir(), qt.Equals, "/a")
 				c.Assert(p.Ext(), qt.Equals, "md")
-				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "index"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"index"})
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md"})
 				c.Assert(p.IsBranchBundle(), qt.IsFalse)
 				c.Assert(p.IsBundle(), qt.IsTrue)
 				c.Assert(p.IsLeafBundle(), qt.IsTrue)
@@ -228,7 +232,7 @@ func TestParse(t *testing.T) {
 				c.Assert(p.ContainerDir(), qt.Equals, "/a")
 				c.Assert(p.Dir(), qt.Equals, "/a/b")
 				c.Assert(p.Ext(), qt.Equals, "md")
-				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "index"})
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
 				c.Assert(p.IsBranchBundle(), qt.IsFalse)
 				c.Assert(p.IsBundle(), qt.IsTrue)
 				c.Assert(p.IsLeafBundle(), qt.IsTrue)
@@ -250,7 +254,7 @@ func TestParse(t *testing.T) {
 				c.Assert(p.Container(), qt.Equals, "b")
 				c.Assert(p.ContainerDir(), qt.Equals, "/a")
 				c.Assert(p.Ext(), qt.Equals, "md")
-				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no", "_index"})
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"md", "no"})
 				c.Assert(p.IsBranchBundle(), qt.IsTrue)
 				c.Assert(p.IsBundle(), qt.IsTrue)
 				c.Assert(p.IsLeafBundle(), qt.IsFalse)
@@ -289,7 +293,7 @@ func TestParse(t *testing.T) {
 			func(c *qt.C, p *Path) {
 				c.Assert(p.Base(), qt.Equals, "/a/b/index.txt")
 				c.Assert(p.Ext(), qt.Equals, "txt")
-				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no", "index"})
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"txt", "no"})
 				c.Assert(p.IsLeafBundle(), qt.IsFalse)
 				c.Assert(p.PathNoIdentifier(), qt.Equals, "/a/b/index")
 			},
@@ -372,7 +376,7 @@ func TestParse(t *testing.T) {
 	}
 	for _, test := range tests {
 		c.Run(test.name, func(c *qt.C) {
-			if test.name != "Basic Markdown file" {
+			if test.name != "Home branch cundle" {
 				// return
 			}
 			test.assert(c, testParser.Parse(files.ComponentFolderContent, test.path))
@@ -401,10 +405,58 @@ func TestParseLayouts(t *testing.T) {
 			"/list.no.html",
 			func(c *qt.C, p *Path) {
 				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "list"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
 				c.Assert(p.Base(), qt.Equals, "/list.html")
 				c.Assert(p.Lang(), qt.Equals, "no")
 			},
 		},
+		{
+			"Kind",
+			"/section.no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+				c.Assert(p.Base(), qt.Equals, "/section.html")
+				c.Assert(p.Lang(), qt.Equals, "no")
+			},
+		},
+		{
+			"Layout",
+			"/list.section.no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Layout(), qt.Equals, "list")
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+				c.Assert(p.Base(), qt.Equals, "/list.html")
+				c.Assert(p.Lang(), qt.Equals, "no")
+			},
+		},
+		{
+			"Layout multiple",
+			"/maylayout.list.section.no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Layout(), qt.Equals, "maylayout")
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "no", "section", "list", "maylayout"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
+				c.Assert(p.Base(), qt.Equals, "/maylayout.html")
+				c.Assert(p.Lang(), qt.Equals, "no")
+			},
+		},
+		{
+			"Layout shortcode",
+			"/_shortcodes/myshort.list.no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Layout(), qt.Equals, "list")
+			},
+		},
+		{
+			"Layout baseof",
+			"/baseof.list.no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Layout(), qt.Equals, "list")
+			},
+		},
 		{
 			"Lang and output format",
 			"/list.no.amp.not.html",
@@ -429,6 +481,20 @@ func TestParseLayouts(t *testing.T) {
 				c.Assert(p.OutputFormat(), qt.Equals, "html")
 			},
 		},
+		{
+			"Shortcode with layout",
+			"/_shortcodes/myshortcode.list.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Base(), qt.Equals, "/_shortcodes/myshortcode.html")
+				c.Assert(p.Type(), qt.Equals, TypeShortcode)
+				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "list", "myshortcode"})
+				c.Assert(p.PathNoIdentifier(), qt.Equals, "/_shortcodes/myshortcode")
+				c.Assert(p.PathBeforeLangAndOutputFormatAndExt(), qt.Equals, "/_shortcodes/myshortcode.list")
+				c.Assert(p.Lang(), qt.Equals, "")
+				c.Assert(p.Kind(), qt.Equals, "")
+				c.Assert(p.OutputFormat(), qt.Equals, "html")
+			},
+		},
 		{
 			"Sub dir",
 			"/pages/home.html",
@@ -445,7 +511,7 @@ func TestParseLayouts(t *testing.T) {
 			"/pages/baseof.list.section.fr.amp.html",
 			func(c *qt.C, p *Path) {
 				c.Assert(p.Identifiers(), qt.DeepEquals, []string{"html", "amp", "fr", "section", "list", "baseof"})
-				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{"list"})
+				c.Assert(p.IdentifiersUnknown(), qt.DeepEquals, []string{})
 				c.Assert(p.Kind(), qt.Equals, kinds.KindSection)
 				c.Assert(p.Lang(), qt.Equals, "fr")
 				c.Assert(p.OutputFormat(), qt.Equals, "amp")
@@ -501,6 +567,9 @@ func TestParseLayouts(t *testing.T) {
 
 	for _, test := range tests {
 		c.Run(test.name, func(c *qt.C) {
+			if test.name != "Baseof" {
+				// return
+			}
 			test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
 		})
 	}
diff --git a/common/paths/paths_integration_test.go b/common/paths/paths_integration_test.go
index 62d40f527..f5ea3066a 100644
--- a/common/paths/paths_integration_test.go
+++ b/common/paths/paths_integration_test.go
@@ -78,3 +78,26 @@ disablePathToLower = true
 	b.AssertFileContent("public/en/mysection/mybundle/index.html", "en|Single")
 	b.AssertFileContent("public/fr/MySection/MyBundle/index.html", "fr|Single")
 }
+
+func TestIssue13596(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+-- content/p1/index.md --
+---
+title: p1
+---
+-- content/p1/a.1.txt --
+-- content/p1/a.2.txt --
+-- layouts/all.html --
+{{ range .Resources.Match "*" }}{{ .Name }}|{{ end }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/p1/index.html", "a.1.txt|a.2.txt|")
+	b.AssertFileExists("public/p1/a.1.txt", true)
+	b.AssertFileExists("public/p1/a.2.txt", true) // fails
+}
diff --git a/hugolib/collections_test.go b/hugolib/collections_test.go
index a8c817bec..f62d4c604 100644
--- a/hugolib/collections_test.go
+++ b/hugolib/collections_test.go
@@ -39,6 +39,8 @@ title: "Page"
 `)
 	b.CreateSites().Build(BuildCfg{})
 
+	// b.H.TemplateStore.PrintDebug("", tplimpl.CategoryLayout, os.Stdout)
+
 	c.Assert(len(b.H.Sites), qt.Equals, 1)
 	c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 2)
 
diff --git a/hugolib/content_map_test.go b/hugolib/content_map_test.go
index c24790495..f72862150 100644
--- a/hugolib/content_map_test.go
+++ b/hugolib/content_map_test.go
@@ -517,7 +517,7 @@ func TestHTMLNotContent(t *testing.T) {
 -- hugo.toml.temp --
 [contentTypes]
 [contentTypes."text/markdown"]
-# Emopty for now.
+# Empty for now.
 -- hugo.yaml.temp --
 contentTypes:
   text/markdown: {}
diff --git a/hugolib/content_render_hooks_test.go b/hugolib/content_render_hooks_test.go
index 56ae0a052..c4e15a5c6 100644
--- a/hugolib/content_render_hooks_test.go
+++ b/hugolib/content_render_hooks_test.go
@@ -464,8 +464,6 @@ title: "Home"
 `
 	b := Test(t, files)
 
-	// b.DebugPrint("", tplimpl.CategoryShortcode)
-
 	b.AssertFileContentExact("public/index.xml", "My shortcode XML.")
 	b.AssertFileContentExact("public/index.html", "My shortcode HTML.")
 	s := b.H.Sites[0]
diff --git a/hugolib/integrationtest_builder.go b/hugolib/integrationtest_builder.go
index 4ea6f420d..3c2f1ad74 100644
--- a/hugolib/integrationtest_builder.go
+++ b/hugolib/integrationtest_builder.go
@@ -918,7 +918,7 @@ type IntegrationTestConfig struct {
 
 	// The files to use on txtar format, see
 	// https://pkg.go.dev/golang.org/x/exp/cmd/txtar
-	// There are some conentions used in this test setup.
+	// There are some contentions used in this test setup.
 	// - §§§ can be used to wrap code fences.
 	// - §§ can be used to wrap multiline strings.
 	// - filenames prefixed with sourcefilename: will be read from the file system relative to the current dir.
diff --git a/hugolib/site_test.go b/hugolib/site_test.go
index a9fe977cf..4d68602e5 100644
--- a/hugolib/site_test.go
+++ b/hugolib/site_test.go
@@ -978,7 +978,7 @@ func TestRefLinking(t *testing.T) {
 		{".", "", true, "/level2/level3/"},
 		{"./", "", true, "/level2/level3/"},
 
-		{"embedded.dot.md", "", true, "/level2/level3/embedded/"},
+		{"embedded.dot.md", "", true, "/level2/level3/embedded.dot/"},
 
 		// test empty link, as well as fragment only link
 		{"", "", true, ""},
diff --git a/hugolib/sitemap_test.go b/hugolib/sitemap_test.go
index 1c2642468..922ecbc12 100644
--- a/hugolib/sitemap_test.go
+++ b/hugolib/sitemap_test.go
@@ -139,7 +139,7 @@ weight = 1
 languageName = "English"
 [languages.nn]
 weight = 2
--- layouts/_default/list.xml --
+-- layouts/list.xml --
 Site: {{ .Site.Title }}|
 -- layouts/home --
 Home.
diff --git a/tpl/collections/collections_integration_test.go b/tpl/collections/collections_integration_test.go
index cc60770f9..b60aaea87 100644
--- a/tpl/collections/collections_integration_test.go
+++ b/tpl/collections/collections_integration_test.go
@@ -278,3 +278,23 @@ disableKinds = ['rss','sitemap', 'taxonomy', 'term', 'page']
 
 	b.AssertFileContentExact("public/index.html", "0: /a3_b1.html\n\n1: /b2.html\n\n2: /a1.html\n\n3: /a2.html\n$")
 }
+
+// Issue 13621.
+func TestWhereNotInEmptySlice(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/home.html --
+{{- $pages := where site.RegularPages "Kind" "not in" (slice) -}}
+Len: {{  $pages | len }}|
+-- layouts/all.html --
+All|{{ .Title }}|
+-- content/p1.md --
+
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/index.html", "Len: 1|")
+}
diff --git a/tpl/collections/where_test.go b/tpl/collections/where_test.go
index 60f97e607..ecf748f93 100644
--- a/tpl/collections/where_test.go
+++ b/tpl/collections/where_test.go
@@ -761,6 +761,7 @@ func TestCheckCondition(t *testing.T) {
 			expect{true, false},
 		},
 		{reflect.ValueOf(123), reflect.ValueOf([]int{45, 678}), "not in", expect{true, false}},
+		{reflect.ValueOf(123), reflect.ValueOf([]int{}), "not in", expect{true, false}},
 		{reflect.ValueOf("foo"), reflect.ValueOf([]string{"bar", "baz"}), "not in", expect{true, false}},
 		{
 			reflect.ValueOf(time.Date(2015, time.May, 26, 19, 18, 56, 12345, time.UTC)),
diff --git a/tpl/partials/partials_integration_test.go b/tpl/partials/partials_integration_test.go
index 6fab3abd8..0fa47104d 100644
--- a/tpl/partials/partials_integration_test.go
+++ b/tpl/partials/partials_integration_test.go
@@ -256,7 +256,6 @@ func TestIncludeTimeout(t *testing.T) {
 	files := `
 -- config.toml --
 baseURL = 'http://example.com/'
-timeout = '200ms'
 -- layouts/index.html --
 {{ partials.Include "foo.html" . }}
 -- layouts/partials/foo.html --
@@ -271,7 +270,7 @@ timeout = '200ms'
 	).BuildE()
 
 	b.Assert(err, qt.Not(qt.IsNil))
-	b.Assert(err.Error(), qt.Contains, "timed out")
+	b.Assert(err.Error(), qt.Contains, "maximum template call stack size exceeded")
 }
 
 func TestIncludeCachedTimeout(t *testing.T) {
@@ -284,6 +283,8 @@ timeout = '200ms'
 -- layouts/index.html --
 {{ partials.IncludeCached "foo.html" . }}
 -- layouts/partials/foo.html --
+{{ partialCached "bar.html" . }}
+-- layouts/partials/bar.html --
 {{ partialCached "foo.html" . }}
   `
 
@@ -295,7 +296,7 @@ timeout = '200ms'
 	).BuildE()
 
 	b.Assert(err, qt.Not(qt.IsNil))
-	b.Assert(err.Error(), qt.Contains, "timed out")
+	b.Assert(err.Error(), qt.Contains, `error calling partialCached: circular call stack detected in partial`)
 }
 
 // See Issue #10789
diff --git a/tpl/templates/templates_integration_test.go b/tpl/templates/templates_integration_test.go
index 93922b4c4..d16333ed4 100644
--- a/tpl/templates/templates_integration_test.go
+++ b/tpl/templates/templates_integration_test.go
@@ -1,4 +1,4 @@
-// Copyright 2024 The Hugo Authors. All rights reserved.
+// Copyright 2025 The Hugo Authors. All rights reserved.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ import (
 	"path/filepath"
 	"testing"
 
+	qt "github.com/frankban/quicktest"
 	"github.com/gohugoio/hugo/hugolib"
 )
 
@@ -229,3 +230,72 @@ layouts/section/section.html
 	b := hugolib.Test(t, files)
 	b.AssertFileContent("public/mysection/index.html", "layouts/section/section.html")
 }
+
+func TestErrorMessageParseError(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/home.html --
+Line 1.
+Line 2. {{ foo }} <- this func does not exist.
+Line 3.
+`
+
+	b, err := hugolib.TestE(t, files)
+	b.Assert(err, qt.IsNotNil)
+	b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"/layouts/home.html:2:1": parse of template failed: template: home.html:2: function "foo" not defined`))
+}
+
+func TestErrorMessageExecuteError(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/home.html --
+Line 1.
+Line 2. {{ .Foo }} <- this method does not exist.
+Line 3.
+`
+
+	b, err := hugolib.TestE(t, files)
+	b.Assert(err, qt.IsNotNil)
+	b.Assert(err.Error(), qt.Contains, filepath.FromSlash(` "/layouts/home.html:2:11": execute of template failed`))
+}
+
+func TestPartialReturnPanicIssue13600(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/home.html --
+Partial: {{ partial "p1.html" . }}
+-- layouts/_partials/p1.html --
+P1.
+{{ return ( delimit . ", " ) | string }}
+`
+
+	b, err := hugolib.TestE(t, files)
+	b.Assert(err, qt.IsNotNil)
+	b.Assert(err.Error(), qt.Contains, "wrong number of args for string: want 1 got 0")
+}
+
+func TestPartialWithoutSuffixIssue13601(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/home.html --
+P1: {{ partial "p1" . }}
+P2: {{ partial "p2" . }}
+-- layouts/_partials/p1 --
+P1.
+-- layouts/_partials/p2 --
+P2.
+{{ return "foo bar" }}
+
+`
+
+	b := hugolib.Test(t, files)
+	b.AssertFileContent("public/index.html", "P1: P1.\nP2: foo bar")
+}
diff --git a/tpl/tplimpl/legacy_integration_test.go b/tpl/tplimpl/legacy_integration_test.go
new file mode 100644
index 000000000..a96e35fca
--- /dev/null
+++ b/tpl/tplimpl/legacy_integration_test.go
@@ -0,0 +1,38 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Portions Copyright The Go Authors.
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tplimpl_test
+
+import (
+	"testing"
+
+	"github.com/gohugoio/hugo/hugolib"
+)
+
+func TestLegacyPartialIssue13599(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/partials/mypartial.html --
+Mypartial.
+-- layouts/_default/index.html --
+mypartial:   {{ template "partials/mypartial.html" . }}
+
+`
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/index.html", "Mypartial.")
+}
diff --git a/tpl/tplimpl/templatestore.go b/tpl/tplimpl/templatestore.go
index 8483b7df0..dba897608 100644
--- a/tpl/tplimpl/templatestore.go
+++ b/tpl/tplimpl/templatestore.go
@@ -1,3 +1,18 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Portions Copyright The Go Authors.
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package tplimpl
 
 import (
@@ -107,6 +122,7 @@ func NewStore(opts StoreOptions, siteOpts SiteOptions) (*TemplateStore, error) {
 		treeMain:            doctree.NewSimpleTree[map[nodeKey]*TemplInfo](),
 		treeShortcodes:      doctree.NewSimpleTree[map[string]map[TemplateDescriptor]*TemplInfo](),
 		templatesByPath:     maps.NewCache[string, *TemplInfo](),
+		shortcodesByName:    maps.NewCache[string, *TemplInfo](),
 		cacheLookupPartials: maps.NewCache[string, *TemplInfo](),
 
 		// Note that the funcs passed below is just for name validation.
@@ -404,9 +420,10 @@ type TemplateStore struct {
 	siteOpts   SiteOptions
 	htmlFormat output.Format
 
-	treeMain        *doctree.SimpleTree[map[nodeKey]*TemplInfo]
-	treeShortcodes  *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
-	templatesByPath *maps.Cache[string, *TemplInfo]
+	treeMain         *doctree.SimpleTree[map[nodeKey]*TemplInfo]
+	treeShortcodes   *doctree.SimpleTree[map[string]map[TemplateDescriptor]*TemplInfo]
+	templatesByPath  *maps.Cache[string, *TemplInfo]
+	shortcodesByName *maps.Cache[string, *TemplInfo]
 
 	dh descriptorHandler
 
@@ -467,20 +484,31 @@ func (t *TemplateStore) ExecuteWithContext(ctx context.Context, ti *TemplInfo, w
 
 	templ := ti.Template
 
+	parent := tpl.Context.CurrentTemplate.Get(ctx)
+	var level int
+	if parent != nil {
+		level = parent.Level + 1
+	}
 	currentTi := &tpl.CurrentTemplateInfo{
-		Parent:                 tpl.Context.CurrentTemplate.Get(ctx),
+		Parent:                 parent,
+		Level:                  level,
 		CurrentTemplateInfoOps: ti,
 	}
 
 	ctx = tpl.Context.CurrentTemplate.Set(ctx, currentTi)
 
+	const levelThreshold = 999
+	if level > levelThreshold {
+		return fmt.Errorf("maximum template call stack size exceeded in %q", ti.Filename())
+	}
+
 	if t.opts.Metrics != nil {
 		defer t.opts.Metrics.MeasureSince(templ.Name(), time.Now())
 	}
 
 	execErr := t.storeSite.executer.ExecuteWithContext(ctx, ti, wr, data)
 	if execErr != nil {
-		return t.addFileContext(ti, execErr)
+		return t.addFileContext(ti, "execute of template failed", execErr)
 	}
 	return nil
 }
@@ -547,20 +575,36 @@ func (s *TemplateStore) LookupPagesLayout(q TemplateQuery) *TemplInfo {
 
 func (s *TemplateStore) LookupPartial(pth string) *TemplInfo {
 	ti, _ := s.cacheLookupPartials.GetOrCreate(pth, func() (*TemplInfo, error) {
-		d := s.templateDescriptorFromPath(pth)
-		desc := d.Desc
-		if desc.LayoutFromTemplate != "" {
-			panic("shortcode template descriptor must not have a layout")
+		pi := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth).ForType(paths.TypePartial)
+		k1, _, _, desc, err := s.toKeyCategoryAndDescriptor(pi)
+		if err != nil {
+			return nil, err
+		}
+		if desc.OutputFormat == "" && desc.MediaType == "" {
+			// Assume HTML.
+			desc.OutputFormat = s.htmlFormat.Name
+			desc.MediaType = s.htmlFormat.MediaType.Type
+			desc.IsPlainText = s.htmlFormat.IsPlainText
 		}
+
 		best := s.getBest()
 		defer s.putBest(best)
-		s.findBestMatchGet(s.key(path.Join(containerPartials, d.Path)), CategoryPartial, nil, desc, best)
+		s.findBestMatchGet(s.key(path.Join(containerPartials, k1)), CategoryPartial, nil, desc, best)
 		return best.templ, nil
 	})
 
 	return ti
 }
 
+func (s *TemplateStore) LookupShortcodeByName(name string) *TemplInfo {
+	name = strings.ToLower(name)
+	ti, _ := s.shortcodesByName.Get(name)
+	if ti == nil {
+		return nil
+	}
+	return ti
+}
+
 func (s *TemplateStore) LookupShortcode(q TemplateQuery) *TemplInfo {
 	q.init()
 	k1 := s.key(q.Path)
@@ -610,7 +654,7 @@ func (s *TemplateStore) PrintDebug(prefix string, category Category, w io.Writer
 			return
 		}
 		s := strings.ReplaceAll(strings.TrimSpace(vv.content), "\n", " ")
-		ts := fmt.Sprintf("kind: %q layout: %q content: %.30s", vv.D.Kind, vv.D.LayoutFromTemplate, s)
+		ts := fmt.Sprintf("kind: %q layout: %q lang: %q content: %.30s", vv.D.Kind, vv.D.LayoutFromTemplate, vv.D.Lang, s)
 		fmt.Fprintf(w, "%s%s %s\n", strings.Repeat(" ", level), key, ts)
 	}
 	s.treeMain.WalkPrefix(prefix, func(key string, v map[nodeKey]*TemplInfo) (bool, error) {
@@ -807,7 +851,7 @@ func (t *TemplateStore) addDeferredTemplate(owner *TemplInfo, name string, n *pa
 	return nil
 }
 
-func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
+func (s *TemplateStore) addFileContext(ti *TemplInfo, what string, inerr error) error {
 	if ti.Fi == nil {
 		return inerr
 	}
@@ -839,25 +883,27 @@ func (s *TemplateStore) addFileContext(ti *TemplInfo, inerr error) error {
 		fe := herrors.NewFileErrorFromName(inErr, fi.Meta().Filename)
 		fe.UpdateContent(f, lineMatcher)
 
-		if !fe.ErrorContext().Position.IsValid() {
-			return inErr, false
-		}
-		return fe, true
+		return fe, fe.ErrorContext().Position.IsValid()
 	}
 
-	inerr = fmt.Errorf("execute of template failed: %w", inerr)
+	inerr = fmt.Errorf("%s: %w", what, inerr)
 
-	if err, ok := checkFilename(ti.Fi, inerr); ok {
-		return err
+	var (
+		currentErr error
+		ok         bool
+	)
+
+	if currentErr, ok = checkFilename(ti.Fi, inerr); ok {
+		return currentErr
 	}
 
 	if ti.base != nil {
-		if err, ok := checkFilename(ti.base.Fi, inerr); ok {
-			return err
+		if currentErr, ok = checkFilename(ti.base.Fi, inerr); ok {
+			return currentErr
 		}
 	}
 
-	return inerr
+	return currentErr
 }
 
 func (s *TemplateStore) extractIdentifiers(line string) []string {
@@ -885,7 +931,7 @@ func (s *TemplateStore) extractInlinePartials() error {
 			name := templ.Name()
 			if !paths.HasExt(name) {
 				// Assume HTML. This in line with how the lookup works.
-				name = name + ".html"
+				name = name + s.htmlFormat.MediaType.FirstSuffix.FullSuffix
 			}
 			if !strings.HasPrefix(name, "_") {
 				name = "_" + name
@@ -1022,6 +1068,7 @@ func (s *TemplateStore) insertShortcode(pi *paths.Path, fi hugofs.FileMetaInfo,
 
 	m1[d] = ti
 
+	s.shortcodesByName.Set(k2, ti)
 	s.setTemplateByPath(pi.Path(), ti)
 
 	if fi != nil {
@@ -1063,6 +1110,12 @@ func (s *TemplateStore) insertTemplate2(
 		panic("category not set")
 	}
 
+	if category == CategoryPartial && d.OutputFormat == "" && d.MediaType == "" {
+		// See issue #13601.
+		d.OutputFormat = s.htmlFormat.Name
+		d.MediaType = s.htmlFormat.MediaType.Type
+	}
+
 	m := tree.Get(key)
 	nk := nodeKey{c: category, d: d}
 
@@ -1073,7 +1126,7 @@ func (s *TemplateStore) insertTemplate2(
 
 	if !replace {
 		if v, found := m[nk]; found {
-			if len(pi.IdentifiersUnknown()) >= len(v.PathInfo.IdentifiersUnknown()) {
+			if len(pi.Identifiers()) >= len(v.PathInfo.Identifiers()) {
 				// e.g. /pages/home.foo.html and  /pages/home.html where foo may be a valid language name in another site.
 				return nil, nil
 			}
@@ -1208,7 +1261,10 @@ func (s *TemplateStore) insertTemplates(include func(fi hugofs.FileMetaInfo) boo
 			)
 
 			base := piOrig.PathBeforeLangAndOutputFormatAndExt()
-			identifiers := pi.IdentifiersUnknown()
+			identifiers := []string{}
+			if pi.Layout() != "" {
+				identifiers = append(identifiers, pi.Layout())
+			}
 			if pi.Kind() != "" {
 				identifiers = append(identifiers, pi.Kind())
 			}
@@ -1374,7 +1430,7 @@ func (s *TemplateStore) parseTemplates() error {
 				if vv.state == processingStateTransformed {
 					continue
 				}
-				if err := s.tns.parseTemplate(vv); err != nil {
+				if err := s.parseTemplate(vv); err != nil {
 					return err
 				}
 			}
@@ -1394,7 +1450,7 @@ func (s *TemplateStore) parseTemplates() error {
 						// The regular expression used to detect if a template needs a base template has some
 						// rare false positives. Assume we don't need one.
 						vv.noBaseOf = true
-						if err := s.tns.parseTemplate(vv); err != nil {
+						if err := s.parseTemplate(vv); err != nil {
 							return err
 						}
 						continue
@@ -1423,7 +1479,7 @@ func (s *TemplateStore) parseTemplates() error {
 				if vvv.state == processingStateTransformed {
 					continue
 				}
-				if err := s.tns.parseTemplate(vvv); err != nil {
+				if err := s.parseTemplate(vvv); err != nil {
 					return err
 				}
 			}
@@ -1451,43 +1507,6 @@ type PathTemplateDescriptor struct {
 	Desc TemplateDescriptor
 }
 
-// templateDescriptorFromPath returns a template descriptor from the given path.
-// This is currently used in partial lookups only.
-func (s *TemplateStore) templateDescriptorFromPath(pth string) PathTemplateDescriptor {
-	var (
-		mt media.Type
-		of output.Format
-	)
-
-	// Common cases.
-	dotCount := strings.Count(pth, ".")
-	if dotCount <= 1 {
-		if dotCount == 0 {
-			// Asume HTML.
-			of, mt = s.resolveOutputFormatAndOrMediaType("html", "")
-		} else {
-			pth = strings.TrimPrefix(pth, "/")
-			ext := path.Ext(pth)
-			pth = strings.TrimSuffix(pth, ext)
-			ext = ext[1:]
-			of, mt = s.resolveOutputFormatAndOrMediaType("", ext)
-		}
-	} else {
-		path := s.opts.PathParser.Parse(files.ComponentFolderLayouts, pth)
-		pth = path.PathNoIdentifier()
-		of, mt = s.resolveOutputFormatAndOrMediaType(path.OutputFormat(), path.Ext())
-	}
-
-	return PathTemplateDescriptor{
-		Path: pth,
-		Desc: TemplateDescriptor{
-			OutputFormat: of.Name,
-			MediaType:    mt.Type,
-			IsPlainText:  of.IsPlainText,
-		},
-	}
-}
-
 // resolveOutputFormatAndOrMediaType resolves the output format and/or media type
 // based on the given output format suffix and media type suffix.
 // Either of the suffixes can be empty, and the function will try to find a match
@@ -1560,24 +1579,12 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
 	outputFormat, mediaType := s.resolveOutputFormatAndOrMediaType(p.OutputFormat(), p.Ext())
 	nameNoIdentifier := p.NameNoIdentifier()
 
-	var layout string
-	unknownids := p.IdentifiersUnknown()
-	if p.Type() == paths.TypeShortcode {
-		if len(unknownids) > 1 {
-			// The name is the last identifier.
-			layout = unknownids[len(unknownids)-2]
-		}
-	} else if len(unknownids) > 0 {
-		// Pick the last, closest to the base name.
-		layout = unknownids[len(unknownids)-1]
-	}
-
 	d := TemplateDescriptor{
 		Lang:               p.Lang(),
 		OutputFormat:       p.OutputFormat(),
 		MediaType:          mediaType.Type,
 		Kind:               p.Kind(),
-		LayoutFromTemplate: layout,
+		LayoutFromTemplate: p.Layout(),
 		IsPlainText:        outputFormat.IsPlainText,
 	}
 
@@ -1617,6 +1624,7 @@ func (s *TemplateStore) toKeyCategoryAndDescriptor(p *paths.Path) (string, strin
 
 	if category == CategoryShortcode {
 		k1 = p.PathNoIdentifier()
+
 		parts := strings.Split(k1, "/"+containerShortcodes+"/")
 		k1 = parts[0]
 		if len(parts) > 1 {
@@ -1690,6 +1698,7 @@ func (s *TemplateStore) transformTemplates() error {
 			continue
 		}
 		if !vv.noBaseOf {
+			// TODO(bep) I don't think this branch is ever called.
 			for vvv := range vv.BaseVariantsSeq() {
 				tctx, err := applyTemplateTransformers(vvv.Template, lookup)
 				if err != nil {
@@ -1825,7 +1834,9 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
 		// Anything is better than nothing.
 		return true
 	}
+
 	if w.w1 <= 0 {
+
 		if best.w.w1 <= 0 {
 			return ti.PathInfo.Path() < best.templ.PathInfo.Path()
 		}
@@ -1868,11 +1879,7 @@ func (best *bestMatch) isBetter(w weight, ti *TemplInfo) bool {
 			return true
 		}
 
-		if ti.D.LayoutFromTemplate != "" && best.desc.LayoutFromTemplate != "" {
-			return ti.D.LayoutFromTemplate != layoutAll
-		}
-
-		return w.distance < best.w.distance || ti.PathInfo.Path() < best.templ.PathInfo.Path()
+		return ti.PathInfo.Path() < best.templ.PathInfo.Path()
 	}
 
 	return true
diff --git a/tpl/tplimpl/templatestore_integration_test.go b/tpl/tplimpl/templatestore_integration_test.go
index 4644c9639..bd00f82b7 100644
--- a/tpl/tplimpl/templatestore_integration_test.go
+++ b/tpl/tplimpl/templatestore_integration_test.go
@@ -3,6 +3,7 @@ package tplimpl_test
 import (
 	"context"
 	"io"
+	"strings"
 	"testing"
 
 	qt "github.com/frankban/quicktest"
@@ -684,6 +685,102 @@ layout: mylayout
 	b.AssertFileContent("public/en/foo/index.xml", "layouts/list.xml")
 }
 
+func TestLookupOrderIssue13636(t *testing.T) {
+	t.Parallel()
+
+	filesTemplate := `
+-- hugo.toml --
+defaultContentLanguage = "en"
+defaultContentLanguageInSubdir = true
+[languages]
+[languages.en]
+weight = 1
+[languages.nn]
+weight = 2
+-- content/s1/p1.en.md --
+---
+outputs: ["html", "amp", "json"]
+---
+-- content/s1/p1.nn.md --
+---
+outputs: ["html", "amp", "json"]
+---
+-- layouts/L1 --
+L1
+-- layouts/L2 --
+L2
+-- layouts/L3 --
+L3
+
+`
+
+	tests := []struct {
+		Lang       string
+		L1         string
+		L2         string
+		L3         string
+		ExpectHTML string
+		ExpectAmp  string
+		ExpectJSON string
+	}{
+		{"en", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""},
+		{"en", "all.amp.html", "all.html", "page.html", "page.html", "all.amp.html", ""},
+		{"en", "all.amp.html", "all.html", "list.html", "all.html", "all.amp.html", ""},
+		{"en", "all.en.html", "all.json", "single.html", "single.html", "single.html", "all.json"},
+		{"en", "all.en.html", "single.json", "single.html", "single.html", "single.html", "single.json"},
+		{"en", "all.en.html", "all.html", "list.html", "all.en.html", "all.en.html", ""},
+		{"en", "list.en.html", "list.html", "list.en.html", "", "", ""},
+		{"nn", "all.en.html", "all.html", "single.html", "single.html", "single.html", ""},
+		{"nn", "all.en.html", "all.nn.html", "single.html", "single.html", "single.html", ""},
+		{"nn", "all.en.html", "all.nn.html", "single.nn.html", "single.nn.html", "single.nn.html", ""},
+		{"nn", "single.json", "single.nn.json", "all.json", "", "", "single.nn.json"},
+		{"nn", "single.json", "single.en.json", "all.nn.json", "", "", "single.json"},
+	}
+
+	for i, test := range tests {
+		if i != 8 {
+			// continue
+		}
+		files := strings.ReplaceAll(filesTemplate, "L1", test.L1)
+		files = strings.ReplaceAll(files, "L2", test.L2)
+		files = strings.ReplaceAll(files, "L3", test.L3)
+		t.Logf("Test %d: %s %s %s %s", i, test.Lang, test.L1, test.L2, test.L3)
+
+		for range 3 {
+			b := hugolib.Test(t, files)
+			b.Assert(len(b.H.Sites), qt.Equals, 2)
+
+			var (
+				pubhHTML = "public/LANG/s1/p1/index.html"
+				pubhAmp  = "public/LANG/amp/s1/p1/index.html"
+				pubhJSON = "public/LANG/s1/p1/index.json"
+			)
+
+			pubhHTML = strings.ReplaceAll(pubhHTML, "LANG", test.Lang)
+			pubhAmp = strings.ReplaceAll(pubhAmp, "LANG", test.Lang)
+			pubhJSON = strings.ReplaceAll(pubhJSON, "LANG", test.Lang)
+
+			if test.ExpectHTML != "" {
+				b.AssertFileContent(pubhHTML, test.ExpectHTML)
+			} else {
+				b.AssertFileExists(pubhHTML, false)
+			}
+
+			if test.ExpectAmp != "" {
+				b.AssertFileContent(pubhAmp, test.ExpectAmp)
+			} else {
+				b.AssertFileExists(pubhAmp, false)
+			}
+
+			if test.ExpectJSON != "" {
+				b.AssertFileContent(pubhJSON, test.ExpectJSON)
+			} else {
+				b.AssertFileExists(pubhJSON, false)
+			}
+		}
+	}
+}
+
 func TestLookupShortcodeDepth(t *testing.T) {
 	t.Parallel()
 
@@ -959,7 +1056,7 @@ All.
 
 	b := hugolib.Test(t, files, hugolib.TestOptWarn())
 
-	b.AssertLogContains("Duplicate content path")
+	b.AssertLogContains("! Duplicate content path")
 }
 
 // Issue #13577.
@@ -1127,6 +1224,28 @@ single.html
 	b.AssertFileContent("public/s3/index.html", "single.html") // fail
 }
 
+func TestIssue13605(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+-- content/s1/p1.md --
+---
+title: p1
+---
+{{< sc >}}
+-- layouts/s1/_shortcodes/sc.html --
+layouts/s1/_shortcodes/sc.html
+-- layouts/single.html --
+{{ .Content }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/s1/p1/index.html", "layouts/s1/_shortcodes/sc.html")
+}
+
 func TestSkipDotFiles(t *testing.T) {
 	t.Parallel()
 
@@ -1141,3 +1260,110 @@ All.
 	// Just make sure it doesn't fail.
 	hugolib.Test(t, files)
 }
+
+func TestPartialsLangIssue13612(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','section','sitemap','taxonomy','term']
+
+defaultContentLanguage = 'ru'
+defaultContentLanguageInSubdir = true
+
+[languages.ru]
+weight = 1
+
+[languages.en]
+weight = 2
+
+[outputs]
+home = ['html','rss']
+
+-- layouts/_partials/comment.en.html --
+layouts/_partials/comment.en.html
+-- layouts/_partials/comment.en.xml --
+layouts/_partials/comment.en.xml
+-- layouts/_partials/comment.ru.html --
+layouts/_partials/comment.ru.html
+-- layouts/_partials/comment.ru.xml --
+layouts/_partials/comment.ru.xml
+-- layouts/home.html --
+{{ partial (print "comment." (default "ru" .Lang) ".html") . }}
+-- layouts/home.rss.xml --
+{{ partial (print "comment." (default "ru" .Lang) ".xml") . }}
+`
+
+	b := hugolib.Test(t, files)
+
+	b.AssertFileContent("public/en/index.html", "layouts/_partials/comment.en.html")
+	b.AssertFileContent("public/en/index.xml", "layouts/_partials/comment.en.xml")   // fail
+	b.AssertFileContent("public/ru/index.html", "layouts/_partials/comment.ru.html") // fail
+	b.AssertFileContent("public/ru/index.xml", "layouts/_partials/comment.ru.xml")   // fail
+}
+
+func TestLayoutIssue13628(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','sitemap','taxonomy','term']
+-- content/p1.md --
+---
+title: p1
+layout: foo
+---
+-- layouts/single.html --
+layouts/single.html
+-- layouts/list.html --
+layouts/list.html
+`
+
+	for range 5 {
+
+		b := hugolib.Test(t, files)
+
+		b.AssertFileContent("public/p1/index.html", "layouts/single.html")
+	}
+}
+
+func TestTemplateLoop(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+-- layouts/_partials/p.html --
+p: {{ partial "p.html" . }}
+-- layouts/all.html --
+{{ partial "p.html" . }}
+
+`
+	b, err := hugolib.TestE(t, files)
+	b.Assert(err, qt.IsNotNil)
+	b.Assert(err.Error(), qt.Contains, "error calling partial: maximum template call stack size exceeded")
+}
+
+func TestIssue13630(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['rss','sitemap']
+-- content/p1.md --
+---
+title: p1
+layout: foo
+---
+-- layouts/list.html --
+layouts/list.html
+-- layouts/taxononmy.html.html --
+layouts/taxononmy.html.html
+`
+
+	var b *hugolib.IntegrationTestBuilder
+
+	for range 3 {
+		b = hugolib.Test(t, files)
+		b.AssertFileExists("public/p1/index.html", false)
+	}
+}
diff --git a/tpl/tplimpl/tplimpl_integration_test.go b/tpl/tplimpl/tplimpl_integration_test.go
index 8b80d5b60..b62898923 100644
--- a/tpl/tplimpl/tplimpl_integration_test.go
+++ b/tpl/tplimpl/tplimpl_integration_test.go
@@ -1,3 +1,18 @@
+// Copyright 2025 The Hugo Authors. All rights reserved.
+//
+// Portions Copyright The Go Authors.
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 package tplimpl_test
 
 import (
diff --git a/tpl/transform/transform_integration_test.go b/tpl/transform/transform_integration_test.go
index ceb80309b..2b3c7d40e 100644
--- a/tpl/transform/transform_integration_test.go
+++ b/tpl/transform/transform_integration_test.go
@@ -379,3 +379,119 @@ Markdown: {{ $markdown }}|
 
 	b.AssertFileContent("public/index.html", "Markdown: ## Heading 2\n|")
 }
+
+func TestUnmarshalCSV(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['page','rss','section','sitemap','taxonomy','term']
+-- layouts/all.html --
+{{ $opts := OPTS }}
+{{ with resources.Get "pets.csv" | transform.Unmarshal $opts }}
+  {{ jsonify . }}
+{{ end }}
+-- assets/pets.csv --
+DATA
+`
+
+	// targetType = map
+	f := strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
+	f = strings.ReplaceAll(f, "DATA",
+		"name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
+	)
+	b := hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html",
+		`[{"age":"3","breed":"Collie","name":"Spot","type":"dog"},{"age":"7","breed":"Malicious","name":"Felix","type":"cat"}]`,
+	)
+
+	// targetType = map (no data)
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
+	f = strings.ReplaceAll(f, "DATA", "")
+	b = hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html", "")
+
+	// targetType = slice
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "slice"`)
+	f = strings.ReplaceAll(f, "DATA",
+		"name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
+	)
+	b = hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html",
+		`[["name","type","breed","age"],["Spot","dog","Collie","3"],["Felix","cat","Malicious","7"]]`,
+	)
+
+	// targetType = slice (no data)
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "slice"`)
+	f = strings.ReplaceAll(f, "DATA", "")
+	b = hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html", "")
+
+	// targetType not specified
+	f = strings.ReplaceAll(files, "OPTS", "dict")
+	f = strings.ReplaceAll(f, "DATA",
+		"name,type,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
+	)
+	b = hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html",
+		`[["name","type","breed","age"],["Spot","dog","Collie","3"],["Felix","cat","Malicious","7"]]`,
+	)
+
+	// targetType not specified (no data)
+	f = strings.ReplaceAll(files, "OPTS", "dict")
+	f = strings.ReplaceAll(f, "DATA", "")
+	b = hugolib.Test(t, f)
+	b.AssertFileContent("public/index.html", "")
+
+	// targetType = foo
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "foo"`)
+	_, err := hugolib.TestE(t, f)
+	if err == nil {
+		t.Errorf("expected error")
+	} else {
+		if !strings.Contains(err.Error(), `invalid targetType: expected either slice or map, received foo`) {
+			t.Log(err.Error())
+			t.Errorf("error message does not match expected error message")
+		}
+	}
+
+	// targetType = foo (no data)
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "foo"`)
+	f = strings.ReplaceAll(f, "DATA", "")
+	_, err = hugolib.TestE(t, f)
+	if err == nil {
+		t.Errorf("expected error")
+	} else {
+		if !strings.Contains(err.Error(), `invalid targetType: expected either slice or map, received foo`) {
+			t.Log(err.Error())
+			t.Errorf("error message does not match expected error message")
+		}
+	}
+
+	// targetType = map (error: expected at least a header row and one data row)
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
+	_, err = hugolib.TestE(t, f)
+	if err == nil {
+		t.Errorf("expected error")
+	} else {
+		if !strings.Contains(err.Error(), `expected at least a header row and one data row`) {
+			t.Log(err.Error())
+			t.Errorf("error message does not match expected error message")
+		}
+	}
+
+	// targetType = map (error: header row contains duplicate field names)
+	f = strings.ReplaceAll(files, "OPTS", `dict "targetType" "map"`)
+	f = strings.ReplaceAll(f, "DATA",
+		"name,name,breed,age\nSpot,dog,Collie,3\nFelix,cat,Malicious,7",
+	)
+	_, err = hugolib.TestE(t, f)
+	if err == nil {
+		t.Errorf("expected error")
+	} else {
+		if !strings.Contains(err.Error(), `header row contains duplicate field names`) {
+			t.Log(err.Error())
+			t.Errorf("error message does not match expected error message")
+		}
+	}
+}
diff --git a/watchtestscripts.sh b/watchtestscripts.sh
index bf61d0cc3..5c6f90009 100755
--- a/watchtestscripts.sh
+++ b/watchtestscripts.sh
@@ -3,5 +3,5 @@
 trap exit SIGINT
 
 # I use "run tests on save" in my editor.
-# Unfortunately, changes to text files does not trigger this. Hence this workaround.
-while true; do find testscripts -type f -name "*.txt" | entr -pd touch main_test.go; done
\ No newline at end of file
+# Unfortunately, changes to text files do not trigger this. Hence this workaround.
+while true; do find testscripts -type f -name "*.txt" | entr -pd touch main_test.go; done