Skip to content

Fix usage day-boundary timezone handling and add configurable usage ranges with smoother UI updates#96

Open
rwang23 wants to merge 4 commits into
TianyiDataScience:mainfrom
rwang23:fix/usage-timezone-range
Open

Fix usage day-boundary timezone handling and add configurable usage ranges with smoother UI updates#96
rwang23 wants to merge 4 commits into
TianyiDataScience:mainfrom
rwang23:fix/usage-timezone-range

Conversation

@rwang23

@rwang23 rwang23 commented Apr 15, 2026

Copy link
Copy Markdown

PR Title

Fix usage day-boundary timezone handling and add configurable usage ranges with smoother UI updates

English PR Description

Summary

This PR fixes a long-standing mismatch between the configured UI timezone and the actual day-boundary logic used by Usage & Cost reporting.

Before this change, several usage-related calculations still implicitly followed UTC-style date slicing, which caused "today", token usage, and estimated cost to drift away from the dashboard's configured timezone. In deployments using UI_TIMEZONE=America/New_York (or any non-UTC timezone), this could make the Usage page show misleading numbers around day rollover.

This PR also expands the Usage page from a binary Today / Cumulative view into a more practical operator workflow with:

  • Today
  • Yesterday
  • This week
  • Last week
  • Last 7 days
  • Last 30 days
  • Cumulative
  • custom date range selection

In addition, the usage range controls now update the Usage page without a full page reload, while preserving the existing server-rendered architecture and visual language.

Why this change matters

From an operator perspective, this was not just a presentation issue.

The dashboard already exposes a configurable UI_TIMEZONE, and absolute timestamps in the UI respect it. However, several usage and cost rollups still derived day buckets with toISOString().slice(0, 10) or equivalent UTC-oriented logic. That created a trust gap:

  • the dashboard said it was showing Eastern Time
  • but "today" usage and cost were still effectively grouped by UTC boundaries in some places

For teams using the dashboard to monitor real spend, token burn, and subscription windows, this made the Usage page harder to trust during the most sensitive time: around daily rollover and reporting cutoffs.

This PR aligns the reporting logic with the configured timezone and makes the page much more useful for real operational review.

What changed

1. Use configured timezone for usage day boundaries

The following usage-related paths now derive calendar days from UI_TIMEZONE rather than UTC date slicing:

  • usage period aggregation
  • runtime usage event day bucketing
  • subscription fallback windows derived from runtime usage
  • commander digest date assignment

A shared timezone-aware date formatter was added so the logic stays centralized and consistent.

2. Add configurable usage range selection

The Usage page now supports richer range selection through both UI state and backend aggregation:

  • today
  • yesterday
  • this_week
  • last_week
  • last_7d
  • last_30d
  • cumulative
  • custom with usage_start / usage_end

The backend now returns a selected range summary and a selected-range breakdown so the page no longer has to fake this behavior on the frontend.

3. Keep the page responsive without full reloads

Range selection now updates the Usage section via lightweight fetch-based partial replacement instead of a full page refresh.

This keeps the current architecture intact:

  • still server-rendered
  • no new client framework
  • no new dependencies
  • URL remains shareable/bookmarkable
  • back/forward navigation still works

4. Improve the custom range UI without introducing a new library

The new date-range controls were styled using the existing design system and CSS patterns already present in the repo.

The result is intentionally incremental rather than disruptive:

  • same visual language as current pills/cards/buttons
  • better spacing and field grouping
  • dark mode support
  • mobile-friendly stacking
  • no third-party date picker added

5. Keep English and Chinese copy aligned

All newly introduced range controls and usage descriptions were wired through the existing bilingual copy helpers, so the feature works cleanly in both English and Chinese.

Files changed

Runtime / data logic

  • src/runtime/ui-timezone.ts
  • src/runtime/usage-cost.ts
  • src/runtime/commander-digest.ts

UI / interaction

  • src/ui/server.ts

Tests

  • test/ui-timezone.test.ts
  • test/usage-cost.test.ts
  • test/ui-render-smoke.test.ts

Implementation notes

Timezone handling

This PR intentionally does not hardcode a specific business timezone in usage logic.

Instead, all relevant day-boundary calculations now flow from the existing configured UI_TIMEZONE value, which keeps behavior consistent with the rest of the dashboard and with deployment-specific expectations.

Range model

The backend now exposes selected-range data separately from the fixed reference windows (today, 7d, 30d), allowing the UI to:

  • show the operator-selected range as the primary snapshot
  • still preserve fixed reference cards for orientation and comparison

This avoids overloading the original period cards with too many responsibilities.

Progressive enhancement

The partial update behavior for usage ranges is implemented as a small client-side enhancement layered on top of the existing server-rendered page. If JavaScript behavior changes in the future, the page still has a clear server-side URL model underneath.

Backward compatibility

This PR is designed to be low-risk for existing deployments:

  • no new dependency introduced
  • no migration required
  • existing UI_TIMEZONE config continues to work
  • existing usage page routes remain valid
  • cumulative remains available as before
  • fallback behavior remains explicit when data sources are not connected

Validation

Typecheck

  • npx tsc -p tsconfig.json --noEmit

Focused tests

  • npm test -- --test test/ui-timezone.test.ts test/usage-cost.test.ts test/ui-render-smoke.test.ts

Additional checks performed

  • verified PM2 restart and app returns online
  • confirmed updated UI renders after restart
  • verified new usage ranges and timezone-aware day bucketing behavior through tests

Test coverage added

This PR adds or updates coverage for:

  • timezone-aware calendar-day derivation
  • usage day-boundary behavior under configured timezone
  • yesterday
  • last_week
  • custom date range aggregation
  • updated usage page rendering expectations

Risks / tradeoffs

The largest behavioral change here is intentional: any deployment that previously looked like it was using one timezone while actually grouping usage by UTC will now report according to the configured timezone. That is the desired outcome, but it may cause some numbers near day boundaries to shift compared with previous behavior.

I believe that is the correct fix, because it aligns the dashboard with its stated configuration and improves operator trust.

What I did not change

To keep this PR reviewable and merge-friendly, I did not:

  • introduce a new date-picker library
  • refactor the entire dashboard rendering model
  • change unrelated timezone defaults in docs/config beyond what was needed for correctness
  • alter unrelated sections outside Usage/Cost and digest day assignment

Suggested reviewer focus

The most important review points are:

  1. Whether UI_TIMEZONE is the right source of truth for usage day boundaries
  2. Whether the new usage range model is a good fit for the existing server-rendered dashboard
  3. Whether the partial-refresh enhancement feels acceptable within the current architecture

Operator impact

After this PR:

  • Usage numbers reflect the configured timezone more truthfully
  • teams can inspect yesterday / weekly / custom windows directly
  • switching usage ranges feels much faster
  • the page remains visually consistent with the existing product
  • no additional dependency burden is introduced

Screenshots / demo

Recommended to capture during review:

  • Usage page with Today
  • Usage page with Yesterday
  • Usage page with custom range
  • same page in English and Chinese
  • confirmation around a non-UTC timezone deployment such as America/New_York

Closing note

This change is meant to improve one of the most trust-sensitive surfaces in the dashboard: usage and cost reporting. I tried to keep the implementation faithful to the current architecture, low-risk to deploy, and practical for real operators instead of adding surface-level UI only.


中文 PR 描述

概述

这个 PR 修复了 Usage & Cost 报表中一个长期存在的问题:界面已经支持配置 UI_TIMEZONE,但部分“按天统计”的用量与费用逻辑仍然隐式按 UTC 边界在算,导致“今天”的 token 用量、费用和真实团队时区不一致。

在使用 UI_TIMEZONE=America/New_York 或其他非 UTC 时区的部署中,这会让 Usage 页面在跨日附近显示出误导性的数字,影响运营判断。

在修复时区边界问题的同时,这个 PR 也把 Usage 页面从原本只有 Today / Cumulative 两种口径,扩展成更贴近真实运营场景的范围查看方式:

  • Today
  • Yesterday
  • This week
  • Last week
  • Last 7 days
  • Last 30 days
  • Cumulative
  • 自定义时间范围

此外,时间范围切换现在支持局部刷新,不再每次都整页重载,同时保持现有服务端渲染架构和视觉风格不变。

为什么这个改动重要

这不只是一个展示层的小问题,而是一个“数据可信度”问题。

当前控制台已经有 UI_TIMEZONE 配置,UI 中绝对时间显示也是按这个配置来的;但如果“今天”的用量与费用仍然按 UTC 切日,用户看到的会是:

  • 页面看起来是美东时间
  • 但 Usage 数字却在某些地方还是按 UTC 在结算

对依赖这个面板看 token 消耗、成本走势和订阅窗口的团队来说,这会直接削弱 Usage 页面的可信度,尤其是在每天切日和预算观察最敏感的时刻。

这个 PR 的核心目标,就是让报表逻辑和配置的时区真正一致,并把 Usage 页做成更适合日常运营使用的形态。

改动内容

1. 让 Usage 的“按天边界”真正跟随配置时区

以下 Usage 相关逻辑现在都改为基于 UI_TIMEZONE 推导自然日,而不再直接用 UTC 风格的日期截取:

  • usage period 聚合
  • runtime usage event 的 day 分桶
  • 基于 runtime usage 回填的 subscription fallback 窗口
  • commander digest 的日期归属

同时新增了统一的时区日期格式化工具,避免各处重复实现和逻辑漂移。

2. 扩展 Usage 范围选择

Usage 页面现在支持以下范围:

  • today
  • yesterday
  • this_week
  • last_week
  • last_7d
  • last_30d
  • cumulative
  • custom,通过 usage_start / usage_end 指定自定义范围

后端也同步增加了“选中范围”的 summary 和 breakdown,因此这不是纯前端切换,而是真正的数据聚合能力扩展。

3. 范围切换改为局部刷新,不再整页 reload

现在点击 Usage 范围按钮或提交自定义时间范围时,会通过 fetch 拉取页面片段并替换 Usage 区块,而不是整页刷新。

这样做的好处是:

  • 仍然保持服务端渲染
  • 不引入新的前端框架
  • 不增加第三方依赖
  • URL 仍然可分享、可收藏
  • 浏览器前进/后退仍然可用

4. 优化自定义时间选择 UI,不引入新库

这次没有引入新的日期选择库,而是直接复用当前项目已有的样式体系进行增强。

结果是一个比较克制的增量优化:

  • 视觉风格和现有 pills / cards / buttons 保持一致
  • 日期输入区布局更清晰
  • 深色模式支持补齐
  • 移动端会自动变为单列
  • 不增加额外依赖负担

5. 保持中英文文案一致

所有新增加的时间范围标签、说明文案和使用提示都接入了现有双语文案体系,确保英文和中文界面都能正常工作,不会出现一边完整、一边像临时文案的情况。

涉及文件

运行时 / 数据逻辑

  • src/runtime/ui-timezone.ts
  • src/runtime/usage-cost.ts
  • src/runtime/commander-digest.ts

UI / 交互

  • src/ui/server.ts

测试

  • test/ui-timezone.test.ts
  • test/usage-cost.test.ts
  • test/ui-render-smoke.test.ts

实现说明

时区处理

这个 PR 没有把某个具体业务时区硬编码到 Usage 逻辑里。

相反,所有相关的“按天边界”计算都统一从已有配置 UI_TIMEZONE 取值,这样逻辑上与整个控制台保持一致,也能适应不同部署环境。

范围模型

后端现在把“选中范围数据”和原有固定参考窗口(today / 7d / 30d)拆开返回,使 UI 可以:

  • 把用户当前选中的范围作为主展示
  • 同时保留固定参考窗口用于对照

这样既增强了灵活性,也避免把原始 period 卡片塞入过多职责。

渐进增强

局部刷新的实现是一个很轻量的客户端增强层,叠加在现有服务端页面之上。如果将来客户端逻辑调整,底层仍然保留完整、可直达的服务端 URL 模型。

兼容性

这个 PR 设计上尽量保持低风险:

  • 不引入新依赖
  • 不需要迁移
  • 原有 UI_TIMEZONE 配置继续生效
  • 原有 Usage 路由仍然可用
  • cumulative 视图仍然保留
  • 数据源未连接时的 fallback 行为仍然明确可见

验证方式

类型检查

  • npx tsc -p tsconfig.json --noEmit

聚焦测试

  • npm test -- --test test/ui-timezone.test.ts test/usage-cost.test.ts test/ui-render-smoke.test.ts

额外验证

  • 确认 PM2 重启后服务恢复 online
  • 确认最新 UI 能正常渲染
  • 通过测试验证新的范围选择和时区分日逻辑

新增 / 更新测试覆盖

本 PR 补充或更新了以下覆盖:

  • 时区感知的自然日推导
  • 配置时区下的 Usage day-boundary 行为
  • yesterday
  • last_week
  • 自定义时间范围聚合
  • Usage 页面渲染断言更新

风险与取舍

这次最大的行为变化是有意为之:如果某个部署此前“看起来像是在某个时区展示”,但实际统计却按 UTC 切日,那么这个 PR 会让数字改为真正跟随配置时区。

这可能会让跨日附近的数字与旧行为不一样,但我认为这是正确修复,因为它让系统行为终于和它宣称的配置保持一致,也提升了运营层面对数据的信任。

我刻意没有做的事情

为了让这个 PR 更容易审查和合并,我没有做以下扩展:

  • 不引入新的日期选择器库
  • 不重构整个 dashboard 渲染架构
  • 不顺手改动与本问题无关的默认时区文档/配置
  • 不扩大修改范围到 Usage / Cost 以外的无关模块

建议审查关注点

建议 reviewer 重点关注:

  1. UI_TIMEZONE 是否应该成为 Usage“按天边界”的唯一可信来源
  2. 新的 usage range 数据模型是否符合当前 server-rendered dashboard 架构
  3. 这种局部刷新实现是否足够轻量、合理,并与现有架构兼容

对运营侧的影响

合并后:

  • Usage 数字会更真实地反映配置时区
  • 团队可以直接查看昨天、本周、上周、近 7 天、近 30 天和自定义窗口
  • 切换 Usage 范围会更快
  • 页面仍保持现有产品风格
  • 不会增加额外依赖负担

截图 / 演示建议

建议在 review 时附上:

  • Today 视图
  • Yesterday 视图
  • 自定义时间范围视图
  • 英文和中文界面对比
  • 一个非 UTC 部署(例如 America/New_York)下的效果确认

结语

这个改动主要针对控制台里最需要“被信任”的页面之一:Usage 与 Cost 报表。我尽量把实现控制在现有架构之内,保持低风险、易部署,并让它真正对运营者有用,而不是只做表层 UI 改动。


Reviewer Note

Primary intent: make usage/cost reporting truly respect the configured timezone and improve operator trust around day rollovers, while keeping the implementation low-risk and aligned with the existing server-rendered architecture.

Testing Checklist

  • npx tsc -p tsconfig.json --noEmit
  • npm test -- --test test/ui-timezone.test.ts test/usage-cost.test.ts test/ui-render-smoke.test.ts
  • Restarted PM2 process and verified app returned online
  • Verified usage range controls render correctly
  • Verified English and Chinese copy for newly added usage range UI
  • Verified range switching no longer requires a full page reload
  • Verified custom date range works with existing UI style and dark mode

@rwang23

rwang23 commented Apr 15, 2026

Copy link
Copy Markdown
Author

Thanks for reviewing this PR.

This one is pretty focused. The main thing I wanted to fix is a trust issue on the Usage page: the dashboard already has a configurable UI_TIMEZONE, but some usage/cost day-boundary logic was still effectively behaving like UTC date slicing. Because of that, “today” usage and estimated cost could look off around rollover time.

So the goals here were basically:

  • make usage/cost really follow the configured timezone
  • make the page more useful in day-to-day operations with ranges like yesterday / this week / last week / custom
  • improve the interaction a bit without pulling in new dependencies or turning this into a big frontend refactor

A few quick notes in case they help while reviewing:

  • I used the existing UI_TIMEZONE as the source of truth instead of hardcoding any business timezone
  • the range switching is still built on top of the current server-rendered setup, just with a small progressive enhancement so it no longer needs a full page refresh
  • I intentionally didn’t add a date-picker library, because that felt like too much extra surface area for a correctness-focused change
  • I also added coverage around timezone-aware day bucketing, custom ranges, and the updated usage-page rendering

If there’s one thing I’d love sanity-checked most, it’s whether UI_TIMEZONE is the right source of truth for usage day boundaries in this project. If that assumption makes sense, the rest of the change should be fairly straightforward.


感谢看这个 PR。

这次改动我其实收得比较紧,主要就是想修一个 Usage 页面的“可信度问题”:项目已经有可配置的 UI_TIMEZONE,但之前部分 usage / cost 的按天统计逻辑实际上还是更像按 UTC 在切,所以在跨日附近,“today”的用量和费用会有点不对劲。

所以这次的目标其实很直接:

  • 让 usage / cost 真正跟着配置时区走
  • 让页面在日常运营里更好用一点,比如直接看昨天、本周、上周、自定义范围
  • 在不引新依赖、不做大前端重构的前提下,把交互顺手优化一下

补几个 reviewer 可能会关心的点:

  • 这里没有把某个业务时区写死,而是统一走现有的 UI_TIMEZONE
  • 时间范围切换还是基于现在的服务端渲染结构做的,只是在上面加了一层很轻的增强,所以不需要整页刷新
  • 我刻意没加新的 date-picker 库,因为这次还是想把范围控制在“正确性修复 + 小幅体验增强”
  • 对应测试也补了,覆盖了时区感知的按天分桶、自定义范围,以及 Usage 页面的渲染

如果 reviewer 想先抓主线,我觉得最值得先确认的一点就是:在这个项目里,UI_TIMEZONE 应不应该作为 Usage 按天统计的统一来源。这个前提如果 OK,后面的实现基本就是顺着现有架构往下补齐。

@TianyiDataScience TianyiDataScience left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the detailed write-up here. The core direction makes sense to me: using the configured UI_TIMEZONE for day-boundary math and expanding the Usage view beyond today / cumulative is a reasonable improvement for operators.

I’m requesting a trim before I can take this one as-is, because the PR currently bundles in a couple of unrelated behavior/config changes:

  • it flips the repo default UI_TIMEZONE from UTC to America/New_York
  • it adds deployment-specific notes/config for opendashboard.ecomstack.net / Cloudflare / nginx / PM2

Those pieces are much more environment-specific than the underlying usage fix, and the default-timezone change would also alter behavior for installs that never set UI_TIMEZONE explicitly.

If you keep the timezone-aware usage aggregation + richer range selection in this PR, and drop the default-timezone flip plus the deployment-specific notes/config, I’m happy to re-check quickly. The main thing I want to preserve is that this lands as a focused product/runtime fix rather than mixing in one team’s deployment defaults.

@rwang23

rwang23 commented Apr 15, 2026

Copy link
Copy Markdown
Author

Sorry about that. We initially made the change while deploying and testing locally, so some local environment-specific config ended up in the PR.

We have now trimmed those parts out:

  • restored the repo default UI_TIMEZONE to UTC
  • removed the deployment-specific notes/config

We also fixed the Usage range interaction so it now stays on the current page and updates via partial refresh, instead of falling through to a full page reload.

The PR is now focused on the timezone-aware usage aggregation and the richer usage range support only.

I also merged the latest upstream/main into this branch and resolved the test/usage-cost.test.ts conflict by keeping both the new upstream coverage and the timezone/range coverage in this PR.

Verification run locally before updating the PR:

  • npx tsc -p tsconfig.json --noEmit
  • npm test -- --test test/config-defaults.test.ts test/ui-timezone.test.ts test/usage-cost.test.ts test/ui-render-smoke.test.ts

@rwang23
rwang23 force-pushed the fix/usage-timezone-range branch from 31265a2 to 87e26de Compare April 15, 2026 19:42

@TianyiDataScience TianyiDataScience left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for the quick follow-up. I re-checked the updated PR metadata and patch against the previous requested changes:

  • the branch is now mergeable against main
  • the changed-file set is back to the usage/runtime/UI/test scope only
  • the repo default timezone is covered by the new test/config-defaults.test.ts and remains UTC
  • I do not see the earlier deployment-specific Cloudflare/nginx/PM2 material in the current changed files

So the trim I asked for looks addressed from the diff. The remaining blocker is just verification on the exact branch: this automation workspace cannot fetch the PR head right now (FETCH_HEAD is sandbox-blocked in the worktree, and an isolated /tmp fetch cannot resolve github.com), and GitHub is not reporting status checks for 87e26dee407ac48abbb046c90b4fc0f57aecf3de.

Next step on my side is a local/CI test pass on the exact head commit before approving or merging. Thanks again for narrowing the PR back to the core usage-timezone change.

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