fix(desktop): pin the transcript baseline fixture's locale too

Pinning TZ fixed one ambient input to the captured-markup fixture and missed
a second one. The session-boundary divider formats its date with a bare
`toLocaleString()` (`AgentSessionTranscriptChrome.tsx`), which is
locale-sensitive as well as zone-sensitive: the fixture holds
"6/14/2026, 7:05:00 PM", and under `LANG=de-DE` the same render produces
"14.6.2026, 19:05:00". Node takes its default locale from LANG/LC_ALL, so
the byte-for-byte comparison failed on any machine with a non-US locale —
the same class of bug as the timezone one, one variable over, and the TZ
guard passed happily while it happened.

Assigning `process.env.LANG` at runtime does not fix this: node resolves its
default locale once at startup, so unlike `TZ` (which `Date` reads lazily)
the assignment is silently ineffective. Pinning the locale means overriding
the two formatting surfaces the render path reaches — `Intl.DateTimeFormat`
constructed without an explicit locale, and `Date.prototype.toLocale*`,
which does not route through `Intl.DateTimeFormat` and needs its own patch.

`Intl.DateTimeFormat` must be replaced with a plain function, not an arrow:
the render path calls it with `new`, and an arrow function is not a constructor.

The guard assertion now covers locale and `toLocaleString` output alongside
the zone, so a future drift fails with a specific message instead of a 12KB
markup diff.

Verified: the de-DE failure reproduces before this change, and the suite
passes after it under TZ/LANG combinations UTC/en-US, UTC/de-DE,
America/New_York/ja-JP, Asia/Tokyo/de-DE, and UTC with LANG unset.

Signed-off-by: ss-dev-01 <11939edb7df583f855dbef923f2358f1184538f88ca452e19e7e35e42ad6d796@buzz.block.builderlab.xyz>
Co-authored-by: Bradley Axen <baxen@squareup.com>
Signed-off-by: Bradley Axen <baxen@squareup.com>
This commit is contained in:
ss-dev-01
2026-08-21 20:05:01 -07:00
parent b03e1759a5
commit e6c994a69e

View File

@@ -20,15 +20,54 @@ import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { after, afterEach, before, test } from "node:test";
// The captured markup embeds locally-formatted timestamps ("… at 7:00:01 PM"),
// because `formatTranscriptTimestampTitle` formats in the ambient zone. Pin the
// zone so the fixture is reproducible on any developer machine and in CI (which
// runs UTC) — otherwise the byte-for-byte comparison fails purely on the
// capturer's offset. Must be set before the transcript modules are imported:
// their `Intl.DateTimeFormat` instances are module-level constants that resolve
// the zone once, at construction.
// The captured markup embeds formatted dates and times, so the fixture is only
// reproducible if every ambient formatting input is pinned. Two of them bite:
//
// - **Zone.** `formatTranscriptTimestampTitle` formats in the ambient zone
// ("… at 7:00:01 PM"), so a capture at UTC-7 fails against CI's UTC.
// - **Locale.** The session-boundary divider uses a bare `toLocaleString()`
// (`AgentSessionTranscriptChrome.tsx`), which is locale-sensitive as well as
// zone-sensitive: "6/14/2026, 7:05:00 PM" becomes "14.6.2026, 19:05:00"
// under de-DE. Node derives its default locale from LANG/LC_ALL, so this
// varies by machine independently of the zone.
//
// `TZ` can be set here because `Date` reads it lazily. The locale CANNOT: node
// resolves its default locale once at startup, so assigning `process.env.LANG`
// at runtime has no effect (verified — it silently keeps the startup locale).
// Pinning it therefore means overriding the two formatting surfaces the render
// path can reach: `Intl.DateTimeFormat` when constructed with no explicit
// locale, and `Date.prototype.toLocale*`, which does NOT route through
// `Intl.DateTimeFormat` and so needs its own patch.
//
// All of this must happen before the transcript modules are imported: their
// `Intl.DateTimeFormat` instances are module-level constants that resolve zone
// and locale once, at construction.
process.env.TZ = "UTC";
const FIXTURE_LOCALE = "en-US";
const OriginalDateTimeFormat = Intl.DateTimeFormat;
// A plain function, not an arrow: the render path calls
// `new Intl.DateTimeFormat(...)`, and an arrow function is not a constructor.
// Returning a genuine instance keeps `new`, plain calls, and `instanceof` all
// working.
function LocalePinnedDateTimeFormat(locales, options) {
return new OriginalDateTimeFormat(locales ?? FIXTURE_LOCALE, options);
}
LocalePinnedDateTimeFormat.prototype = OriginalDateTimeFormat.prototype;
LocalePinnedDateTimeFormat.supportedLocalesOf =
OriginalDateTimeFormat.supportedLocalesOf.bind(OriginalDateTimeFormat);
Intl.DateTimeFormat = LocalePinnedDateTimeFormat;
for (const method of [
"toLocaleString",
"toLocaleDateString",
"toLocaleTimeString",
]) {
const original = Date.prototype[method];
Date.prototype[method] = function (locales, options) {
return original.call(this, locales ?? FIXTURE_LOCALE, options);
};
}
import { JSDOM } from "jsdom";
const BASELINE_MARKUP = JSON.parse(
@@ -740,14 +779,24 @@ test("default and compactPreview markup is unchanged by the conversation variant
});
test("the byte-for-byte baseline actually exercises every renderable item kind", async () => {
// The fixture embeds formatted timestamps, so a zone other than the one it was
// captured in fails the comparison above for a reason that has nothing to do
// with markup. Fail loudly and specifically instead.
// The fixture embeds formatted dates and times, so a zone or locale other than
// the one it was captured in fails the comparison above for a reason that has
// nothing to do with markup. Fail loudly and specifically instead.
assert.equal(
new Intl.DateTimeFormat().resolvedOptions().timeZone,
"UTC",
"the baseline fixture is captured in UTC — see the TZ pin at the top of this file",
);
assert.equal(
new Intl.DateTimeFormat().resolvedOptions().locale,
FIXTURE_LOCALE,
`the baseline fixture is captured in ${FIXTURE_LOCALE} — see the locale pin at the top of this file`,
);
assert.equal(
new Date("2026-06-14T19:05:00.000Z").toLocaleString(),
"6/14/2026, 7:05:00 PM",
"Date.prototype.toLocaleString must be locale-pinned too — the session-boundary divider uses it",
);
// Guards the fixture itself: a baseline that silently stopped covering a kind
// would keep passing the comparison above while protecting nothing. Asserted
// against `default`, which renders every kind.