Skip to content

fix(core): compareTo on a forward date range (next month, next quarter, next 30 days) compares with the period just before it, not with itself - #30196

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6kk1x6
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-6kk1x6

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Fixes #30140

维护者速读

  • 改了什么:指标卡开启「与上一周期对比」时,日期范围选「下月」「下季度」「未来 30 天」不再拿窗口和自己比。下月对比本月,下季度对比本季度,未来 30 天(今天起共 31 天)对比截至昨天的 31 天;趋势文字随之改为「较本月」「较本季度」「较上期」。新增两个文案键,10 种语言都已补齐。
  • 为什么改:feat(spec): add next_month, next_quarter and next_30_days date-range presets #30133 加了这三个前向预设,但前端的对比逻辑只会把「本期」换成「上期」,前向窗口原样不动,所以涨跌永远是 0%,文字还写成「较上月」。
  • 风险与回滚:只动 compare-to.ts 的一个纯函数和文案。其余 13 个预设的对比窗口与文字逐字节不变(新加对照钉住,另在 8 个参考日期上整表比对 main 与本分支一致)。回滚即 revert。
  • 先看:① ui/packages/core/src/utils/compare-to.ts 的 forwardRollingPair / shiftForwardRollingWindows(滚动窗口怎么认、怎么平移);② 同文件 PREVIOUS_PERIOD_TOKENS 为什么一次替换(两次会把下月一路推到上月);③ compareToTrendLabelKey 里前向判断排在后向判断之前。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/core/src/utils/__tests__/dashboard-filters.presetWindows-29116.test.ts,预期 16 个预设各有一条对比用例全部通过。
  • 需要你做的:无,席位落地。

Clause-②: no
Metadata-protocol: none

What changes

  • @object-ui/core shiftFilterByCompareTo, kind: 'previousPeriod'. It substituted only current_* / today tokens. The forward presets are spelled {next_month_start}…{next_month_end}, {next_quarter_start}…{next_quarter_end} and {today}…{30_days_from_now} (DATE_RANGE_PRESET_MACRO_WINDOWS), so the first two compared the window with itself and the third moved only its start ({yesterday}…{30_days_from_now}, a 32-day window overlapping the current one). Now:
    • NEXT_TO_CURRENT_TOKENS maps next_month_* / next_quarter_* to current_*. It is merged with CURRENT_TO_LAST_TOKENS into one map applied in ONE pass; a second pass would carry next_month_start on to last_month_start.
    • A {today} + {N_days_from_now} pair inside one field's operator object (or a two-member $between) moves back by its own length, N + 1 days: {(N+1)_days_ago} … {yesterday}. This runs first, so the generic today → yesterday swap never sees that {today}. A lone {today}, a lone {N_days_from_now}, and two FIELDS holding those tokens keep today's reading.
    • next_week_start / next_year_start are not mapped: the macro vocabulary has no _end partner for them, so no preset or closed window uses them.
  • compareToTrendLabelKey names the window really compared: vsThisMonth, vsThisQuarter, and vsPreviousPeriod for a forward rolling window (it read vsLastMonth, vsLastQuarter, vsYesterday). Each forward check sits before the backward pattern that also matches it (quarter_start is inside next_quarter_start).
  • @object-ui/i18n: dashboard.trend.vsThisMonth / vsThisQuarter in all ten locale packs (zh 较本月 / 较本季度). No other existing key fits: vsLastMonth names the wrong month, and vsPreviousPeriod is used where it is exact (the rolling window).
  • English fallbacks for the two keys in ObjectMetricWidget, MetricWidget (its defaults table and its phrase-to-key map) and ObjectChart's comparison-series label; plugin-dashboard/SKILL.md's label table gains the three rows.

Why

Issue #30140: a should-fix from the contract review of #30133. The governing text is the compare-to.ts docblock: "kind: 'previousPeriod' — the equal-length window immediately before the current one". No spec change, no new compareTo kind, no new preset.

Tests

  • compare-to.test.ts: next month → this month, next quarter → this quarter (also across a year boundary, from 2026-12-10), {today}…{30_days_from_now} → the 31 days ending yesterday, a $between rolling window, and the cases that must NOT move (lone tokens, two fields). Label keys for all three, under previousPeriod and previousYear.
  • dashboard-filters.presetWindows-29116.test.ts: the compareTo case now runs on all 16 presets, with a completeness pin. Each preset pins both kinds' windows and the label at 2026-07-15. Forward presets also assert, on all six reference dates, that the previous window ends the day before the current one starts (equal length for next_30_days, starting on the 1st for the calendar ones).
  • CONTROL: RETIRED_COMPARE_AT_REFERENCE holds the 13 earlier presets' windows and labels read off compare-to.ts at b7075d99 before the fix; the retired-spelling equivalence still runs on all six dates.
  • ObjectMetricWidget.compareTo.forwardPresets-30140.test.tsx (rendered): the registered object-metric block with each forward preset's window and compareTo: previousPeriod. The data source answers 120 for the current window, 100 only for the expected previous window, 7 otherwise; the tile shows 20% and the right label, and not the stale one.

Acceptance notes

  • Where the inline path is reached (measured). A dashboard metric widget is dataset-bound: DashboardRenderer routes it to DatasetWidget, and the comparison is shifted by the analytics executor (shiftRange), labelled from compareTo.kind alone (vsPreviousPeriod). shiftFilterByCompareTo runs in ObjectMetricWidget (the object-metric block) and in ObjectChart (a provider: 'object' series widget, which the dashboard date filter is broadcast into). Those are the surfaces this PR changes.
  • The executor on forward presets (measured, not changed). tsx probe of lowerDatasetCompareDateRange + inclusiveCalendarDayWindow + shiftRange with the clock at 2026-07-15: next_month 2026-08-01..08-31 → 2026-07-01..07-31; next_quarter 2026-10-01..12-31 → 2026-07-01..09-30; next_30_days 2026-07-15..08-14 → 2026-06-14..07-14. No forward window compares with itself, and next_30_days matches this PR's inline window exactly. The executor counts days, so a forward month of 30 days (2026-09-01..09-30) compares with 2026-08-02..08-31, not calendar August; that is its rule for every preset (this_month 2026-07-01..07-31 → 2026-05-31..06-30), not a forward-preset defect.
  • Pre-existing, kept by the control on purpose (out of scope): on the inline path yesterday, last_week, last_month, last_quarter and last_year compare with themselves (no last_* token is shifted; e.g. last_month at 2026-07-15 → 2026-06-01..06-30), and last_N_days compares an N + 1 day window with N days. previousYear from a leap day re-resolves at 2027-03-01, so this_month on 2028-02-29 compares with March 2027.
  • Why a rendered unit check instead of a browser screenshot. The fix is a pure function between the filter and the second aggregate. The rendered test drives the real registered block, its real aggregate calls and its real trend badge, and pins the exact previous window the browser would query. A dashboard metric widget in the console would exercise the dataset path, which this PR does not touch.

Verification

Suites, type-check, lint, ablations
  • pnpm --dir ui exec vitest run --maxWorkers=3 packages/<pkg>/ under os-verify-lock: core exit 0, i18n exit 0, plugin-dashboard exit 0 (201 files, 1,925 passed, 6 skipped), plugin-charts exit 0 (103 files, 1,333 passed, 9 skipped).
  • pnpm run type-check in core, i18n, plugin-dashboard and plugin-charts: exit 0 each.
  • npx eslint on every touched file: 0 errors (warnings only, no-explicit-any, as in the surrounding code).
  • Ablation 1 (scripts/ablation-replace.mjs): previousPeriod back to substituteTokens(filter, CURRENT_TO_LAST_TOKENS) → 11 failed / 70 passed across the three test files (every forward-window pin, the three forward 29116 rows, the three rendered rows); restored, git diff HEAD empty.
  • Ablation 2: drop the next_quarter_ label check → 3 failed (label pin, the next_quarter 29116 row, the rendered next_quarter row); restored.
  • CONTROL measured outside the tests: a tsx probe of all 16 presets × 8 reference dates × both kinds plus labels, on main and on this branch — the 13 earlier presets' output is byte-identical; only the three forward presets change.
  • Derived gates (dispatch-gates --commands, 35): see the report on Dashboard compareTo on a forward date preset (下月, 下季度, 未来 30 天) compares the window with itself #30140.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; lint passes on the touched files.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: n/a.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 section excepted).

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzijmF7yUSk5ga9jRkh

… just before it, not with itself

previousPeriod moved only current_* / today tokens, so a dashboard window of
next_month or next_quarter was compared with itself (a 0 change every time)
and next_30_days moved only its start. A forward window now steps back one
period: next_month_* to this month, next_quarter_* to this quarter, and a
{today} .. {N_days_from_now} pair to the N + 1 days ending yesterday. The
trend label names that window (vsThisMonth, vsThisQuarter, vsPreviousPeriod);
the two new keys are in all ten locale packs. The 13 backward presets keep
their windows and labels.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGufzijmF7yUSk5ga9jRkh
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC

维护者速读(席位终稿)

  • 改了什么:仪表板上直接绑对象的 KPI 块和对象图表,选「下月 / 下季度 / 未来 30 天」再开「环比」时,现在跟紧挨着它之前、等长的时间段比:下月比本月,下季度比本季度,未来 30 天比截至昨天的前 31 天。趋势标签也改成「较本月 / 较本季度 / 较上一周期」。
  • 为什么改:feat(spec): add next_month, next_quarter and next_30_days date-range presets #30133 新加了这三个向前的预设,但环比只会把「本期」往前挪,向前的窗口没挪,等于自己跟自己比,变化永远是 0,标签还写成「较上月」。
  • 风险与回滚:低。只改 compare-to.ts 一个纯函数和两个新的翻译键;原有 13 个预设的结果逐字节不变(全 16 预设 × 8 个日期探针对照)。绑定数据集的指标卡走服务端执行器,本来就对,这里不动。回滚即 revert 本 PR。
  • 先看:ui/packages/core/src/utils/compare-to.ts 的 shiftFilterByCompareTo 与 compareToTrendLabelKey。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/plugin-dashboard/src/__tests__/ObjectMetricWidget.compareTo.forwardPresets-30140.test.tsx。
  • 需要你做的:无。不是受管面,CI 全绿,席位直接落地。
  • 席位意见:ACCEPT。

席位核对 · ACCEPT · head 98450bd1b845ad39627d548fc5f65c9bb4ec94d0 · 2026-11-05T02:02Z

  1. Shape: draft, base main, head merges origin/main bedb96e0. Fixes #30140. One changeset: @object-ui/core, @object-ui/i18n, @object-ui/plugin-dashboard, @object-ui/plugin-charts patch, Clause-②: no / Metadata-protocol: none. 19 files, +466/−18. check-governed-merges --pr 30196: NOT governed (ui/packages/plugin-dashboard/SKILL.md is a package doc, not skills/**). No spec key.
  2. Read by the seat: NEXT_TO_CURRENT_TOKENS maps only the closed next_month_* / next_quarter_* pairs; both maps apply in ONE substituteTokens pass, so next_month_start cannot slide on to last_month_start. The rolling pair {today} … {N_days_from_now} is rewritten first, only when it sits in one field's $-operators or one $between, to {(N+1)_days_ago} … {yesterday} (N + 1 days, equal length; {N_days_ago} is a supported macro, date-macros.ts:36). Label checks put next_quarter_ / next_month_ before the backward patterns they contain. This matches the governing docblock (compare-to.ts ~L20, "the equal-length window immediately before the current one").
  3. Accepted deviations: file surface beyond the Claim (English fallbacks for the two keys in MetricWidget.tsx, ObjectMetricWidget.tsx, ObjectChart.tsx, the plugin-dashboard SKILL.md label table, one rendered test): needed for the keys to render, accepted. Rendered unit check instead of a browser shot: the dataset-bound dashboard metric widget does not run this path (measured: executor shiftRange already handles forward presets), and the rendered test drives the registered object-metric block with a data source that can only show +20% by querying the exact previous window. Accepted.
  4. Evidence: ablation 1 (old substitution) 11 red, ablation 2 (drop next_quarter_ label) 3 red, both restored; four package suites + type-checks green; dispatch-gates 35/35 exit 0 with --ran reconciled. CI on this head: 44 success, 11 skipped.
  5. Out of scope: the pre-existing inline defects in the Acceptance notes (backward last_* / yesterday presets compare with themselves, last_N_days length, previousYear leap day) → filed Inline compareTo previousPeriod: backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, last_N_days compares N+1 days with N #30197. Skills-docs: n/a (Metadata-protocol: none).
  6. Landing: ordinary queue, now: relay pr_ready + automerge_enable, follow to MERGED, landing record on Dashboard compareTo on a forward date preset (下月, 下季度, 未来 30 天) compares the window with itself #30140.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 02:03
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit ff12aed Nov 5, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-6kk1x6 branch November 5, 2026 02:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard compareTo on a forward date preset (下月, 下季度, 未来 30 天) compares the window with itself

1 participant