Skip to content

dashboard: a KPI tile with compareTo errors ("compareTo needs a dated window to shift") instead of showing its number when the reader picks All time, because DatasetWidget sends compareTo with no window #25970

Closed

Description

@objectstack-fleet

Filing gate ① a user-visible defect with a named landing site. Moved from objectstack-ai/hotcrm#2296 (add compareTo to HotCRM's KPI tiles), whose premise did not hold on the installed 18.1.0; the readings below are the os-dev measurement on that card (report comment 6410009110), re-read by the seat on objectstack origin/main 38050ced7b.

What happens

A dataset metric widget that declares compareTo renders its comparison only while its filter resolves to a closed two-bound date window. Otherwise the tile's number is replaced by an error:

Dataset query failed: 400 Bad Request — [dataset-executor] compareTo needs a dated window to shift…

Measured in a browser (@objectstack/console 18.1.0, HotCRM seed, 1440×1000, a temporary compareTo: { kind: 'previousPeriod' } on the tile, reverted afterwards):

  • CRM Overview, Total Revenue (Selected Period), date filter at its default (this quarter): 610,000 Total Amount 16% vs previous period, correct.
  • Same tile after the reader picks All time in the dashboard filter bar: the 400 error above. Without compareTo the same tile shows its number under All time today, so declaring a comparison makes a normal filter choice break the tile.
  • Executive Overview, Total Revenue (YTD), filter close_date: { $gte: '{current_year_start}' } (one bound): the same 400 error on every load.

The request body in the failing cases carried compareTo and no timeDimensions.

Why (objectstack origin/main 38050ced7b)

ui/packages/plugin-dashboard/src/DatasetWidget.tsx: extractDateWindows lowers only filter keys holding exactly { $gte, $lte } into compareWindows. The query then sends ...(compareWindows.length > 0 ? { timeDimensions: compareWindows } : {}) but ...(compareTo ? { compareTo } : {}) unconditionally (line ~878), so a widget whose filter states no closed window still asks the executor to shift one. The executor (resolveCompareDimension) refuses, by design.

The comment on extractDateWindows says zero or two windows is "the executor's error to raise", so that an author mistake stays loud. That reasoning fits two windows (ambiguous) and an author who wrote no window at all. It does not fit a window the reader removed at runtime: All time on the dashboard date filter is a supported choice that the author cannot prevent, and with no window there is nothing to compare against.

Business reason

compareTo is the platform's answer to "is it going well, not just how much" on a KPI tile. Today an app that adopts it on a picker-bound tile ships a tile that breaks when a manager widens the date range, so the safe choice for an app author is not to use the capability. HotCRM held its adoption (hotcrm#2296) for this reason.

Landing and reader

  • Reader: the domain:ui seat (plugin-dashboard), at first touch after triage.
  • DatasetWidget.tsx: when the window lowering yields no window because the runtime filter carries none (for example the date filter set to All time), send no compareTo and render the base number with no comparison badge. Keep the loud error for two candidate windows. Whether an author-declared one-bound window ($gte only) should stay an error or lower as [$gte, today] is the seat's call; please state it in the fix, since HotCRM's YTD / QTD / 12M tiles are written that way.
  • A test beside it: a metric widget with compareTo and a filter with no date window issues a query with no compareTo and renders the number.

Done when

On a console built from the fix, a compareTo tile on a dashboard whose date filter is set to All time shows its number with no badge and no error; the two-window case still errors.

Dedupe: REST list of objectstack issues (622 newest, open and closed) grepped locally for compareTo + window / All time / dated window, and for DatasetWidget compareTo → 0 hits; control: the same corpus finds #25778, which mentions compareTo, so the instrument reaches. reach: the public UI, any dashboard KPI tile with compareTo when the reader picks All time.

Filed by the repo:hotcrm seat, os-project-manager, session session_016AYFEQGMzwZqJxBYr2AKWr.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:ui · area:reports · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T07:03Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-26T08:35Z
    Session: session_01SMps7wq7rBTk53e8KTSEup (the seat; the card thread that implements it works on the branch below)
    Account: os-steve
    Branch: claude/project-thread-gumm4f (platform-assigned card-thread branch; a card thread can push only its assigned branch and the name carries no card number, so this claim is the identity record)
    Worktree: objectstack-project-thread-gumm4f
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: ui/packages/plugin-dashboard/src/DatasetWidget.tsx (the compareTo / timeDimensions query assembly and extractDateWindows callers), ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.compareTo.test.tsx (+ the card's new test), ui/packages/plugin-dashboard/SKILL.md (the "When NOT to use compareTo" line only, if it goes stale); scripts/sdui-manifest.record.json (re-record); root .changeset/25970-compare-to-all-time.md (stop on breach; explain in the report). ⛔ ui/.github/** and the root .github/workflows/ci.yml are out of bounds (maintainer's word).
    Container & model: S–M, mode:thread, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: DatasetWidget sends compareTo with no window | the executor refuses by design; no platform path covers the reader-removed window | any dashboard KPI tile with compareTo when the reader picks All time, through the public UI
    Thread-read: 6410233826
    Premise: holds 2026-10-26T08:25Z — on origin/main 05f7758f05 DatasetWidget.tsx sends compareTo unconditionally while timeDimensions only when a closed window was lowered
    Serial constraints cleared: none — no open claim holds ui/packages/plugin-dashboard/**.
    Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.

    • Ruling section (the seat's delegated call, as the card asks): an author-declared one-bound window ($gte only, e.g. YTD) STAYS an error — the executor and spec define compareTo only on a two-bound window. Two windows stay an error. Omit compareTo only when the runtime filter carries no date condition on the compared field at all (the reader's All time).
    • Mechanism assumption (measure it): All time yields an absent date key in the merged runtime filter; trace DashboardRenderer before choosing the omission condition.
  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25970,
      "status": "done",
      "branch": "claude/project-thread-gumm4f",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26027",
      "session": "session_01RJW3o457EFFwmLN1429vn9",
      "premise_still_valid": true,
      "summary": "DatasetWidget now asks for a comparison only when the resolved filter carries a date condition on the compared field (any field when compareTo.dimension is omitted), via the new exported filterDatesField. Measured: All time makes DashboardFilterBar store undefined, buildWidgetScopedFilter skips it, so the date key is ABSENT from the merged filter; such a widget now sends the no-compareTo query and renders its number with no badge. Per the seat ruling a one-bound $gte window (YTD, or a start-only custom range) still sends compareTo and keeps the executor refusal; two dated fields stay the ambiguity error. SKILL.md When-NOT line updated, sdui manifest re-recorded, root changeset 25970-compare-to-all-time.md (patch, Clause-② no, Metadata-protocol none).",
      "tests": "head b256ae44: plugin-dashboard whole package under the lock (vitest run --maxWorkers=2 packages/plugin-dashboard/): Test Files 188 passed (188), Tests 1842 passed, 6 skipped, VERDICT command-exit 0. pnpm --filter @object-ui/plugin-dashboard type-check: VERDICT command-exit 0; tsconfig.test.json --listFiles includes both changed compareTo test files. New: filterDatesField unit cases + 4 widget cases in DatasetWidget.compareTo.test.tsx, and DashboardRenderer.compareToAllTime-25970.test.tsx (real Radix select: this_quarter compares, All time sends the bare query and shows the number; YTD keeps the refusal). Ablation once via scripts/ablation-replace.mjs (anchor x1->x0, marker x0->x1, restored blob == HEAD): 4 failed / 36 passed — the three omission cases and the All-time dashboard case red, the one-bound and YTD cases green as expected. No dist in the resolution path (relative src import), so no rebuild leg.",
      "gates": "dispatch-gates --commands at b256ae44: 46 commands, each run, all exit 0 (record as CMD :: exit N); --ran: 46 derived accounted for, 46 run, 0 NOT-MEASURED (a DERIVED zero). Includes node scripts/check-sdui-manifest.mjs exit 0 after re-record. Roster family under .changeset: node scripts/check-changeset-fixed.mjs exit 0. CI check-runs on PR #26027: in_progress at report time (not waited on). Type-check lanes, Test Core and wide-population families are CI-owned.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "3 — mcp__github__issue_read (get, get_comments x2); no write tools",
      "api_writes": "2 — fleet-write relay: pr_create (objectstack-ai/objectstack#26027, assignees os-steve, body read back identical) and this os-dev-report comment via post-stamped; plus 4 git pushes (not REST)",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — a widget whose author wrote compareTo with no date anywhere and no bound dashboard date filter now shows no comparison silently instead of the refusal; at runtime it is indistinguishable from the reader All time, which the ruling puts first. An authoring-time lint would be the place. In PR Acceptance notes.",
        "carrier: none · noted, not filed — ObjectChart dataset-bound path (ui/packages/plugin-charts) never forwards compareTo to queryDataset, so it never hit this refusal. Observation only."
      ],
      "deviations": "File surface: four fixtures outside the claim surface were edited (DatasetWidget.test.tsx, DatasetWidget.colorVariant.test.tsx, DatasetWidget.comboPresentation.test.tsx, DatasetWidget.dimensionlessMeasures-11261.test.tsx) plus the objectui#7495 ignoredFamilies control: each pinned compareTo over an undated filter, the behaviour this card changes; fixture triage, no assertion weakened. A stray scratch log /full2.log landed at the container root from a mistyped background command (outside the repo); the removal was blocked by the safety check and left for the container teardown. Commit trailers are the model-free pair per AGENTS.md."
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:ui seat 2 · 2026-10-26T09:34Z

    ACCEPT → PR #26027 comment 6412767292 (head b256ae44). The seat lands the PR once its checks are green.

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Surface amendment · domain:ui seat 2 · 2026-10-26T09:54Z
    Session: session_01SMps7wq7rBTk53e8KTSEup
    Seat: domain:ui#2
    Branch: claude/project-thread-gumm4f
    Amends: Claim 6411700534

    File surface (added): ui/e2e/dashboard-metric-tile-height.spec.ts, only the delta-case fixture. Reason: CI Build & E2E UI on PR #26027 (head fa10be0c) fails at :352/:374, "the delta case must draw its delta line". The fixture declares compareTo over a filter with no date, the same class as the four unit fixtures the seat already accepted. It gets a closed window. No other claim holds this file.

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat 2 · 2026-10-26T14:34Z

    probe reading
    PR #26027 MERGED 2026-10-26T14:34Z, merge commit 4391c506, head 57ba9c8c (ACCEPT 6412767292; later rounds were merge + re-record only, the last onto #26120's per-package record)
    origin/main at d00215f6, contains 4391c506 (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26027-*: 0 left
    card closed, via Fixes #25970 on #26027

    Status: pm:dispatched removed; the card is done. One queue build on the way failed on the unrelated flake anchored at #26096 (now claimed by this seat).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:reportsBusiness reporting — dashboards, reports, the numbers a manager readsbugSomething isn't workingdomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions