Repository: gohugoio/hugo
Pull request #13742 (2025-05-26 14:45:39)

parser/pageparser: Add coverage for all IsX methods of Item

This PR adds unit tests for all `IsX` boolean methods of the `Item` struct in the `parser/pageparser` package.


Patch:
diff --git a/common/paths/pathparser.go b/common/paths/pathparser.go
index b0a2f9fc4..d10523a84 100644
--- a/common/paths/pathparser.go
+++ b/common/paths/pathparser.go
@@ -120,11 +120,11 @@ func (pp *PathParser) parse(component, s string) (*Path, error) {
 	return p, nil
 }
 
-func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot int) {
+func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot, numDots int) {
 	if p.posContainerHigh != -1 {
 		return
 	}
-	mayHaveLang := p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
+	mayHaveLang := numDots > 1 && p.posIdentifierLanguage == -1 && pp.LanguageIndex != nil
 	mayHaveLang = mayHaveLang && (component == files.ComponentFolderContent || component == files.ComponentFolderLayouts)
 	mayHaveOutputFormat := component == files.ComponentFolderLayouts
 	mayHaveKind := p.posIdentifierKind == -1 && mayHaveOutputFormat
@@ -167,7 +167,6 @@ func (pp *PathParser) parseIdentifier(component, s string, p *Path, i, lastDot i
 			if langFound {
 				p.identifiersKnown = append(p.identifiersKnown, id)
 				p.posIdentifierLanguage = len(p.identifiersKnown) - 1
-
 			}
 		}
 
@@ -234,19 +233,21 @@ func (pp *PathParser) doParse(component, s string, p *Path) (*Path, error) {
 	p.s = s
 	slashCount := 0
 	lastDot := 0
+	lastSlashIdx := strings.LastIndex(s, "/")
+	numDots := strings.Count(s[lastSlashIdx+1:], ".")
 
 	for i := len(s) - 1; i >= 0; i-- {
 		c := s[i]
 
 		switch c {
 		case '.':
-			pp.parseIdentifier(component, s, p, i, lastDot)
+			pp.parseIdentifier(component, s, p, i, lastDot, numDots)
 			lastDot = i
 		case '/':
 			slashCount++
 			if p.posContainerHigh == -1 {
 				if lastDot > 0 {
-					pp.parseIdentifier(component, s, p, i, lastDot)
+					pp.parseIdentifier(component, s, p, i, lastDot, numDots)
 				}
 				p.posContainerHigh = i + 1
 			} else if p.posContainerLow == -1 {
diff --git a/config/allconfig/allconfig.go b/config/allconfig/allconfig.go
index d3ee28490..0db0be1d8 100644
--- a/config/allconfig/allconfig.go
+++ b/config/allconfig/allconfig.go
@@ -146,7 +146,7 @@ type Config struct {
 
 	// The cascade configuration section contains the top level front matter cascade configuration options,
 	// a slice of page matcher and params to apply to those pages.
-	Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, maps.Params]] `mapstructure:"-"`
+	Cascade *config.ConfigNamespace[[]page.PageMatcherParamsConfig, *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]] `mapstructure:"-"`
 
 	// The segments defines segments for the site. Used for partial/segmented builds.
 	Segments *config.ConfigNamespace[map[string]segments.SegmentConfig, segments.Segments] `mapstructure:"-"`
@@ -776,7 +776,7 @@ type Configs struct {
 }
 
 func (c *Configs) Validate(logger loggers.Logger) error {
-	c.Base.Cascade.Config.Range(func(p page.PageMatcher, params maps.Params) bool {
+	c.Base.Cascade.Config.Range(func(p page.PageMatcher, cfg page.PageMatcherParamsConfig) bool {
 		page.CheckCascadePattern(logger, p)
 		return true
 	})
diff --git a/go.mod b/go.mod
index c51a5fc52..e0ce06568 100644
--- a/go.mod
+++ b/go.mod
@@ -55,7 +55,7 @@ require (
 	github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c
 	github.com/muesli/smartcrop v0.3.0
 	github.com/niklasfasching/go-org v1.7.0
-	github.com/olekukonko/tablewriter v0.0.5
+	github.com/olekukonko/tablewriter v1.0.7
 	github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58
 	github.com/pelletier/go-toml/v2 v2.2.4
 	github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
@@ -141,13 +141,16 @@ require (
 	github.com/kylelemons/godebug v1.1.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-runewidth v0.0.9 // indirect
+	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
 	github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
 	github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
+	github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect
+	github.com/olekukonko/ll v0.0.8 // indirect
 	github.com/perimeterx/marshmallow v1.1.5 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
+	github.com/rivo/uniseg v0.2.0 // indirect
 	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 	go.opencensus.io v0.24.0 // indirect
 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
diff --git a/go.sum b/go.sum
index a70bc0390..05978a56e 100644
--- a/go.sum
+++ b/go.sum
@@ -378,6 +378,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
 github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
@@ -394,8 +396,14 @@ github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//J
 github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
 github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
 github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
+github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 h1:r3FaAI0NZK3hSmtTDrBVREhKULp8oUeqLT5Eyl2mSPo=
+github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
+github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
+github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
+github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0=
 github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y=
 github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
@@ -414,6 +422,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
 github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
diff --git a/helpers/processing_stats.go b/helpers/processing_stats.go
index 3f48466c7..42376b46d 100644
--- a/helpers/processing_stats.go
+++ b/helpers/processing_stats.go
@@ -19,6 +19,8 @@ import (
 	"sync/atomic"
 
 	"github.com/olekukonko/tablewriter"
+	"github.com/olekukonko/tablewriter/renderer"
+	"github.com/olekukonko/tablewriter/tw"
 )
 
 // ProcessingStats represents statistics about a site build.
@@ -66,23 +68,6 @@ func (s *ProcessingStats) Add(counter *uint64, amount int) {
 	atomic.AddUint64(counter, uint64(amount))
 }
 
-// Table writes a table-formatted representation of the stats in a
-// ProcessingStats instance to w.
-func (s *ProcessingStats) Table(w io.Writer) {
-	titleVals := s.toVals()
-	data := make([][]string, len(titleVals))
-	for i, tv := range titleVals {
-		data[i] = []string{tv.name, strconv.Itoa(int(tv.val))}
-	}
-
-	table := tablewriter.NewWriter(w)
-
-	table.AppendBulk(data)
-	table.SetHeader([]string{"", s.Name})
-	table.SetBorder(false)
-	table.Render()
-}
-
 // ProcessingStatsTable writes a table-formatted representation of stats to w.
 func ProcessingStatsTable(w io.Writer, stats ...*ProcessingStats) {
 	names := make([]string, len(stats)+1)
@@ -106,13 +91,26 @@ func ProcessingStatsTable(w io.Writer, stats ...*ProcessingStats) {
 				data[j] = append(data[j], strconv.Itoa(int(tv.val)))
 			}
 		}
-
 	}
 
-	table := tablewriter.NewWriter(w)
-
-	table.AppendBulk(data)
-	table.SetHeader(names)
-	table.SetBorder(false)
+	table := tablewriter.NewTable(
+		w,
+		tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
+			Borders: tw.BorderNone,
+			Symbols: tw.NewSymbols(tw.StyleLight),
+			Settings: tw.Settings{
+				Separators: tw.Separators{BetweenRows: tw.Off},
+				Lines:      tw.Lines{ShowFooterLine: tw.On},
+			},
+		})),
+		tablewriter.WithConfig(
+			tablewriter.Config{
+				MaxWidth: 70,
+				Row:      tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignRight, PerColumn: []tw.Align{tw.AlignLeft}}},
+			}),
+	)
+
+	table.Bulk(data)
+	table.Header(names)
 	table.Render()
 }
diff --git a/hugolib/content_map_page.go b/hugolib/content_map_page.go
index 9e67fbb1b..47637d3e8 100644
--- a/hugolib/content_map_page.go
+++ b/hugolib/content_map_page.go
@@ -21,7 +21,6 @@ import (
 	"sort"
 	"strconv"
 	"strings"
-	"sync"
 	"sync/atomic"
 	"time"
 
@@ -1310,7 +1309,6 @@ func (h *HugoSites) resolveAndResetDependententPageOutputs(ctx context.Context,
 		}
 
 		po.renderState = 0
-		po.p.resourcesPublishInit = &sync.Once{}
 		if r == identity.FinderFoundOneOfMany || po.f.Name == output.HTTPStatus404HTMLFormat.Name {
 			// Will force a re-render even in fast render mode.
 			po.renderOnce = false
@@ -1412,7 +1410,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
 		}
 
 		// Handle cascades first to get any default dates set.
-		var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+		var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
 		if keyPage == "" {
 			// Home page gets it's cascade from the site config.
 			cascade = sa.conf.Cascade.Config
@@ -1424,7 +1422,7 @@ func (sa *sitePagesAssembler) applyAggregates() error {
 		} else {
 			_, data := pw.WalkContext.Data().LongestPrefix(paths.Dir(keyPage))
 			if data != nil {
-				cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+				cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
 			}
 		}
 
@@ -1506,11 +1504,11 @@ func (sa *sitePagesAssembler) applyAggregates() error {
 				pageResource := rs.r.(*pageState)
 				relPath := pageResource.m.pathInfo.BaseRel(pageBundle.m.pathInfo)
 				pageResource.m.resourcePath = relPath
-				var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+				var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
 				// Apply cascade (if set) to the page.
 				_, data := pw.WalkContext.Data().LongestPrefix(resourceKey)
 				if data != nil {
-					cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+					cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
 				}
 				if err := pageResource.setMetaPost(cascade); err != nil {
 					return false, err
@@ -1574,10 +1572,10 @@ func (sa *sitePagesAssembler) applyAggregatesToTaxonomiesAndTerms() error {
 				const eventName = "dates"
 
 				if p.Kind() == kinds.KindTerm {
-					var cascade *maps.Ordered[page.PageMatcher, maps.Params]
+					var cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]
 					_, data := pw.WalkContext.Data().LongestPrefix(s)
 					if data != nil {
-						cascade = data.(*maps.Ordered[page.PageMatcher, maps.Params])
+						cascade = data.(*maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig])
 					}
 					if err := p.setMetaPost(cascade); err != nil {
 						return false, err
diff --git a/hugolib/page.go b/hugolib/page.go
index 84d4d1ea8..bb3835c1e 100644
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -19,7 +19,6 @@ import (
 	"path/filepath"
 	"strconv"
 	"strings"
-	"sync"
 	"sync/atomic"
 
 	"github.com/gohugoio/hugo/hugofs"
@@ -29,6 +28,7 @@ import (
 	"github.com/gohugoio/hugo/media"
 	"github.com/gohugoio/hugo/output"
 	"github.com/gohugoio/hugo/related"
+	"github.com/gohugoio/hugo/resources"
 	"github.com/gohugoio/hugo/tpl/tplimpl"
 	"github.com/spf13/afero"
 
@@ -110,8 +110,7 @@ type pageState struct {
 	*pageCommon
 
 	resource.Staler
-	dependencyManager    identity.Manager
-	resourcesPublishInit *sync.Once
+	dependencyManager identity.Manager
 }
 
 func (p *pageState) incrPageOutputTemplateVariation() {
@@ -522,39 +521,35 @@ func (p *pageState) initPage() error {
 }
 
 func (p *pageState) renderResources() error {
-	var initErr error
+	for _, r := range p.Resources() {
 
-	p.resourcesPublishInit.Do(func() {
-		for _, r := range p.Resources() {
-			if _, ok := r.(page.Page); ok {
+		if _, ok := r.(page.Page); ok {
+			if p.s.h.buildCounter.Load() == 0 {
 				// Pages gets rendered with the owning page but we count them here.
 				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
-				continue
 			}
+			continue
+		}
 
-			if _, isWrapper := r.(resource.ResourceWrapper); isWrapper {
-				// Skip resources that are wrapped.
-				// These gets published on its own.
-				continue
-			}
+		if resources.IsPublished(r) {
+			continue
+		}
 
-			src, ok := r.(resource.Source)
-			if !ok {
-				initErr = fmt.Errorf("resource %T does not support resource.Source", r)
-				return
-			}
+		src, ok := r.(resource.Source)
+		if !ok {
+			return fmt.Errorf("resource %T does not support resource.Source", r)
+		}
 
-			if err := src.Publish(); err != nil {
-				if !herrors.IsNotExist(err) {
-					p.s.Log.Errorf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err)
-				}
-			} else {
-				p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
+		if err := src.Publish(); err != nil {
+			if !herrors.IsNotExist(err) {
+				p.s.Log.Errorf("Failed to publish Resource for page %q: %s", p.pathOrTitle(), err)
 			}
+		} else {
+			p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Files)
 		}
-	})
+	}
 
-	return initErr
+	return nil
 }
 
 func (p *pageState) AlternativeOutputFormats() page.OutputFormats {
diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go
index e8bce20d1..0d6d22e9a 100644
--- a/hugolib/page__meta.go
+++ b/hugolib/page__meta.go
@@ -83,8 +83,8 @@ type pageMetaParams struct {
 
 	// These are only set in watch mode.
 	datesOriginal   pagemeta.Dates
-	paramsOriginal  map[string]any                               // contains the original params as defined in the front matter.
-	cascadeOriginal *maps.Ordered[page.PageMatcher, maps.Params] // contains the original cascade as defined in the front matter.
+	paramsOriginal  map[string]any                                                // contains the original params as defined in the front matter.
+	cascadeOriginal *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] // contains the original cascade as defined in the front matter.
 }
 
 func (m *pageMetaParams) init(preserveOriginal bool) {
@@ -291,7 +291,7 @@ func (p *pageMeta) setMetaPre(pi *contentParseInfo, logger loggers.Logger, conf
 	return nil
 }
 
-func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Params]) error {
+func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig]) error {
 	ps.m.setMetaPostCount++
 	var cascadeHashPre uint64
 	if ps.m.setMetaPostCount > 1 {
@@ -303,15 +303,20 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
 	// Apply cascades first so they can be overridden later.
 	if cascade != nil {
 		if ps.m.pageConfig.CascadeCompiled != nil {
-			cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
+			cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
 				vv, found := ps.m.pageConfig.CascadeCompiled.Get(k)
 				if !found {
 					ps.m.pageConfig.CascadeCompiled.Set(k, v)
 				} else {
 					// Merge
-					for ck, cv := range v {
-						if _, found := vv[ck]; !found {
-							vv[ck] = cv
+					for ck, cv := range v.Params {
+						if _, found := vv.Params[ck]; !found {
+							vv.Params[ck] = cv
+						}
+					}
+					for ck, cv := range v.Fields {
+						if _, found := vv.Fields[ck]; !found {
+							vv.Fields[ck] = cv
 						}
 					}
 				}
@@ -341,11 +346,17 @@ func (ps *pageState) setMetaPost(cascade *maps.Ordered[page.PageMatcher, maps.Pa
 
 	// Cascade is also applied to itself.
 	var err error
-	cascade.Range(func(k page.PageMatcher, v maps.Params) bool {
+	cascade.Range(func(k page.PageMatcher, v page.PageMatcherParamsConfig) bool {
 		if !k.Matches(ps) {
 			return true
 		}
-		for kk, vv := range v {
+		for kk, vv := range v.Params {
+			if _, found := ps.m.pageConfig.Params[kk]; !found {
+				ps.m.pageConfig.Params[kk] = vv
+			}
+		}
+
+		for kk, vv := range v.Fields {
 			if ps.m.pageConfig.IsFromContentAdapter {
 				if _, found := ps.m.pageConfig.ContentAdapterData[kk]; !found {
 					ps.m.pageConfig.ContentAdapterData[kk] = vv
diff --git a/hugolib/page__new.go b/hugolib/page__new.go
index 10336e255..9d05abea6 100644
--- a/hugolib/page__new.go
+++ b/hugolib/page__new.go
@@ -16,7 +16,6 @@ package hugolib
 import (
 	"fmt"
 	"strings"
-	"sync"
 	"sync/atomic"
 
 	"github.com/gohugoio/hugo/hugofs/files"
@@ -190,7 +189,6 @@ func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
 			pid:                               pid,
 			pageOutput:                        nopPageOutput,
 			pageOutputTemplateVariationsState: &atomic.Uint32{},
-			resourcesPublishInit:              &sync.Once{},
 			Staler:                            m,
 			dependencyManager:                 m.s.Conf.NewIdentityManager(m.Path()),
 			pageCommon: &pageCommon{
diff --git a/hugoreleaser.env b/hugoreleaser.env
index 61fc999d5..46cd6ee93 100644
--- a/hugoreleaser.env
+++ b/hugoreleaser.env
@@ -1,7 +1,8 @@
 # Release env. 
 # These will be replaced by script before release.
-HUGORELEASER_TAG=v0.147.5
-HUGORELEASER_COMMITISH=7766fc62416ff95ed7768c19947ac0fcd96444cc
+HUGORELEASER_TAG=v0.147.6
+HUGORELEASER_COMMITISH=0a5fd8ebb8e2ca798515e8c564c14e32db3b4127
+
 
 
 
diff --git a/parser/pageparser/item.go b/parser/pageparser/item.go
index 47ec6d64d..7d63be0ad 100644
--- a/parser/pageparser/item.go
+++ b/parser/pageparser/item.go
@@ -104,7 +104,7 @@ func (i Item) ValTyped(source []byte) any {
 }
 
 func (i Item) IsText() bool {
-	return i.Type == tText || i.Type == tIndentation
+	return i.Type == tText || i.IsIndentation()
 }
 
 func (i Item) IsIndentation() bool {
@@ -152,7 +152,7 @@ func (i Item) IsFrontMatter() bool {
 }
 
 func (i Item) IsDone() bool {
-	return i.Type == tError || i.Type == tEOF
+	return i.IsError() || i.IsEOF()
 }
 
 func (i Item) IsEOF() bool {
@@ -166,18 +166,19 @@ func (i Item) IsError() bool {
 func (i Item) ToString(source []byte) string {
 	val := i.Val(source)
 	switch {
-	case i.Type == tEOF:
+	case i.IsEOF():
 		return "EOF"
-	case i.Type == tError:
+	case i.IsError():
 		return string(val)
-	case i.Type == tIndentation:
+	case i.IsIndentation():
 		return fmt.Sprintf("%s:[%s]", i.Type, util.VisualizeSpaces(val))
 	case i.Type > tKeywordMarker:
 		return fmt.Sprintf("<%s>", val)
 	case len(val) > 50:
 		return fmt.Sprintf("%v:%.20q...", i.Type, val)
+	default:
+		return fmt.Sprintf("%v:[%s]", i.Type, val)
 	}
-	return fmt.Sprintf("%v:[%s]", i.Type, val)
 }
 
 type ItemType int
diff --git a/resources/page/page_matcher.go b/resources/page/page_matcher.go
index 27a7c7e9e..306c2d03c 100644
--- a/resources/page/page_matcher.go
+++ b/resources/page/page_matcher.go
@@ -105,9 +105,9 @@ func CheckCascadePattern(logger loggers.Logger, m PageMatcher) {
 	}
 }
 
-func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]], error) {
-	buildConfig := func(in any) (*maps.Ordered[PageMatcher, maps.Params], any, error) {
-		cascade := maps.NewOrdered[PageMatcher, maps.Params]()
+func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, PageMatcherParamsConfig]], error) {
+	buildConfig := func(in any) (*maps.Ordered[PageMatcher, PageMatcherParamsConfig], any, error) {
+		cascade := maps.NewOrdered[PageMatcher, PageMatcherParamsConfig]()
 		if in == nil {
 			return cascade, []map[string]any{}, nil
 		}
@@ -124,11 +124,7 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
 				c   PageMatcherParamsConfig
 				err error
 			)
-			if handleLegacyFormat {
-				c, err = mapToPageMatcherParamsConfigLegacy(m)
-			} else {
-				c, err = mapToPageMatcherParamsConfig(m)
-			}
+			c, err = mapToPageMatcherParamsConfig(m)
 			if err != nil {
 				return nil, nil, err
 			}
@@ -147,23 +143,28 @@ func DecodeCascadeConfig(logger loggers.Logger, handleLegacyFormat bool, in any)
 			if found {
 				// Merge
 				for k, v := range cfg.Params {
-					if _, found := c[k]; !found {
-						c[k] = v
+					if _, found := c.Params[k]; !found {
+						c.Params[k] = v
+					}
+				}
+				for k, v := range cfg.Fields {
+					if _, found := c.Fields[k]; !found {
+						c.Fields[k] = v
 					}
 				}
 			} else {
-				cascade.Set(m, cfg.Params)
+				cascade.Set(m, cfg)
 			}
 		}
 
 		return cascade, cfgs, nil
 	}
 
-	return config.DecodeNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, maps.Params]](in, buildConfig)
+	return config.DecodeNamespace[[]PageMatcherParamsConfig, *maps.Ordered[PageMatcher, PageMatcherParamsConfig]](in, buildConfig)
 }
 
 // DecodeCascade decodes in which could be either a map or a slice of maps.
-func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, maps.Params], error) {
+func DecodeCascade(logger loggers.Logger, handleLegacyFormat bool, in any) (*maps.Ordered[PageMatcher, PageMatcherParamsConfig], error) {
 	conf, err := DecodeCascadeConfig(logger, handleLegacyFormat, in)
 	if err != nil {
 		return nil, err
@@ -181,47 +182,22 @@ func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, er
 				return pcfg, err
 			}
 			pcfg.Target = target
-		default:
+		case "params":
 			if pcfg.Params == nil {
 				pcfg.Params = make(maps.Params)
 			}
-			pcfg.Params[k] = v
-		}
-	}
-	return pcfg, pcfg.init()
-}
-
-func mapToPageMatcherParamsConfigLegacy(m map[string]any) (PageMatcherParamsConfig, error) {
-	var pcfg PageMatcherParamsConfig
-	for k, v := range m {
-		switch strings.ToLower(k) {
-		case "params":
-			// We simplified the structure of the cascade config in Hugo 0.111.0.
-			// There is a small chance that someone has used the old structure with the params keyword,
-			// those values will now be moved to the top level.
-			// This should be very unlikely as it would lead to constructs like .Params.params.foo,
-			// and most people see params as an Hugo internal keyword.
 			params := maps.ToStringMap(v)
-			if pcfg.Params == nil {
-				pcfg.Params = params
-			} else {
-				for k, v := range params {
-					if _, found := pcfg.Params[k]; !found {
-						pcfg.Params[k] = v
-					}
+			for k, v := range params {
+				if _, found := pcfg.Params[k]; !found {
+					pcfg.Params[k] = v
 				}
 			}
-		case "_target", "target":
-			var target PageMatcher
-			if err := decodePageMatcher(v, &target); err != nil {
-				return pcfg, err
-			}
-			pcfg.Target = target
 		default:
-			if pcfg.Params == nil {
-				pcfg.Params = make(maps.Params)
+			if pcfg.Fields == nil {
+				pcfg.Fields = make(maps.Params)
 			}
-			pcfg.Params[k] = v
+
+			pcfg.Fields[k] = v
 		}
 	}
 	return pcfg, pcfg.init()
@@ -250,10 +226,14 @@ func decodePageMatcher(m any, v *PageMatcher) error {
 type PageMatcherParamsConfig struct {
 	// Apply Params to all Pages matching Target.
 	Params maps.Params
+	// Fields holds all fields but Params.
+	Fields maps.Params
+	// Target is the PageMatcher that this config applies to.
 	Target PageMatcher
 }
 
 func (p *PageMatcherParamsConfig) init() error {
 	maps.PrepareParams(p.Params)
+	maps.PrepareParams(p.Fields)
 	return nil
 }
diff --git a/resources/page/pagemeta/page_frontmatter.go b/resources/page/pagemeta/page_frontmatter.go
index 7dea7ca6b..fd4f7759b 100644
--- a/resources/page/pagemeta/page_frontmatter.go
+++ b/resources/page/pagemeta/page_frontmatter.go
@@ -125,10 +125,10 @@ type PageConfig struct {
 	ContentAdapterData map[string]any `mapstructure:"-" json:"-"`
 
 	// Compiled values.
-	CascadeCompiled         *maps.Ordered[page.PageMatcher, maps.Params] `mapstructure:"-" json:"-"`
-	ContentMediaType        media.Type                                   `mapstructure:"-" json:"-"`
-	ConfiguredOutputFormats output.Formats                               `mapstructure:"-" json:"-"`
-	IsFromContentAdapter    bool                                         `mapstructure:"-" json:"-"`
+	CascadeCompiled         *maps.Ordered[page.PageMatcher, page.PageMatcherParamsConfig] `mapstructure:"-" json:"-"`
+	ContentMediaType        media.Type                                                    `mapstructure:"-" json:"-"`
+	ConfiguredOutputFormats output.Formats                                                `mapstructure:"-" json:"-"`
+	IsFromContentAdapter    bool                                                          `mapstructure:"-" json:"-"`
 }
 
 func ClonePageConfigForRebuild(p *PageConfig, params map[string]any) *PageConfig {
diff --git a/resources/resource.go b/resources/resource.go
index 6ef9bdae0..f6e5b9d73 100644
--- a/resources/resource.go
+++ b/resources/resource.go
@@ -24,6 +24,7 @@ import (
 	"sync/atomic"
 
 	"github.com/gohugoio/hugo/identity"
+	"github.com/gohugoio/hugo/lazy"
 	"github.com/gohugoio/hugo/resources/internal"
 
 	"github.com/gohugoio/hugo/common/hashing"
@@ -54,6 +55,7 @@ var (
 	_ identity.DependencyManagerProvider = (*genericResource)(nil)
 	_ identity.Identity                  = (*genericResource)(nil)
 	_ fileInfo                           = (*genericResource)(nil)
+	_ isPublishedProvider                = (*genericResource)(nil)
 )
 
 type ResourceSourceDescriptor struct {
@@ -242,6 +244,7 @@ type baseResourceInternal interface {
 	fileInfo
 	mediaTypeAssigner
 	targetPather
+	isPublishedProvider
 
 	ReadSeekCloser() (hugio.ReadSeekCloser, error)
 
@@ -355,7 +358,7 @@ func GetTestInfoForResource(r resource.Resource) GenericResourceTestInfo {
 
 // genericResource represents a generic linkable resource.
 type genericResource struct {
-	publishInit *sync.Once
+	publishInit *lazy.OnceMore
 
 	key     string
 	keyInit *sync.Once
@@ -536,6 +539,10 @@ func (l *genericResource) Publish() error {
 	return err
 }
 
+func (l *genericResource) isPublished() bool {
+	return l.publishInit.Done()
+}
+
 func (l *genericResource) RelPermalink() string {
 	return l.spec.PathSpec.GetBasePath(false) + paths.PathEscape(l.paths.TargetLink())
 }
@@ -629,7 +636,7 @@ func (rc *genericResource) cloneWithUpdates(u *transformationUpdate) (baseResour
 }
 
 func (l genericResource) clone() *genericResource {
-	l.publishInit = &sync.Once{}
+	l.publishInit = &lazy.OnceMore{}
 	l.keyInit = &sync.Once{}
 	return &l
 }
@@ -643,6 +650,10 @@ type targetPather interface {
 	TargetPath() string
 }
 
+type isPublishedProvider interface {
+	isPublished() bool
+}
+
 type resourceHash struct {
 	value    uint64
 	size     int64
@@ -702,6 +713,11 @@ func InternalResourceSourcePathBestEffort(r resource.Resource) string {
 	return InternalResourceTargetPath(r)
 }
 
+// isPublished returns true if the resource is published.
+func IsPublished(r resource.Resource) bool {
+	return r.(isPublishedProvider).isPublished()
+}
+
 type targetPathProvider interface {
 	// targetPath is the relative path to this resource.
 	// In most cases this will be the same as the RelPermalink(),
diff --git a/resources/resource/resourcetypes.go b/resources/resource/resourcetypes.go
index 585dfd150..51255c612 100644
--- a/resources/resource/resourcetypes.go
+++ b/resources/resource/resourcetypes.go
@@ -81,11 +81,6 @@ type ResourceWithoutMeta interface {
 	ResourceDataProvider
 }
 
-type ResourceWrapper interface {
-	UnwrappedResource() Resource
-	WrapResource(Resource) ResourceWrapper
-}
-
 type ResourceTypeProvider interface {
 	// ResourceType is the resource type. For most file types, this is the main
 	// part of the MIME type, e.g. "image", "application", "text" etc.
diff --git a/resources/resource_spec.go b/resources/resource_spec.go
index 56ad9a27d..806a5ff8d 100644
--- a/resources/resource_spec.go
+++ b/resources/resource_spec.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/gohugoio/hugo/config"
 	"github.com/gohugoio/hugo/config/allconfig"
+	"github.com/gohugoio/hugo/lazy"
 	"github.com/gohugoio/hugo/output"
 	"github.com/gohugoio/hugo/resources/internal"
 	"github.com/gohugoio/hugo/resources/jsconfig"
@@ -189,7 +190,7 @@ func (r *Spec) NewResource(rd ResourceSourceDescriptor) (resource.Resource, erro
 	gr := &genericResource{
 		Staler:           &AtomicStaler{},
 		h:                &resourceHash{},
-		publishInit:      &sync.Once{},
+		publishInit:      &lazy.OnceMore{},
 		keyInit:          &sync.Once{},
 		includeHashInKey: isImage,
 		paths:            rp,
diff --git a/resources/transform.go b/resources/transform.go
index abb55f3cc..572143d49 100644
--- a/resources/transform.go
+++ b/resources/transform.go
@@ -61,6 +61,7 @@ var (
 	_ identity.DependencyManagerProvider = (*resourceAdapter)(nil)
 	_ identity.IdentityGroupProvider     = (*resourceAdapter)(nil)
 	_ resource.NameNormalizedProvider    = (*resourceAdapter)(nil)
+	_ isPublishedProvider                = (*resourceAdapter)(nil)
 )
 
 // These are transformations that need special support in Hugo that may not
@@ -325,6 +326,11 @@ func (r *resourceAdapter) Publish() error {
 	return r.target.Publish()
 }
 
+func (r *resourceAdapter) isPublished() bool {
+	r.init(false, false)
+	return r.target.isPublished()
+}
+
 func (r *resourceAdapter) ReadSeekCloser() (hugio.ReadSeekCloser, error) {
 	r.init(false, false)
 	return r.target.ReadSeekCloser()
diff --git a/tpl/partials/partials.go b/tpl/partials/partials.go
index 19882e36a..57a2aa280 100644
--- a/tpl/partials/partials.go
+++ b/tpl/partials/partials.go
@@ -133,7 +133,7 @@ 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)
+		ns.deps.Log.Warnidf(constants.WarnPartialSuperfluousPrefix, "Doubtful use of partial function in {{ partial \"%s\"}}), this is most likely not what you want. Consider removing superfluous prefix \"partials/\" from template name given as first function argument.", name)
 	}
 	v := ns.deps.TemplateStore.LookupPartial(name)
 	if v == nil {


Test patch:
diff --git a/common/paths/pathparser_test.go b/common/paths/pathparser_test.go
index a6194e756..ad76b9367 100644
--- a/common/paths/pathparser_test.go
+++ b/common/paths/pathparser_test.go
@@ -563,11 +563,20 @@ func TestParseLayouts(t *testing.T) {
 				c.Assert(p.Type(), qt.Equals, TypePartial)
 			},
 		},
+		{
+			"Shortcode lang in root",
+			"/_shortcodes/no.html",
+			func(c *qt.C, p *Path) {
+				c.Assert(p.Type(), qt.Equals, TypeShortcode)
+				c.Assert(p.Lang(), qt.Equals, "")
+				c.Assert(p.NameNoIdentifier(), qt.Equals, "no")
+			},
+		},
 	}
 
 	for _, test := range tests {
 		c.Run(test.name, func(c *qt.C) {
-			if test.name != "Baseof" {
+			if test.name != "Shortcode lang in root" {
 				// return
 			}
 			test.assert(c, testParser.Parse(files.ComponentFolderLayouts, test.path))
diff --git a/hugolib/cascade_test.go b/hugolib/cascade_test.go
index d0a6730db..10b47764f 100644
--- a/hugolib/cascade_test.go
+++ b/hugolib/cascade_test.go
@@ -917,3 +917,24 @@ title: p2
 	b.AssertFileExists("public/sx/index.html", true)    // failing
 	b.AssertFileExists("public/sx/p2/index.html", true) // failing
 }
+
+func TestCascadeGotmplIssue13743(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+disableKinds = ['home','rss','section','sitemap','taxonomy','term']
+[cascade.params]
+foo = 'bar'
+[cascade.target]
+path = '/p1'
+-- content/_content.gotmpl --
+{{ .AddPage (dict "title" "p1" "path" "p1") }}
+-- layouts/all.html --
+{{ .Title }}|{{ .Params.foo }}
+`
+
+	b := Test(t, files)
+
+	b.AssertFileContent("public/p1/index.html", "p1|bar") // actual content is "p1|"
+}
diff --git a/hugolib/rebuild_test.go b/hugolib/rebuild_test.go
index 0b22b9aa7..d4a15fb5b 100644
--- a/hugolib/rebuild_test.go
+++ b/hugolib/rebuild_test.go
@@ -1946,3 +1946,23 @@ tags: ["tag1"]
 	// But that is a harder problem to tackle.
 	b.AssertFileContent("public/tags/index.html", "All. Tag1|Tag2|")
 }
+
+func TestRebuildEditNonReferencedResourceIssue13748(t *testing.T) {
+	t.Parallel()
+
+	files := `
+-- hugo.toml --
+baseURL = "https://example.com"
+disableLiveReload = true
+-- content/mybundle/index.md --
+-- content/mybundle/resource.txt --
+This is a resource file.
+-- layouts/all.html --
+All.
+`
+	b := TestRunning(t, files)
+
+	b.AssertFileContent("public/mybundle/resource.txt", "This is a resource file.")
+	b.EditFileReplaceAll("content/mybundle/resource.txt", "This is a resource file.", "This is an edited resource file.").Build()
+	b.AssertFileContent("public/mybundle/resource.txt", "This is an edited resource file.")
+}
diff --git a/hugolib/site_stats_test.go b/hugolib/site_stats_test.go
index 02f2c0a8c..c045963f3 100644
--- a/hugolib/site_stats_test.go
+++ b/hugolib/site_stats_test.go
@@ -16,7 +16,6 @@ package hugolib
 import (
 	"bytes"
 	"fmt"
-	"io"
 	"testing"
 
 	"github.com/gohugoio/hugo/helpers"
@@ -89,14 +88,11 @@ aliases: [/Ali%d]
 		h.Sites[1].PathSpec.ProcessingStats,
 	}
 
-	stats[0].Table(io.Discard)
-	stats[1].Table(io.Discard)
-
 	var buff bytes.Buffer
 
 	helpers.ProcessingStatsTable(&buff, stats...)
 
-	c.Assert(buff.String(), qt.Contains, "Pages            | 21 |  7")
+	c.Assert(buff.String(), qt.Contains, "Pages            │ 21 │  7")
 }
 
 func TestSiteLastmod(t *testing.T) {
diff --git a/parser/pageparser/item_test.go b/parser/pageparser/item_test.go
index 36b95e93a..10dbfe895 100644
--- a/parser/pageparser/item_test.go
+++ b/parser/pageparser/item_test.go
@@ -47,3 +47,217 @@ func TestItemValTyped(t *testing.T) {
 	source = []byte("xtrue")
 	c.Assert(Item{low: 0, high: len(source)}.ValTyped(source), qt.Equals, "xtrue")
 }
+
+func TestItemBoolMethods(t *testing.T) {
+	c := qt.New(t)
+
+	source := []byte("  shortcode ")
+	tests := []struct {
+		name   string
+		item   Item
+		source []byte
+		want   bool
+		call   func(Item, []byte) bool
+	}{
+		{
+			name: "IsText true",
+			item: Item{Type: tText},
+			call: func(i Item, _ []byte) bool { return i.IsText() },
+			want: true,
+		},
+		{
+			name: "IsIndentation false",
+			item: Item{Type: tText},
+			call: func(i Item, _ []byte) bool { return i.IsIndentation() },
+			want: false,
+		},
+		{
+			name: "IsShortcodeName",
+			item: Item{Type: tScName},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeName() },
+			want: true,
+		},
+		{
+			name: "IsNonWhitespace true",
+			item: Item{
+				Type: tText,
+				low:  2,
+				high: 11,
+			},
+			source: source,
+			call:   func(i Item, src []byte) bool { return i.IsNonWhitespace(src) },
+			want:   true,
+		},
+		{
+			name: "IsShortcodeParam false",
+			item: Item{Type: tScParamVal},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeParam() },
+			want: false,
+		},
+		{
+			name: "IsInlineShortcodeName",
+			item: Item{Type: tScNameInline},
+			call: func(i Item, _ []byte) bool { return i.IsInlineShortcodeName() },
+			want: true,
+		},
+		{
+			name: "IsLeftShortcodeDelim tLeftDelimScWithMarkup",
+			item: Item{Type: tLeftDelimScWithMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsLeftShortcodeDelim() },
+			want: true,
+		},
+		{
+			name: "IsLeftShortcodeDelim tLeftDelimScNoMarkup",
+			item: Item{Type: tLeftDelimScNoMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsLeftShortcodeDelim() },
+			want: true,
+		},
+		{
+			name: "IsRightShortcodeDelim tRightDelimScWithMarkup",
+			item: Item{Type: tRightDelimScWithMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsRightShortcodeDelim() },
+			want: true,
+		},
+		{
+			name: "IsRightShortcodeDelim tRightDelimScNoMarkup",
+			item: Item{Type: tRightDelimScNoMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsRightShortcodeDelim() },
+			want: true,
+		},
+		{
+			name: "IsShortcodeClose",
+			item: Item{Type: tScClose},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeClose() },
+			want: true,
+		},
+		{
+			name: "IsShortcodeParamVal",
+			item: Item{Type: tScParamVal},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeParamVal() },
+			want: true,
+		},
+		{
+			name: "IsShortcodeMarkupDelimiter tLeftDelimScWithMarkup",
+			item: Item{Type: tLeftDelimScWithMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeMarkupDelimiter() },
+			want: true,
+		},
+		{
+			name: "IsShortcodeMarkupDelimiter tRightDelimScWithMarkup",
+			item: Item{Type: tRightDelimScWithMarkup},
+			call: func(i Item, _ []byte) bool { return i.IsShortcodeMarkupDelimiter() },
+			want: true,
+		},
+		{
+			name: "IsFrontMatter TypeFrontMatterYAML",
+			item: Item{Type: TypeFrontMatterYAML},
+			call: func(i Item, _ []byte) bool { return i.IsFrontMatter() },
+			want: true,
+		},
+		{
+			name: "IsFrontMatter TypeFrontMatterTOML",
+			item: Item{Type: TypeFrontMatterTOML},
+			call: func(i Item, _ []byte) bool { return i.IsFrontMatter() },
+			want: true,
+		},
+		{
+			name: "IsFrontMatter TypeFrontMatterJSON",
+			item: Item{Type: TypeFrontMatterJSON},
+			call: func(i Item, _ []byte) bool { return i.IsFrontMatter() },
+			want: true,
+		},
+		{
+			name: "IsFrontMatter TypeFrontMatterORG",
+			item: Item{Type: TypeFrontMatterORG},
+			call: func(i Item, _ []byte) bool { return i.IsFrontMatter() },
+			want: true,
+		},
+		{
+			name: "IsDone tError",
+			item: Item{Type: tError},
+			call: func(i Item, _ []byte) bool { return i.IsDone() },
+			want: true,
+		},
+		{
+			name: "IsDone tEOF",
+			item: Item{Type: tEOF},
+			call: func(i Item, _ []byte) bool { return i.IsDone() },
+			want: true,
+		},
+		{
+			name: "IsEOF",
+			item: Item{Type: tEOF},
+			call: func(i Item, _ []byte) bool { return i.IsEOF() },
+			want: true,
+		},
+		{
+			name: "IsError",
+			item: Item{Type: tError},
+			call: func(i Item, _ []byte) bool { return i.IsError() },
+			want: true,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.call(tt.item, tt.source)
+			c.Assert(got, qt.Equals, tt.want)
+		})
+	}
+}
+
+func TestItem_ToString(t *testing.T) {
+	c := qt.New(t)
+
+	source := []byte("src")
+	long := make([]byte, 100)
+	for i := range long {
+		long[i] = byte(i)
+	}
+
+	tests := []struct {
+		name   string
+		item   Item
+		source []byte
+		want   string
+		call   func(Item, []byte) string
+	}{
+		{
+			name: "EOF",
+			item: Item{Type: tEOF},
+			call: func(i Item, _ []byte) string { return i.ToString(source) },
+			want: "EOF",
+		},
+		{
+			name: "Error",
+			item: Item{Type: tError},
+			call: func(i Item, _ []byte) string { return i.ToString(source) },
+			want: "",
+		},
+		{
+			name: "Indentation",
+			item: Item{Type: tIndentation},
+			call: func(i Item, _ []byte) string { return i.ToString(source) },
+			want: "tIndentation:[]",
+		},
+		{
+			name: "Long",
+			item: Item{Type: tKeywordMarker + 1, low: 0, high: 100},
+			call: func(i Item, _ []byte) string { return i.ToString(long) },
+			want: "<" + string(long) + ">",
+		},
+		{
+			name: "Empty",
+			item: Item{Type: tKeywordMarker + 1},
+			call: func(i Item, _ []byte) string { return i.ToString([]byte("")) },
+			want: "<>",
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.call(tt.item, tt.source)
+			c.Assert(got, qt.Equals, tt.want)
+		})
+	}
+}
diff --git a/resources/page/page_matcher_test.go b/resources/page/page_matcher_test.go
index 2fe6ccc89..ad35da43c 100644
--- a/resources/page/page_matcher_test.go
+++ b/resources/page/page_matcher_test.go
@@ -84,19 +84,17 @@ func TestPageMatcher(t *testing.T) {
 
 	c.Run("mapToPageMatcherParamsConfig", func(c *qt.C) {
 		fn := func(m map[string]any) PageMatcherParamsConfig {
-			v, err := mapToPageMatcherParamsConfigLegacy(m)
+			v, err := mapToPageMatcherParamsConfig(m)
 			c.Assert(err, qt.IsNil)
 			return v
 		}
-		// Legacy.
 		c.Assert(fn(map[string]any{"_target": map[string]any{"kind": "page"}, "foo": "bar"}), qt.DeepEquals, PageMatcherParamsConfig{
-			Params: maps.Params{
+			Fields: maps.Params{
 				"foo": "bar",
 			},
 			Target: PageMatcher{Path: "", Kind: "page", Lang: "", Environment: ""},
 		})
 
-		// Current format.
 		c.Assert(fn(map[string]any{"target": map[string]any{"kind": "page"}, "params": map[string]any{"foo": "bar"}}), qt.DeepEquals, PageMatcherParamsConfig{
 			Params: maps.Params{
 				"foo": "bar",
@@ -134,7 +132,7 @@ func TestDecodeCascadeConfig(t *testing.T) {
 	c.Assert(err, qt.IsNil)
 	c.Assert(got, qt.IsNotNil)
 	c.Assert(got.Config.Keys(), qt.DeepEquals, []PageMatcher{{Kind: "page", Environment: "production"}, {Kind: "page"}})
-	c.Assert(got.Config.Values(), qt.DeepEquals, []maps.Params{{"a": string("av")}, {"b": string("bv")}})
+
 	c.Assert(got.SourceStructure, qt.DeepEquals, []PageMatcherParamsConfig{
 		{
 			Params: maps.Params{"a": string("av")},
diff --git a/tpl/tplimpl/shortcodes_integration_test.go b/tpl/tplimpl/shortcodes_integration_test.go
index e884c9a8d..275e8cc15 100644
--- a/tpl/tplimpl/shortcodes_integration_test.go
+++ b/tpl/tplimpl/shortcodes_integration_test.go
@@ -730,16 +730,16 @@ SHORTCODE
 	b.Assert(err.Error(), qt.Contains, `no compatible template found for shortcode "mymarkdown" in [/_shortcodes/mymarkdown.md]; note that to use plain text template shortcodes in HTML you need to use the shortcode {{% delimiter`)
 }
 
-func TestShortcodeOnlyLanguageInBaseIssue13699(t *testing.T) {
+func TestShortcodeOnlyLanguageInBaseIssue13699And13740(t *testing.T) {
 	t.Parallel()
 
 	files := `
 -- hugo.toml --
 baseURL = 'https://example.org/'
+disableLanguages = ['de']
 [languages]
 [languages.en]
 weight = 1
-disableLanguages = ['de']
 [languages.de]
 weight = 2
 -- layouts/_shortcodes/de.html --