authorgravatar for me@t3.ggTheo Browne <me@t3.gg> 2026-08-24 12:42:33-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-24 12:42:33-07:00
log5f1147cad72197d7315a2fd073c30d7ffe1767f2
treeede54202e19b75a050d9c01016df2b853bcf7023
parentf035a0f4cdf4abaa6704673af7b5a4a321149ba2
signature Signed by PGP key B5690EEEBB952194

fix(web): detect outdated nightly servers (#8124)


2 files changed, 31 insertions(+), 8 deletions(-)

apps/web/src/versionSkew.test.ts+19
......@@ -50,6 +50,25 @@ describe("versionSkew", () => {
5050 expect(resolveVersionMismatch("0.0.34")).toBeNull();
5151 });
5252
53 it.each(["0.0.34-nightly.20260823.1124", "0.0.34-nightly.20260824.1124"])(
54 "warns when nightly server %s is behind a nightly client on the same release",
55 (serverVersion) => {
56 branding.APP_VERSION = "0.0.34-nightly.20260824.1125";
57
58 expect(resolveVersionMismatch(serverVersion)).toEqual({
59 clientVersion: "0.0.34-nightly.20260824.1125",
60 serverVersion,
61 hint: MISMATCH_HINT,
62 });
63 },
64 );
65
66 it("does not warn when a nightly server is ahead on the same release", () => {
67 branding.APP_VERSION = "0.0.34-nightly.20260824.1125";
68
69 expect(resolveVersionMismatch("0.0.34-nightly.20260824.1126")).toBeNull();
70 });
71
5372 it("treats a nightly server built past the client as ahead, not skew", () => {
5473 expect(resolveVersionMismatch("0.0.35-nightly.20260818.1124")).toBeNull();
5574 });
apps/web/src/versionSkew.ts+12-8
......@@ -33,13 +33,11 @@ function versionCore(version: string): string {
3333 * The skew a user can act on: the connected server runs an older T3 Code than
3434 * this client, so the server is the side that needs updating.
3535 *
36 * Versions compare as semver on their core `major.minor.patch` only. Nightlies
37 * are `<core>-nightly.<date>.<run>` builds of the release they precede, so a
38 * stable client on a nightly server (or the reverse, at the same core) shares a
39 * wire contract and is not skew. A server *ahead* of the client is not skew
40 * either: the client is the stale side, and every consumer of this result tells
41 * the user to update their server. Versions that do not parse as semver fall
42 * back to plain string inequality.
36 * Two nightly builds compare their full versions, including the date and run.
37 * Other combinations compare their core `major.minor.patch` only, so a stable
38 * build and a nightly build with the same core do not cause an update warning.
39 * A server ahead of the client does not need an update. Versions that do not
40 * parse as semver fall back to plain string inequality.
4341 */
4442export function resolveVersionMismatch(
4543 serverVersion: string | null | undefined,
......@@ -52,9 +50,15 @@ export function resolveVersionMismatch(
5250
5351 const clientCore = versionCore(normalizedClientVersion);
5452 const serverCore = versionCore(normalizedServerVersion);
53 const compareNightlyBuilds =
54 parseSemver(normalizedClientVersion)?.prerelease[0] === "nightly" &&
55 parseSemver(normalizedServerVersion)?.prerelease[0] === "nightly";
5556 const serverIsBehind =
5657 parseSemver(clientCore) && parseSemver(serverCore)
57 ? compareSemverVersions(serverCore, clientCore) < 0
58 ? compareSemverVersions(
59 compareNightlyBuilds ? normalizedServerVersion : serverCore,
60 compareNightlyBuilds ? normalizedClientVersion : clientCore,
61 ) < 0
5862 : normalizedServerVersion !== normalizedClientVersion;
5963 if (!serverIsBehind) {
6064 return null;