Skip to content

Fix wording on patch-family upgrade pages (4.2.2.x, 4.3.1.x)#428

Merged
vvlladd28 merged 6 commits into
thingsboard:mainfrom
trikimiki:fix-43x-upgrade-notes
Jul 15, 2026
Merged

Fix wording on patch-family upgrade pages (4.2.2.x, 4.3.1.x)#428
vvlladd28 merged 6 commits into
thingsboard:mainfrom
trikimiki:fix-43x-upgrade-notes

Conversation

@trikimiki

Copy link
Copy Markdown
Member

Description

Summary

  • Commit a2373cdcb77510b24a4de27b744e1f394e623f7d:
    • The 4.3.1.2 upgrade page said "applicable for ... any 4.3.1 patch" and "from version 4.3.1.x". But anyone on a 4.3.x release can use those steps - now both spots read 4.3.x.
    • Done via a new optional patchableFrom field on UpgradeVersion, set on the three 4.3.1.x entries. Templates fall back to baseVersion when unset, so every other upgrade page renders byte-identical to before.
image image
  • Commit 112b33c207786ee4d3927bc160d1e6848c1acd5b:
    • Previously every patch in a family said "Upgrading ... to latest 4.2.2 (4.2.2.X)" — 4.2.2.2 and 4.2.2.1 both claimed "latest". Now only the actual newest carries it, and reads "Upgrading ... to 4.2.2.2 (latest 4.2.2 patch)"; older patches drop the qualifier.
    • Done via a derived LATEST_PATCH_VERSIONS set in the model (no per-entry flag to maintain).
image image

Type of change

  • New / updated documentation page (src/content/docs/**)
  • Shared include file (src/content/_includes/**)
  • Component, layout, or styling change (src/components/**, src/styles/**)
  • Landing / use-case / case-study page (src/pages/**, src/data/**)
  • Redirects (src/data/redirects.ts)
  • Version bump / release (see release skill)
  • Build, CI, scripts, or tooling
  • Other (please describe)

Upgrade-instruction page templates (src/pages/docs/.../upgrade-instructions/[platform]/[familySlug].astro, CE + PE) and the upgrade-version data model (src/models/upgrade-instructions.ts).

Affected products

PE + CE upgrade docs

Related issues

  • Closes #

Checklist

  • pnpm check passes (Astro / TypeScript)
  • pnpm lint:eslint passes
  • pnpm lint:slugcheck passes (required if pages were added/renamed/moved across languages)
  • pnpm lint:linkcheck passes locally — required to merge; run it before requesting review (use pnpm lint:linkcheck:nobuild if you already ran a build)
  • Renamed/removed pages have a redirect in src/data/redirects.ts, and pnpm generate:redirects was run
  • No hardcoded versions — values come from src/data/versions.ts
  • Screenshots attached for visual changes

@vvlladd28 vvlladd28 changed the base branch from develop to main May 29, 2026 12:13
@vvlladd28 vvlladd28 changed the base branch from main to develop May 29, 2026 12:14
@vvlladd28 vvlladd28 changed the base branch from develop to main June 11, 2026 09:23

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed 7 changed files in Fix wording on patch-family upgrade pages (4.2.2.x, 4.3.1.x). Clean, minimal, and backward-compatible: patchableFrom is an optional override that falls back to baseVersion, and LATEST_PATCH_VERSIONS is derived so no per-entry flag needs maintaining. The page-template heading condition was also brought in line with the component condition (v.patch && v.baseVersion !== v.displayVersion && …), which incidentally fixes a prior TOC/heading mismatch for patch entries where baseVersion === displayVersion. Anchors are unaffected since headings render with an explicit id={v.anchor}. Left 1 minor comment inline.

Additional findings

These observations are about existing code outside the PR's diff — spotted while reading surrounding context.

  • src/components/upgrade-instructions/LinuxUpgradeSteps.astro:64 (and the identical line in DockerComposeUpgradeSteps.astro, DockerUpgradeSteps.astro, WindowsUpgradeSteps.astro) — const prevLabel = v.x ? v.upgradableFrom : v.upgradableFrom; is a no-op ternary: both branches return the same value, so v.x is never actually consulted. It can be simplified to const prevLabel = v.upgradableFrom;, or the intended branch behavior restored if one side was meant to differ.

This is an auto-generated review. Findings may contain errors — please verify before applying changes.

Comment thread src/models/upgrade-instructions.ts Outdated
export const LATEST_PATCH_VERSIONS: Set<string> = (() => {
const seen = new Set<string>();
const latest = new Set<string>();
for (const v of UPGRADE_VERSIONS) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This silently depends on UPGRADE_VERSIONS being ordered newest-patch-first within each baseVersion group — the first entry seen for a given baseVersion is taken as "latest." That invariant holds today (and UPGRADE_FAMILIES above relies on the same ordering), but nothing enforces it: if a future edit inserts an older patch above a newer one for the same base, the wrong version silently gets the "(latest X patch)" label with no error. Worth a one-line comment noting the newest-first requirement, or deriving "latest" via an actual version comparison rather than positional order.

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review

Two deeper findings after cross-checking against the documented release policy (src/content/_includes/docs/releases/release-policy.mdx). The first is a possible correctness issue in the upgrade guidance itself; the second is a concrete fix for the ordering fragility raised earlier.


This is an auto-generated review. Findings may contain errors — please verify before applying changes.

baseVersion: '4.3.1',
releaseDate: 'May 28 2026',
upgradableFrom: '4.2.1.x',
patchableFrom: '4.3.x',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth confirming this against the release policy before it ships. Per src/content/_includes/docs/releases/release-policy.mdx, the scheme is MAJOR.MINOR.MAINTENANCE.PATCH and the "no upgrade script" guarantee is PATCH-level only — i.e. within the same MAINTENANCE number. Here baseVersion is exactly the MAINTENANCE level, so 4.3.0 and 4.3.1 are two distinct maintenance releases.

Setting patchableFrom: '4.3.x' broadens the caution note ("if you are upgrading from version 4.3.x, DO NOT run the upgrade script") to cover users on 4.3.0.x moving to 4.3.1.x — which crosses a maintenance boundary. The policy says maintenance bumps "may require upgrade scripts" and should be done in a maintenance window (its example: 4.2.0.x → 4.2.1.0). The original 4.3.1.x scope (the baseVersion fallback) matched the policy exactly.

Caveat that cuts the other way: 4.3.1 is the Angular-20 maintenance release (release notes #14944), i.e. the 4.3 analog of 4.2.2.0, which the policy explicitly calls out as "no DB scripts required." So the broadening might be intentional and correct. But then it's inconsistent — this same PR leaves 4.2.2.x at 4.2.2.x scope (no patchableFrom). Either both Angular-maintenance lines should be broadened, or neither.

Could you confirm whether upgrade.sh has an incremental 4.3.0 → 4.3.1 step, or only the step from the 4.2.1.x LTS base? If the former, this should stay 4.3.1.x. (Same patchableFrom: '4.3.x' is set on all three 4.3.1.x entries — lines 52, 69, 87.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you confirm whether upgrade.sh has an incremental 4.3.0 → 4.3.1 step, or only the step from the 4.2.1.x LTS base? If the former, this should stay 4.3.1.x. (Same patchableFrom: '4.3.x' is set on all three 4.3.1.x entries — lines 52, 69, 87.)

yep, upgrade script is not needed, and that was actually the main focus of these changes.
current upgrade notes say "do not run starting from 4.3.1.x" - user upgrading from 4.3.0.x may misinterpret that script is required, run upgrade.sh, see "cannot upgrade this version" and wonder how to proceed.
actually - there were real customer portal tickets with questions on such confusions.

Caveat that cuts the other way: 4.3.1 is the Angular-20 maintenance release (release notes #14944), i.e. the 4.3 analog of 4.2.2.0, which the policy explicitly calls out as "no DB scripts required."
...
But then it's inconsistent — this same PR leaves 4.2.2.x at 4.2.2.x scope (no patchableFrom). Either both Angular-maintenance lines should be broadened, or neither.

a) separate Angular note exists, warning the user about this breaking change - also, 'patchableFrom' is strictly about "should you run upgrade.sh or not"
b) 4.2.x upgrades were consciously untouched since current cautions/notes are valid; additionally, these changes to upgrade pages were done with defaults and fallbacks to ensure they work in the same way as before in case 'patchableFrom' wasnt provided

Comment thread src/models/upgrade-instructions.ts Outdated
export const LATEST_PATCH_VERSIONS: Set<string> = (() => {
const seen = new Set<string>();
const latest = new Set<string>();
for (const v of UPGRADE_VERSIONS) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concrete suggestion for the ordering dependency flagged earlier — derive "latest" by comparing version components rather than relying on array position, so an out-of-order insert can't silently mislabel the newest patch:

function cmpVersion(a: string, b: string): number {
	const pa = a.split('.').map(Number);
	const pb = b.split('.').map(Number);
	for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
		const d = (pa[i] ?? 0) - (pb[i] ?? 0);
		if (d) return d;
	}
	return 0;
}

export const LATEST_PATCH_VERSIONS: Set<string> = (() => {
	const byBase = new Map<string, string>(); // baseVersion → newest version
	for (const v of UPGRADE_VERSIONS) {
		if (!v.patch || !v.baseVersion) continue;
		const cur = byBase.get(v.baseVersion);
		if (!cur || cmpVersion(v.version, cur) > 0) byBase.set(v.baseVersion, v.version);
	}
	return new Set(byBase.values());
})();

If you'd rather keep the positional approach, at least add a one-line comment stating the "newest patch listed first within each baseVersion" invariant, since nothing currently enforces it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imho, the concern is overblown: the upgrade instructions should be already ordered, since adding out-of-order version on top of the file is not something that could happen (new release = version number increments) or should happen (you logically never "upgrade" to older version, and the document is also always logically read bottom-to-top).
apart from that the "silent error" is factually incorrect: even if an out-of-order entry did slip in, UPGRADE_FAMILIES dedups by position, while table components render UPGRADE_VERSIONS in array order - so misorder would surface directly in the rendered table and get caught immediately, not silently.

i had Claude trace the consumers to confirm - the newest-first order is already relied on by UPGRADE_FAMILIES and every table/step component; so ordering is already load-bearing across all the UPGRADE_VERSIONS consumers, not just this Set.

since that is such a corner case and ordering - although already logical - is not enforced, i think adding comment will suffice - will commit it asap

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done: 5de6f49

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed 7 changed files in Fix wording on patch-family upgrade pages (4.2.2.x, 4.3.1.x). Left 4 comment(s) inline.

The logic is correct: LATEST_PATCH_VERSIONS picks exactly 4.3.1.2 / 4.3.0.1 / 4.2.2.2 / 4.2.1.2, the patchableFrom fallback keeps every untouched entry rendering as before, anchors are unchanged, and aligning the page-template TOC condition with the component condition even fixes a pre-existing TOC/heading mismatch for the 4.3.1 and 4.2.2 entries (where baseVersion === displayVersion). The inline comments are about keeping this maintainable — the same wording now lives in six copies, and the newest-first ordering the derived set depends on is enforced only by a comment.

Additional findings

These observations are about existing code outside the PR's diff — spotted while reading surrounding context.

  • src/components/upgrade-instructions/EdgeDockerUpgradeSteps.astro:26 (also EdgeLinux/EdgeWindows and TrendzUpgradeSteps) — The Edge and Trendz upgrade-steps components still use the old to latest ${baseVersion} (${displayVersion}) heading pattern this PR moves away from. Today neither model has two patches in one baseVersion group, so the "two versions both claim latest" bug doesn't manifest there yet — but the first time Edge or Trendz ships a second patch it will, and in the meantime readers see two different heading styles across products. Fine if deliberately scoped to TB CE/PE, but worth a follow-up to port the LATEST_PATCH_VERSIONS approach to the edge/trendz models too.

This review was auto-generated. Findings may contain errors — please verify before applying changes.

const needsCompat = needsCompatibilityCheck(v.version);
const heading = v.patch && v.baseVersion !== v.displayVersion
? `Upgrading ThingsBoard${isPE ? ' PE' : ''} to latest ${v.baseVersion} (${v.displayVersion})`
const heading = v.patch && v.baseVersion !== v.displayVersion && LATEST_PATCH_VERSIONS.has(v.version)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This three-condition heading ternary is now copy-pasted in six places: the four *UpgradeSteps components and both [familySlug].astro page templates. The page-template copy feeds the TOC while the component copy renders the h2, so they must stay character-identical or the TOC silently drifts from the page — and this PR itself shows how easily they diverge (the old page templates lacked the baseVersion !== displayVersion guard the components had). Worth extracting a helper into the model, e.g. getUpgradeHeading(v, productLabel), so the wording lives in exactly one place.

Comment thread src/models/upgrade-instructions.ts Outdated
* Relies on UPGRADE_VERSIONS being newest-first per baseVersion (see note there) —
* the first entry seen for each baseVersion is taken as the latest.
*/
export const LATEST_PATCH_VERSIONS: Set<string> = (() => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The derived set leans on the newest-first ordering invariant, and today that invariant is enforced only by the (nicely written) comment above. Since this all runs at build time, a cheap guard would make it self-enforcing — either throw at module load when a later entry within a baseVersion group has a numerically higher version, or sidestep ordering entirely by picking the max patch per baseVersion with a small version comparison instead of "first seen wins". The order-independent variant also removes one of the three bullet points a future editor has to keep in their head.

<div class="starlight-aside__content">
{v.patch ? (
<p>If you are upgrading from {v.upgradableFrom}, you <strong>must</strong> run the script below. However, if you are upgrading from version {`${v.baseVersion}.x`}, <strong>DO NOT</strong> run the upgrade script; proceed directly to starting the service.</p>
<p>If you are upgrading from {v.upgradableFrom}, you <strong>must</strong> run the script below. However, if you are upgrading from version {v.patchableFrom ?? v.baseVersion + '.x'}, <strong>DO NOT</strong> run the upgrade script; proceed directly to starting the service.</p>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two readability nits on this expression (applies to the same line in all four components): ?? mixed with + reads ambiguously at a glance — parenthesizing or keeping the template-literal form (`${v.baseVersion}.x`) would help — and the same field falls back to bare v.baseVersion in the note aside above but to v.baseVersion + '.x' here. So patchableFrom substitutes for a plain base version in one sentence and for an .x pattern in another; a future author who sets patchableFrom: '4.4' (mirroring the baseVersion style) would silently drop the .x from this caution. The interface doc-comment does explain the dual role, but a tiny pair of helpers in the model (e.g. patchFamilyLabel(v) / patchScriptLabel(v)) would make both call sites uniform and misuse-proof.

const heading = v.patch && v.baseVersion !== v.displayVersion && LATEST_PATCH_VERSIONS.has(v.version)
? `Upgrading ThingsBoard${isPE ? ' PE' : ''} to ${v.displayVersion} (latest ${v.baseVersion} patch)`
: `Upgrading ThingsBoard${isPE ? ' PE' : ''} to ${v.displayVersion}`;
const prevLabel = v.x ? v.upgradableFrom : v.upgradableFrom;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not introduced by this PR, but it sits directly between the lines being changed in all four components: this ternary has identical branches, so it's just const prevLabel = v.upgradableFrom;. Since the PR already touches the adjacent lines in every copy, folding this cleanup in would be nearly free.

# Conflicts:
#	src/components/upgrade-instructions/DockerComposeUpgradeSteps.astro
#	src/components/upgrade-instructions/DockerUpgradeSteps.astro
#	src/components/upgrade-instructions/LinuxUpgradeSteps.astro
#	src/components/upgrade-instructions/WindowsUpgradeSteps.astro
#	src/models/upgrade-instructions.ts
#	src/pages/docs/installation/upgrade-instructions/[platform]/[familySlug].astro
#	src/pages/docs/pe/installation/upgrade-instructions/[platform]/[familySlug].astro
… fix to Edge and Trendz

- new src/models/upgrade-shared.ts: getUpgradeHeading(), patchFamilyLabel(),
  patchScriptLabel(), latestPatchPerBaseline(), assertNewestFirst()
- TB components/pages generate h2 and TOC headings via the single helper
  (was 6 hand-synced copies) and drop the dead prevLabel ternary
- assertNewestFirst() fails the build on an out-of-order version insert
  instead of relying on a comment
- Edge and Trendz get the same heading format, latest-patch-per-baseline
  filtering, patchableFrom support (Edge 4.3.1.1 -> 4.3.x) and the guard
@vvlladd28 vvlladd28 merged commit a767d42 into thingsboard:main Jul 15, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants