Skip to content

Inline compareTo previousPeriod: backward presets (上月, 上季度, 昨天, 上周, 去年) compare a window with itself, last_N_days compares N+1 days with N #30197

Closed

Description

@objectstack-fleet

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

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.

Measured (PR #30196, Acceptance notes).

  1. 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.
  2. last_N_days compares an N + 1 day window with an N day window.
  3. 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.

Blocked-by: #30140

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:ui · pm:queue · 2026-11-05T01:57Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

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

    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).

  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

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

    Landed · PR #30246 · 2026-11-05T04:04Z

    probe reading
    merge 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions