translate check over a source tree and reports each finding as
|}
-{{prevnext|Standalone binary|Development}}
+{{prevnext}}
diff --git a/docs/Configuration.wikitext b/docs/Configuration.wikitext
index 5f85d733d..125b2b2e8 100644
--- a/docs/Configuration.wikitext
+++ b/docs/Configuration.wikitext
@@ -222,4 +222,4 @@ docker run --rm --entrypoint cat ghcr.io/chaotic-ground/wikven extensions/Wikven
It is also in the [https://github.com/chaotic-ground/wikven/blob/main/default.yml repository].
-{{prevnext|Troubleshooting|Standalone binary}}
+{{prevnext}}
diff --git a/docs/Deploying.wikitext b/docs/Deploying.wikitext
index 9b3cc62fa..f9d7ea236 100644
--- a/docs/Deploying.wikitext
+++ b/docs/Deploying.wikitext
@@ -182,4 +182,4 @@ A commit that follows a branch is the same manifest with the source
This site is the working example. Its manifests are in [updatecli/updatecli.d/] (one release tag per reference pin, one branch head per commit pin), and [dist/ under a /wikven/ path prefix,
Versions follow [https://www.conventionalcommits.org/ Conventional Commits], which release-please uses to generate the changelog and bump the version.
-{{prevnext|Commands|Writing an extension}}
+{{prevnext}}
diff --git a/docs/Editing sidebar.wikitext b/docs/Editing sidebar.wikitext
index 71de7bc39..5b7d6f679 100644
--- a/docs/Editing sidebar.wikitext
+++ b/docs/Editing sidebar.wikitext
@@ -55,4 +55,4 @@ Some default sidebar blocks need a live wiki, so {{SITENAME}} drops them from th
* [My photo.png, that is File:My photo.png.wikitext.
-{{prevnext|Editing sidebar|Skins}}
+{{prevnext}}
diff --git a/docs/Installation.wikitext b/docs/Installation.wikitext
index 534574f29..4485a46b8 100644
--- a/docs/Installation.wikitext
+++ b/docs/Installation.wikitext
@@ -61,4 +61,4 @@ This produces a local wikven image you run exactly like the publish
Once you have {{SITENAME}}, head to [[default are loaded: a static site has no logged
These docs ship one such gadget, PersistTabber, which remembers whether you pick the Docker or the binary tab in the install steps. Every other tabber on the page switches to the tab you picked, and the choice is still there when you move to another page.
-{{prevnext|Lua modules|Searching}}
+{{prevnext}}
diff --git a/docs/Licenses.wikitext b/docs/Licenses.wikitext
index 9538c9bf1..b6f1d8946 100644
--- a/docs/Licenses.wikitext
+++ b/docs/Licenses.wikitext
@@ -24,4 +24,4 @@ __TOC__
The standalone binary is compiled with [https://frankenphp.dev/ FrankenPHP], which also links several Caddy modules; see the FrankenPHP project for their licenses. The Docker image keeps MediaWiki's own COPYING, and each component's full license text is available from its project.
-{{prevnext|prev=Writing an extension}}
+{{prevnext}}
diff --git a/docs/Lua modules.wikitext b/docs/Lua modules.wikitext
index 0cc623886..ec209e4cd 100644
--- a/docs/Lua modules.wikitext
+++ b/docs/Lua modules.wikitext
@@ -195,4 +195,4 @@ A refused build says so in its exit status as well as in words, so a script that
This documentation site uses a module itself, and the page you are reading is baked with the image on every change, with the module's answer asserted to be in it. The binary is held to the same rule on a source tree of its own, x86-64 on every change and arm64 on every nightly, so each row of the table above is something a machine checks rather than something this page claims.
-{{prevnext|Extensions|JavaScript}}
+{{prevnext}}
diff --git a/docs/Module:Sequence.lua b/docs/Module:Sequence.lua
new file mode 100644
index 000000000..0f7cdee04
--- /dev/null
+++ b/docs/Module:Sequence.lua
@@ -0,0 +1,91 @@
+-- The order of the documentation, read from the one place it is written down.
+--
+-- MediaWiki:Sidebar lists the pages in order, and Template:prevnext used to list them again as its
+-- arguments. Nothing checked that the two agreed, and twice they did not: a page was added to the
+-- sidebar without a chain entry, and the chain's last page was left with no row at all (#455). Both
+-- are the same failure -- adding a page meant editing three files and forgetting one was invisible.
+--
+-- The direction is forced. MediaWiki:Sidebar is read line by line by the skin rather than parsed as
+-- wikitext, so it cannot call anything; but it is an ordinary page, so anything can read it.
+--
+-- What the sidebar contributes is order alone. The link labels stay where they were, in
+-- Template:prevnext/label, which reads each target's own translated title.
+--
+-- Named with the .lua marker, which Special:MyLanguage/Lua modules describes without recommending:
+-- the page is Module:Sequence.lua, and Template:prevnext spells the invoke that way.
+local p = {}
+
+-- Where the order lives, and the entry line to read out of it: "** Special:MyLanguage/Page|key".
+local SIDEBAR = 'MediaWiki:Sidebar'
+local ENTRY = '^%*%*%s*Special:MyLanguage/([^|\n]+)'
+
+-- Both sidebar groups, in one sequence. The chain has always crossed from the docs group into the
+-- references group, and a reader at the end of the first group is better sent on than stopped.
+local function sequence()
+ local sidebar = mw.title.new(SIDEBAR)
+ local content = sidebar and sidebar:getContent()
+ local pages = {}
+ if content then
+ for line in mw.text.gsplit(content, '\n') do
+ local target = line:match(ENTRY)
+ if target then
+ pages[#pages + 1] = mw.text.trim(target)
+ end
+ end
+ end
+ return pages
+end
+
+-- Where this page sits in the sequence, or nil if the sidebar does not name it -- which is every
+-- page that is not a step of the documentation. The main page is one of those: it is where a reader
+-- arrives rather than a step they walk, and it names its own way on in its opening lines.
+--
+-- A translation counts as its source page: "Searching/ko" is the sidebar's "Searching" in another
+-- language, and the sidebar names the page it was translated from. Matched as a prefix rather than
+-- by asking the title for its root, because rootText only strips a subpage where the namespace has
+-- them turned on, and this one does not -- there, "Searching/ko" is one whole title. Anchoring on
+-- the names the sidebar gives keeps that out of it: no entry is another entry plus a slash.
+local function positionOf(pages, here)
+ for i, page in ipairs(pages) do
+ if here == page or here:sub(1, #page + 1) == page .. '/' then
+ return i
+ end
+ end
+ return nil
+end
+
+-- The page on either side of this one, or nil at an end of the sequence.
+local function neighbour(step)
+ local pages = sequence()
+ -- Loud rather than empty. A row that renders as nothing is the failure this module exists to
+ -- end, and it is the invisible kind: the wrapper div is still there and the page still looks
+ -- finished. If the sidebar stops being readable, that should stop a build, not reach a reader.
+ if #pages == 0 then
+ error(SIDEBAR .. ' has no "** Special:MyLanguage/Page" entries to read the order from', 0)
+ end
+ local here = positionOf(pages, mw.title.getCurrentTitle().text)
+ return here and pages[here + step] or nil
+end
+
+local function link(frame, page, before, after)
+ local label = frame:expandTemplate{ title = 'prevnext/label', args = { page } }
+ return before .. '[[Special:MyLanguage/' .. page .. '|' .. label .. ']]' .. after
+end
+
+-- The row: a previous link, a next link, or both. A page the sidebar does not name gets neither,
+-- which is what every page outside the sequence -- a template, a category, a File: page -- should
+-- get if it ever calls this.
+function p.row(frame)
+ local out = {}
+ local previous = neighbour(-1)
+ if previous then
+ out[#out + 1] = ''
+ end
+ local following = neighbour(1)
+ if following then
+ out[#out + 1] = ''
+ end
+ return table.concat(out)
+end
+
+return p
diff --git a/docs/Pages.wikitext b/docs/Pages.wikitext
index 2a7b7e2f2..82c5112de 100644
--- a/docs/Pages.wikitext
+++ b/docs/Pages.wikitext
@@ -87,4 +87,4 @@ saved as Template:Note.wikitext and used on a page as:
renders as "'''Note:''' Back up your wiki first." Templates take positional ({{{1}}} ) and named parameters and run [Search.
-{{prevnext|JavaScript|Translating}}
+{{prevnext}}
diff --git a/docs/Skins.wikitext b/docs/Skins.wikitext
index 4f3243d66..b246babb4 100644
--- a/docs/Skins.wikitext
+++ b/docs/Skins.wikitext
@@ -153,4 +153,4 @@ The other six the skin reads off the configuration itself, so they behave as doc
What the inert ones would have configured, {{SITENAME}} supplies its own way where it can: the colour theme is on the Settings page the build writes in place of Special:MobileOptions, and the site's own navigation is written into the main menu of every rendered page.
-{{prevnext|Images|Extensions}}
+{{prevnext}}
diff --git a/docs/Standalone binary.wikitext b/docs/Standalone binary.wikitext
index a65fae12e..ca51b8f22 100644
--- a/docs/Standalone binary.wikitext
+++ b/docs/Standalone binary.wikitext
@@ -133,4 +133,4 @@ The binary renders on its own, but uses these host programs when they are presen
{{note|Fetching over HTTPS, Wikimedia Commons images via InstantCommons and any [[ca-certificates package). A site with only local content and images needs no network access.}}
-{{prevnext|Configuration|Commands}}
+{{prevnext}}
diff --git a/docs/Template:prevnext.wikitext b/docs/Template:prevnext.wikitext
index e1b050d03..13fc49e8a 100644
--- a/docs/Template:prevnext.wikitext
+++ b/docs/Template:prevnext.wikitext
@@ -1,11 +1,7 @@
Module:Sequence.lua reads it: the page before this one in the sidebar becomes the previous
+link, the page after it the next. The first and last pages of the sequence get one link rather than
+two, without having to say so, and a page the sidebar does not name gets no row at all.
-The next page is the last positional argument, so one of them is a next and two are a previous and
-a next. The last page of the chain is the one form that leaves out: it has a previous and no next,
-and an empty second argument reads the same as an absent one, so there is no positional way to say
-it. That page names prev= instead, which is otherwise the same previous link.
+It used to take the page names, which meant the order was written down twice -- once as the
+sidebar's and once across twenty-one calls -- with nothing checking that the two agreed. Twice they
+did not: a page reached the sidebar without a call naming it, and the last page of the chain had no
+row, because a lone argument read as the *next* step and there was no way to spell "nothing after
+this". Adding a page meant three edits, and forgetting the third was invisible. Now it is two: the
+sidebar, and a {{prevnext}} at the foot of the new page.
+
+The main page has no row. It is not a sidebar entry -- every skin already points its logo at it --
+and it is where a reader arrives rather than a step they walk, so it names its own way on in its
+opening lines instead.
== Translated pages ==
A prevnext sits outside the <translate> tags, so it is copied whole into every
-translation of the page it is on. That is why it takes page names alone: a label passed in would be
-a second copy of the target's title in every page that links to it, and one nothing checks, so
-renaming a page would leave the links to it quietly wrong in every language.
+translation of the page it is on. That is why no label is written into the call, and was not when
+the call still took page names: a label passed in would be a second copy of the target's title in
+every page that links to it, and one nothing checks, so renaming a page would leave the links to it
+quietly wrong in every language.
Template:prevnext/label reads that title instead, from the one place it is translated:
the target's title unit, which the build writes to Translate's "Page display title"
diff --git a/docs/Translating.wikitext b/docs/Translating.wikitext
index cd30f70b1..3232f8aa9 100644
--- a/docs/Translating.wikitext
+++ b/docs/Translating.wikitext
@@ -147,4 +147,4 @@ $ docker run --rm -v "$PWD/src:/workspace/src" ghcr.io/chaotic-ground/wikven tra
The [https://github.com/chaotic-ground/wikven/tree/main/actions/check-translations check-translations action] runs the same check on pull requests, reporting each one as an annotation on the file it belongs to. It fails the build on a broken source page and never on a translation that is behind; pass gate: false to report even a broken page without failing. This documentation site is checked this way. To bring a translation back up to date, edit it to match the new source, then translate stamp it again.
-{{prevnext|Searching|Deploying}}
+{{prevnext}}
diff --git a/docs/Troubleshooting.wikitext b/docs/Troubleshooting.wikitext
index 7a3f53570..948341536 100644
--- a/docs/Troubleshooting.wikitext
+++ b/docs/Troubleshooting.wikitext
@@ -75,4 +75,4 @@ Every parse of a page embedding a Commons image asks Commons for its thumbnail.
On a site with several skins, each is rendered by its own process and the build reports each one that failed by name (build failed for skin ...). The output of each pass is printed under its own heading, so read the section for the named skin rather than the end of the log. If the machine is short of memory, WIKVEN_BUILD_JOBS=1 runs the passes one at a time; see [[