From 53d1be4baccfdc8f84c71adb25ad983a0bda1bb2 Mon Sep 17 00:00:00 2001 From: ursasi Date: Sun, 11 Oct 2026 23:16:57 +0800 Subject: [PATCH 1/3] tasks: pin the tasks-heading test clock to local zone so it is $TZ-stable (#1808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The window buckets days in the viewer's local zone on purpose (usageBucketStart, and the "THE DAY IS THE WRITER'S" law on SpendToday). The test built its fixture clock in a fixed -04:00 zone and asserted the chat manual quotes the rendered `since aug 11` verbatim, so once that clock was read as local on a host far enough east it crossed midnight and rendered `aug 12` instead — green on a UTC runner, red on a UTC+8 box. Build the fixture clock in time.Local so it agrees with the bucketing; verified identical under TZ from Pago_Pago to Kiritimati. --- internal/tui3/tallyrows_test.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/internal/tui3/tallyrows_test.go b/internal/tui3/tallyrows_test.go index 9f0284f936..d674aabb66 100644 --- a/internal/tui3/tallyrows_test.go +++ b/internal/tui3/tallyrows_test.go @@ -210,7 +210,15 @@ func TestTheTasksFootStillCountsEveryPieceByItsActualState(t *testing.T) { // branches and the corpus is asked for what they produced, so neither heading can // be restated as a literal that drifts. func TestTheManualQuotesBothTasksOpeningHeadingsExactly(t *testing.T) { - loc := time.FixedZone("fixture", -4*60*60) + // The window buckets days in the viewer's LOCAL zone, on purpose: spend and + // subtask counts are attributed to the writer's own calendar day (session's + // usageBucketStart, and the "THE DAY IS THE WRITER'S" law on SpendToday). So + // the fixture clock has to live in that same zone, or the rendered `since` + // date — which the chat manual quotes verbatim — shifts by a day on any host + // where a fixed foreign offset crosses midnight once read as local. A + // `-04:00` clock did exactly that: green on a UTC runner, red on a UTC+8 box, + // `aug 11` versus `aug 12` (#1808). + loc := time.Local now := time.Date(2026, time.August, 25, 13, 11, 0, 0, loc) win := session.LastDays(now, 15) From 56bc429f61a371ae9bf13787d12dea80a69e56c2 Mon Sep 17 00:00:00 2001 From: ursasi Date: Sun, 11 Oct 2026 23:19:20 +0800 Subject: [PATCH 2/3] tasks: change entry for the $TZ-stable tasks-heading test (#1809) --- .../1809-tz-stable-tasks-heading-test.md | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md diff --git a/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md b/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md new file mode 100644 index 0000000000..991aed76c0 --- /dev/null +++ b/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md @@ -0,0 +1,22 @@ +--- +kind: internal +title: the tasks-heading manual test pins its clock to the local zone, so it no longer fails east of UTC +pr: 1809 +surface: [chat] +invalidates: + - >- + TestTheManualQuotesBothTasksOpeningHeadingsExactly was believed to be a + deterministic, host-independent test. It built its fixture clock in a fixed + -04:00 zone while the tasks window buckets days in the viewer's LOCAL zone + (usageBucketStart, and the "THE DAY IS THE WRITER'S" law on SpendToday), so + the rendered `since aug 11` the chat manual quotes rendered as `aug 12` on a + host far enough east that the foreign offset crossed midnight — green on a + UTC runner, red on a UTC+8 box (#1808). The fixture clock is now built in + time.Local, so the test reads the same date under every $TZ. +--- + +No product behavior changed: the window following the viewer's local calendar +day is deliberate and stays. Only the test was host-dependent, because it +injected a fixed -04:00 clock and then asserted a manual string that only +matched in the author's timezone band. Verified identical green from +Pacific/Pago_Pago (UTC-11) to Pacific/Kiritimati (UTC+14). From 038863b51c1b7a72bb1f7d891a29dc106841986a Mon Sep 17 00:00:00 2001 From: ursasi Date: Sun, 11 Oct 2026 23:35:03 +0800 Subject: [PATCH 3/3] tasks: pin every tasks-page fixture clock to local zone, not just one (#1808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The same fixed -04:00 fixture clock lived in five tasks-page test files, and the window buckets days in the viewer's local zone, so two tests already failed on a UTC+8 box — TestTheManualQuotesBothTasksOpeningHeadingsExactly on 'aug 11' vs 'aug 12', and TestTheTasksPageGroupsByWhatYouDoNext on the 'shift+← aug 2 – aug 25 →' window control. One shared cause (#176's lesson), so fix it everywhere: build each fixture clock in time.Local. Verified green across the full span of host zones from Pago_Pago to Kiritimati. --- .../1809-tz-stable-tasks-heading-test.md | 30 +++++++++++-------- internal/tui3/tasksconversation_test.go | 2 +- internal/tui3/tasksparked_test.go | 2 +- internal/tui3/tasksplace_test.go | 8 ++--- internal/tui3/taskstable_test.go | 2 +- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md b/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md index 991aed76c0..bf5af726a7 100644 --- a/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md +++ b/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md @@ -1,22 +1,26 @@ --- kind: internal -title: the tasks-heading manual test pins its clock to the local zone, so it no longer fails east of UTC +title: the tasks-page test fixtures pin their clocks to the local zone, so they no longer fail east of UTC pr: 1809 surface: [chat] invalidates: - >- - TestTheManualQuotesBothTasksOpeningHeadingsExactly was believed to be a - deterministic, host-independent test. It built its fixture clock in a fixed - -04:00 zone while the tasks window buckets days in the viewer's LOCAL zone - (usageBucketStart, and the "THE DAY IS THE WRITER'S" law on SpendToday), so - the rendered `since aug 11` the chat manual quotes rendered as `aug 12` on a - host far enough east that the foreign offset crossed midnight — green on a - UTC runner, red on a UTC+8 box (#1808). The fixture clock is now built in - time.Local, so the test reads the same date under every $TZ. + The tasks-page tests were believed to be deterministic and host-independent. + They built their fixture clocks in a fixed -04:00 zone while the tasks window + buckets days in the viewer's LOCAL zone (usageBucketStart, and the "THE DAY + IS THE WRITER'S" law on SpendToday), so a date the tests assert — the chat + manual's `since aug 11`, and the `shift+← aug 2 – aug 25 →` window control — + shifted by a day once the foreign offset was read as local on a host far + enough east: green on a UTC runner, red on a UTC+8 box (#1808). Every + tasks-page fixture clock is now built in time.Local, so the tests read the + same dates under every $TZ. --- No product behavior changed: the window following the viewer's local calendar -day is deliberate and stays. Only the test was host-dependent, because it -injected a fixed -04:00 clock and then asserted a manual string that only -matched in the author's timezone band. Verified identical green from -Pacific/Pago_Pago (UTC-11) to Pacific/Kiritimati (UTC+14). +day is deliberate and stays. The tests were host-dependent because each fixture +injected a fixed -04:00 clock and then asserted dates that only matched in the +author's timezone band — one shared cause across tallyrows_test.go, +tasksplace_test.go, tasksparked_test.go, tasksconversation_test.go and +taskstable_test.go, two of whose tests were already red on a UTC+8 box. +Verified identical green from Pacific/Pago_Pago (UTC-11) to Pacific/Kiritimati +(UTC+14). diff --git a/internal/tui3/tasksconversation_test.go b/internal/tui3/tasksconversation_test.go index 5da0d9e55d..ef82f108f9 100644 --- a/internal/tui3/tasksconversation_test.go +++ b/internal/tui3/tasksconversation_test.go @@ -33,7 +33,7 @@ import ( // is the depth the old page could not draw. `thor clips` finished yesterday and // has nothing under it but one row. func tasksChatFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.September, 6, 13, 0, 0, 0, loc) ago := func(d time.Duration) time.Time { return now.Add(-d) } diff --git a/internal/tui3/tasksparked_test.go b/internal/tui3/tasksparked_test.go index 0ce2cfa5e5..6f8cf23972 100644 --- a/internal/tui3/tasksparked_test.go +++ b/internal/tui3/tasksparked_test.go @@ -21,7 +21,7 @@ import ( // one piece of work waiting on a person, and two admitted behind it that nothing // has started. func tasksParkedFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.September, 2, 23, 52, 0, 0, loc) row := session.SessionRow{ID: "room-a", Title: "The Task Surface", Project: "codeaf", Open: true, Live: false} entry := func(id, label, status string) session.TaskIndexEntry { diff --git a/internal/tui3/tasksplace_test.go b/internal/tui3/tasksplace_test.go index 86296145a8..f7a265262a 100644 --- a/internal/tui3/tasksplace_test.go +++ b/internal/tui3/tasksplace_test.go @@ -17,7 +17,7 @@ import ( var tasksWidths = []int{60, 80, 120, 200} func tasksFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.August, 25, 13, 11, 0, 0, loc) at := func(day, hour int) time.Time { return time.Date(2026, time.August, day, hour, 0, 0, 0, loc) } entry := func(id, label, status string, ended time.Time, cost float64) session.TaskIndexEntry { @@ -381,7 +381,7 @@ func TestTasksChangedSinceCountsOnlyLandedWorkAfterTheLook(t *testing.T) { // that landed today with three workers under it, and a second root beside it // that is nobody's family. func tasksFamilyFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.August, 25, 13, 11, 0, 0, loc) at := func(hour int) time.Time { return time.Date(2026, time.August, 25, hour, 0, 0, 0, loc) } row := session.SessionRow{ID: "room-a", Title: "The Split", Project: "codeaf", Open: true} @@ -573,7 +573,7 @@ func TestVisibleChildrenKeepTheirOwnSectionsWhenTheRootIsAbsent(t *testing.T) { // detail beside each — and arriving OUT of time order, which is what the world // scan hands this place. func tasksPolishFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.September, 2, 23, 3, 0, 0, loc) done := func(id, label, outcome string, files int, cost float64, ago time.Duration) session.TaskIndexEntry { return session.TaskIndexEntry{ @@ -956,7 +956,7 @@ func TestTheTaskPageNamesTheConversationItCameOutOf(t *testing.T) { // [homeName], whose title case has raised the id's first letter), so the // composer keys on the trimmed title equal to the row's own id, folded. func TestAConversationWithNoTitleIsCalledTheWordNotItsId(t *testing.T) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.September, 11, 14, 25, 0, 0, loc) id := "de9ea39e6f4c18c3" transcript := "/private/tmp/htw/codeaf/" + id + "/transcript.jsonl" diff --git a/internal/tui3/taskstable_test.go b/internal/tui3/taskstable_test.go index 382c085fc5..bbf6766508 100644 --- a/internal/tui3/taskstable_test.go +++ b/internal/tui3/taskstable_test.go @@ -18,7 +18,7 @@ import ( // their headings, and the rows inside one of them have three different prices so // that descending is distinguishable from arriving. func tasksTableFixture() (session.World, session.UsageWindow, time.Time) { - loc := time.FixedZone("fixture", -4*60*60) + loc := time.Local // #1808: the window buckets in local time, so a fixed foreign offset would shift the dates by $TZ now := time.Date(2026, time.September, 11, 12, 0, 0, 0, loc) ago := func(d time.Duration) time.Time { return now.Add(-d) } work := func(room, id, label, status string, ended time.Time, files int, cost float64) session.TaskIndexEntry {