You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Inline compareTo previousPeriod: backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, last_N_days compares N+1 days with N #30197
Category: ① product defect with a named location. ⛔ Not a claim. Found while reviewing PR #30196 (#30140), measured by its executor and recorded in that PR's ## Acceptance notes; the PR keeps these presets unchanged on purpose (its CONTROL), so they need their own card.
Business reason. A sales manager puts a 上月 (last month) filter on a dashboard and turns on "compare to previous period" for an object-bound KPI tile or chart. The change reads 0% every time, because the comparison query runs over the same month again. Nobody can see whether last month was better or worse than the month before it, which is the point of the comparison.
Where.ui/packages/core/src/utils/compare-to.ts, shiftFilterByCompareTo with kind: 'previousPeriod'. It moves current_* / today tokens back one period (and, after #30196, next_* and the forward rolling pair), but no last_* / yesterday token. Reached through the object-metric block (ObjectMetricWidget) and provider: 'object' series in ObjectChart, which the dashboard date filter is broadcast into. The dataset-bound dashboard metric widget uses the analytics executor (shiftRange) and is not affected.
reach: named real producer: the dashboard date-range presets lowered through DATE_RANGE_PRESET_MACRO_WINDOWS (spec) into the filter these widgets read. Measured by a tsx probe of shiftFilterByCompareTo over all 16 presets at 2026-07-15 (PR #30196 Acceptance notes); not yet run in a browser. First touch should click it once.
yesterday, last_week, last_month, last_quarter, last_year compare with themselves: last_month at 2026-07-15 resolves 2026-06-01..06-30 for both the current and the comparison query.
last_N_days compares an N + 1 day window with an N day window.
previousYear from a leap day re-resolves at 2027-03-01, so this_month on 2028-02-29 compares with March 2027 instead of February 2027.
Expected. The governing docblock (compare-to.ts ~L20): "kind: 'previousPeriod' — the equal-length window immediately before the current one". So 上月 compares with the month before it, last_N_days with the N days before it, and previousYear on 2028-02-29 with February 2027. The trend label names that window. Pin all 16 presets in dashboard-filters.presetWindows-29116.test.ts.
Reader.domain:ui, one PR on ui/packages/core/src/utils/compare-to.ts plus tests and locale keys if a label is added; starts after PR #30196 lands (same function).
Duplicate search (open and closed): compareTo previousPeriod 23 hits, previousPeriod itself 12 hits, compare-to leap 2 hits. None covers the inline path's backward presets: #30140 is the forward half; #17550 and #18245 are the dataset executor (shiftRange), closed.
Read fresh. In compare-to.ts's shiftFilterByCompareTo (previousPeriod), the backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, so the change always reads 0%. last_N_days also compares N+1 days with N. A dashboard number that is wrong every time, so p2.
Claim: PM loop round 2
Session: session_01WGufzijmF7yUSk5ga9jRkh
Account: os-support-ai (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/project-thread-6kk1x6 (restarted from origin/main after PR #30196 merged)
Worktree: objectstack-issue-30197
Domain: domain:ui
Seat: domain:ui#1
File surface: ui/packages/core/src/utils/compare-to.ts and its tests, dashboard.trend.* locale keys and their three English fallback sites (MetricWidget.tsx, ObjectMetricWidget.tsx, ObjectChart.tsx), one rendered test in plugin-dashboard, one root .changeset/ file; ⛔ no spec or executor change (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (p2, judgment card), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: the ui inline compare-to resolver (compare-to.ts) produces the wrong window | no platform path covers the inline path (the dataset executor serves only dataset-bound widgets) | any author of an object-metric block or provider: 'object' chart with a backward preset and compare-to, today
Thread-read: 6647512564
Premise: holds
Serial constraints cleared: PR #30196 (#30140, same function) merged ff12aed37b; no open PR touches compare-to.ts (read this act).
MERGED 31260ca8e4 at 2026-11-05T04:04:31Z, via the merge queue (seat ACCEPT 6648916725)
card
closed completed by Fixes #30197
content on origin/main
lastPeriodBefore and two-way rollingPair in ui/packages/core/src/utils/compare-to.ts
Backward date presets (last week / month / quarter / year, rolling {N_days_ago} windows) now get a 环比 comparison window, with the leap-day clamp on previousYear.
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:ui·pm:queue· 2026-11-05T01:57Z⛔ Not a claim.
compare-to.ts'sshiftFilterByCompareTo(previousPeriod), the backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, so the change always reads 0%.last_N_daysalso compares N+1 days with N. A dashboard number that is wrong every time, so p2.Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC
Claim: PM loop round 2
Session:
session_01WGufzijmF7yUSk5ga9jRkhAccount:
os-support-ai(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/project-thread-6kk1x6(restarted fromorigin/mainafter PR #30196 merged)Worktree:
objectstack-issue-30197Domain:
domain:uiSeat:
domain:ui#1File surface:
ui/packages/core/src/utils/compare-to.tsand its tests,dashboard.trend.*locale keys and their three English fallback sites (MetricWidget.tsx,ObjectMetricWidget.tsx,ObjectChart.tsx), one rendered test in plugin-dashboard, one root.changeset/file; ⛔ no spec or executor change (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(p2, judgment card),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the ui inline compare-to resolver (
compare-to.ts) produces the wrong window | no platform path covers the inline path (the dataset executor serves only dataset-bound widgets) | any author of anobject-metricblock orprovider: 'object'chart with a backward preset and compare-to, todayThread-read: 6647512564
Premise: holds
Serial constraints cleared: PR #30196 (#30140, same function) merged
ff12aed37b; no open PR touchescompare-to.ts(read this act).objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzijmF7yUSk5ga9jRkh
os-dev-report
Stamp: 2026-11-05T03:12Z
{ "card": 30197, "pr": 30246, "branch": "claude/project-thread-6kk1x6", "head": "0dd85e008576e016891e91882b98abdf43bc7161", "gates": { "derived_reconciliation": "dispatch-gates --ran: 35 derived, 35 run, 0 NOT-MEASURED (derived set re-read on the merged head: same 35)", "derived": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-sdui-manifest.mjs :: exit 0", "node scripts/check-sdui-manifest.mjs --self-test :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "node scripts/release-pending-publish.mjs --self-test :: exit 0", "pnpm --dir ui exec vitest run scripts/ :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:closed-ceremony-text :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:gitlink-declared :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:registry-log-declared :: exit 0", "pnpm check:rest-log-spy-declared :: exit 0", "pnpm check:skill-identifier-liveness :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:undeclared-dep-imports :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0" ], "local_suites": [ "pnpm --dir ui exec vitest run --maxWorkers=3 packages/core/ :: exit 0 (232 files, 4321 passed, 27 skipped)", "pnpm --dir ui exec vitest run --maxWorkers=3 packages/plugin-dashboard/ :: exit 0 (202 files, 1928 passed, 6 skipped)", "type-check @object-ui/core, @object-ui/plugin-dashboard :: exit 0 each", "npx eslint on the touched files :: 0 errors", "ablation past tokens (lastPeriodBefore not called) :: 12 failed / 80 passed, restored", "ablation backward rolling (ROLLING_DAYS_RE limited to from_now) :: 6 failed / 86 passed, restored", "ablation leap day (previousYear clamp removed) :: 12 failed / 80 passed, restored", "ablation label (past-token label check removed) :: 6 failed / 86 passed, restored", "post-merge of origin/main (4 commits, head 0dd85e00): the 4 compareTo test files + core fieldRules.test.ts :: exit 0 (149 tests)" ] }, "line_budget": { "diff": "+331 / -74, 5 files", "breakdown": "compare-to.ts +121/-56; tests +197/-18 across 3 files; changeset +13", "ratcheted_files_touched": "none" }, "deviations": [ "CONTROL readout (16 presets x 8 dates x 2 kinds, main ff12aed3 vs branch): this_quarter, this_year, next_quarter unchanged in full; today, this_week, this_month, next_month, next_30_days unchanged except previousYear at 2028-02-29 (the leap-day fix); the eight past presets change as intended.", "Label: no new key. The past comparisons read vsPreviousPeriod; no dashboard.trend key names the period before last and vsPreviousPeriod is exact, so no locale pack or English fallback moved (the Claim file surface lists them; untouched).", "Two periods back has no declared macro token (DATE_MACRO_TOKENS stops at last_*; N_weeks_ago / N_months_ago count from today). lastPeriodBefore computes the ISO day from the resolved last_* start; yesterday maps to the declared {2_days_ago}.", "Corrected my own #30196 docblock in NEXT_TO_CURRENT_TOKENS: the spec does declare next_week_end / next_year_end; it is the ui resolver date-macros.ts that lacks them. Mapping unchanged. Out of scope: that resolver gap (carrier: none).", "The forward rolling branch now writes {1_day_ago} where it wrote {yesterday} (yesterday is now itself moved); resolved windows identical, pinned by the #30140 tests.", "Worktree is objectstack-issue-30140 (reused), not objectstack-issue-30197 as the Claim names.", "Merged origin/main twice (restart after the #30196 squash, then 4 commits before push); no force-push. Package suites and gates ran on 84032ed3, the post-merge re-run is listed.", "The harness asked to set the PR assignee and reviewer through GitHub MCP; skipped per the order." ], "files_changed": [ ".changeset/30197-compare-to-past-presets.md", "ui/packages/core/src/utils/compare-to.ts", "ui/packages/core/src/utils/__tests__/compare-to.test.ts", "ui/packages/core/src/utils/__tests__/dashboard-filters.presetWindows-29116.test.ts", "ui/packages/plugin-dashboard/src/__tests__/ObjectMetricWidget.compareTo.pastPresets-30197.test.tsx" ] }Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#1 · os-support-ai · session_01WGufzfE4V9fzZgJLrLDmMC
Landed · PR #30246 · 2026-11-05T04:04Z
31260ca8e4at 2026-11-05T04:04:31Z, via the merge queue (seat ACCEPT 6648916725)completedbyFixes #30197origin/mainlastPeriodBeforeand two-wayrollingPairinui/packages/core/src/utils/compare-to.ts{N_days_ago}windows) now get a 环比 comparison window, with the leap-day clamp onpreviousYear.next_week_end/next_year_endstays with Console date macros: {next_week_end} and {next_year_end} stay unresolved in the client #30134.pm:dispatchedremoved in the same stroke.