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..bf5af726a7 --- /dev/null +++ b/docs/changes/unreleased/1809-tz-stable-tasks-heading-test.md @@ -0,0 +1,26 @@ +--- +kind: internal +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: + - >- + 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. 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/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) 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 {