Skip to content

Restore the Service dashboard's dateRange and relax the #546 CI guard — both upstream preconditions have landed and the window is measurably correct on 17.0.0 GA #1157

Description

@hotlong

Filed unassigned from #1153 (GA close-out B2) while closing #520. Search-checked first: #460 is closed (via PR #546) and cannot carry this; #520 is the platform card and is now closed as a non-reproducer; no open issue tracks the restore.

What this is

src/dashboards/service.dashboard.ts deliberately ships without a dateRange, and test/metadata-references.test.ts carries a guard that fails any dashboard windowing a datetime field. Both were the right call at the time. PR #546 wrote the restore precondition into the file itself:

Restore the line below only once both upstream issues are fixed; the guard in metadata-references.test.ts fails while it is still unsafe.

dateRange: { field: 'created_date', defaultRange: 'last_90_days', allowCustomRange: true },

The two named preconditions are objectstack#3912 (datetime filter comparands coerced to epoch-ms against ISO TEXT storage) and objectstack#3777 (bare-date $lte upper bound dropping same-day records).

The precondition is now satisfied — measured, not assumed

On @objectstack/* 17.0.0 GA (hotcrm d4ddee0), against a fresh seeded database, every answer compared to a SQLite ground truth computed in the same run:

filter on case_metrics API truth rc.0
none 51 51 ✓ ok
$gte only 37 37 ✓ all rows — floor ignored
$lte only 1 1 ✓ 0
$gte + $lte 37 37 ✓ 0
bare-date $lte 1 1 ✓ objectstack#3777

And the dashboard's own widgets, replayed from the compiled artifact with the window ANDed in exactly as the runtime does:

  widget                     unwindowed   windowed   truth(windowed)
  open_cases                         43         30                30   ✓
  critical_cases                      8          7                 7   ✓
  sla_violations                      3          3                 3   ✓
  cases_by_status                    43         30                30   ✓
  cases_by_priority                  43         30                30   ✓
  cases_by_origin                    51         38                38   ✓
  open_cases_by_priority             43         30                30   ✓

  widgets that go BLANK once the datetime window is applied: 0 of 7

The preset chain resolves too. The shipped console lowers last_30_days to { from: '{30_days_ago}', to: '{today}' }, and replaying all seven widgets through that spelling for last_7_days / last_30_days / last_90_days gives 21 of 21 combinations matching ground truth.

Full reading: #520 (comment dated 2026-08-14).

What the work is

  1. Uncomment the dateRange block in src/dashboards/service.dashboard.ts and rewrite the long root-cause comment above it into a short historical note (the mechanism is fixed; the block should not read as if the bug were live).
  2. Relax the guard in test/metadata-references.test.ts that fails any datetime dashboard window. Do not simply delete it — it caught a real class of defect. Better shapes: pin it to the platform version range it was true for, or convert it into a test that asserts the window actually selects rows (which is now a thing that can be asserted).
  3. Re-check the daily_case_volume widget's $gte: '{30_days_ago}' floor, which PR fix(dashboards): drop the Service dashboard date range — datetime filters match nothing (#460) #546 recorded as inert at the time ("that chart currently plots every case, not the last 30 days"). It is no longer inert — measured, {30_days_ago} now resolves to a start-of-day floor and selects correctly — so the widget's title becomes true again and its filterBindings: { dateRange: false } opt-out should be re-examined on purpose rather than by inheritance.
  4. Browser-verify the dashboard with the picker present, changing presets. Everything above is API-layer; nobody has watched the picker itself work since rc.0.
  5. Changeset (user-visible: the dashboard regains a date picker).

One adjacent hazard to know about while doing this

Filed upstream as objectstack#8690: a date comparand the platform cannot parse (for example the bare preset name "last_30_days", rather than the {30_days_ago} macro the console sends) is passed through, compares false, and returns HTTP 200 with zero rows and no diagnostic. The console path is safe — it does the mapping — but if any part of this work hand-writes a preset name into a filter, the symptom is an all-zero dashboard that looks exactly like the bug this restore is undoing.


Generated by Claude Code

Activity

  1. added
    upstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstream
    on Aug 14, 2026
  2. hotlong commented on Aug 14, 2026

    @hotlong
    ContributorAuthor

    upstream:objectstack stripped — this is hotcrm work. Card stays open.

    Checked as part of the transfer sweep #1156 (part 2). This card carried upstream:objectstack, but under the maintainer ruling that sweep runs on — quoted verbatim and untranslated —

    hotcrm 席位的原则是用平台的能力做元数据应用的开发,平台的需求应该转给平台

    the label asks one question: where does the fix land? Here it lands entirely in this repo, so the label is wrong. Removed. ⛔ Not closed — this is live hotcrm work.

    Verified rather than taken from the card

    The card states both upstream preconditions have landed. Since this sweep has already falsified three inherited "already fixed" premises, I checked them at the source rather than trusting the sentence:

    precondition state closed by
    objectstack#3912 — datetime filter comparands coerced to epoch-ms against ISO TEXT storage closed / completed 2026-07-29 merged PR objectstack#3953 (plus #3979)
    objectstack#3777 — bare-date $lte upper bound drops same-day records closed / completed 2026-07-30 merged PR objectstack#4041

    Both are the exact two named in PR #546's restore precondition comment, and both closed via merged PRs. The card's own GA measurements agree independently and are the stronger evidence: every filter shape compared against a SQLite ground truth computed in the same run ($gte only 37/37, $lte only 1/1, both 37/37, bare-date $lte 1/1), all seven dashboard widgets matching truth under the window with 0 of 7 going blank, and 21 of 21 preset-chain combinations matching.

    Why the remaining work is this repo's

    Every item in the card's "What the work is" list touches hotcrm files only:

    1. uncomment the dateRange block in src/dashboards/service.dashboard.ts and rewrite the root-cause comment into a historical note;
    2. relax — not delete — the datetime-window guard in test/metadata-references.test.ts;
    3. re-examine daily_case_volume's {30_days_ago} floor and its filterBindings: { dateRange: false } opt-out, now that the floor is no longer inert;
    4. browser-verify the picker;
    5. changeset.

    Nothing on that list is blocked on the platform, and no platform card is waiting on it. The adjacent hazard the card names — objectstack#8690, an unparseable date comparand answering HTTP 200 with zero rows — is a hazard to know about while doing this work, not a precondition for it: the card records that the console path does the macro mapping correctly, so it only bites if this work hand-writes a bare preset name into a filter.

    Label state after this edit

    This card now carries no labels. I stripped the incorrect one and deliberately did not regrade prio:* — grading is the PM's triage call, not mine, and inventing a priority here would be guessing. Flagging it so the card gets a grade rather than sitting ungraded by accident.

    Authority: #1156.


    Generated by Claude Code


    Generated by Claude Code

  3. added
    metadataDeclarative metadata — schema, security posture, UI surfaces
    pm:queueReady for the PM dispatch loop
    and removed
    upstream:objectstackBlocked on / caused by the ObjectStack platform — tracked upstream
    on Aug 14, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 16, 2026
  5. self-assigned this
    on Aug 16, 2026
  6. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor

    Claimed — PM dispatch, session c94a8dab-47f1-52c9-936a-011792389e89. Branch: claude/issue-1157-service-dashboard-daterange. No earlier claim comment on this card.


    Generated by Claude Code

  7. os-zhuang commented on Aug 16, 2026

    @os-zhuang
    Contributor
    {
      "issue": 1157,
      "status": "done",
      "branch": "claude/issue-1157-service-dashboard-daterange",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1170",
      "premise_still_valid": true,
      "summary": "All five items done. (1) `dateRange: { field: 'created_date', defaultRange: 'last_90_days', allowCustomRange: true }` restored in src/dashboards/service.dashboard.ts, 35-line root-cause comment rewritten as a short historical note. (2) Guard relaxed by CONVERSION, not version-pinning: the datetime ban is removed and replaced by test/dashboard-date-range-window.test.ts, which executes the window against a real SQLite database and compares every widget to a ground truth computed in the same run. Note the guard was in test/action-references.test.ts, not metadata-references.test.ts (#814 split that file by family). (3) daily_case_volume KEEPS filterBindings.dateRange:false, decided on purpose. (4) Browser-verified: picker renders, all 13 presets, KPIs move with the preset, no widget blank. (5) Changeset added. One in-scope addition, named in the PR body: a static guard in analytics-integrity.test.ts rejecting a bare date-range preset name as a filter comparand — the objectstack#8690 trap the card flags; measured in-process, `$gte: 'last_30_days'` returns 0 rows and does NOT throw.",
      "tests": "All at final head 7154ff18. Full suite `npx vitest run --maxWorkers=2`: 105 files, 2568 passed, 1 skipped. Every CI step of ci.yml run locally green: check-stackblitz-lock (in sync), check-source-hygiene, pnpm validate, pnpm lint, pnpm lint:i18n-gate (0 i18n/missing-*), pnpm typecheck, pnpm build (dist 1990.8 KB), pnpm test:coverage (96.3% stmts, thresholds pass). NOT run: E2E (playwright suite — the installed browser revision 1194 does not match this Playwright build's expected 1234; drove Chromium directly at /opt/pw-browsers/chromium-1194 instead of running `playwright install`), CodeQL, Link Check, Labeler. REVERSE VERIFICATION, three ablations, each applied to a committed tree then reverted: (a) drop the $gte floor (objectstack#3912's floor-ignored half) -> 3 tests red, `expected [13,13,13,13] to equal [4,7,10,13]`; (b) ceiling matches nothing -> 6 tests red incl. every no-widget-blank case; (c) bare preset comparand -> 5 tests red. BROWSER (objectstack dev -p 4011, fresh seeded DB, dev admin, 14 Recharts marks confirmed hydrated before reading): Last 90 days -> Open 30 / Critical 7 / Avg 45.0h / SLA 3 / KB 8 / Closed 8; Last 7 days -> 12 / 2 / 24.0 / 2 / 2 / 2; Last 30 days -> same as 90 (demo seed spans ~30 days). Wire evidence: widgets receive {\"$and\":[{\"is_closed\":false},{\"created_date\":{\"$gte\":\"2026-05-18\",\"$lte\":\"2026-08-16\"}}]} — the console lowers the preset to a BARE-DATE upper bound on a datetime column, exactly objectstack#3777's shape, answering correctly; daily_case_volume sends its own unbound {\"created_date\":{\"$gte\":\"2026-07-17\"}} with no $lte, so the opt-out works in the renderer and not only in metadata. 0 non-200 analytics calls, 0 empty result sets in any preset state.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Item 2 — which guard shape, and why

    Of the card's two options I took "assert the window actually selects rows" and rejected the version pin. A pin asserts a belief about version numbers and re-measures nothing: on 17.0.0 it becomes a no-op, and it is blind to the same symptom arriving by another mechanism — objectstack#8690 produces an identical all-zero dashboard on any version.

    The executable guard is sharper than the card's phrasing in one way worth recording: "selects rows" would not have caught half of the original bug. objectstack#3912's $gte half matched every row, so a broken floor reads as a healthy widget with a bigger number. Every count widget is therefore compared for equality against a truth computed in the same run, plus an explicit assertion that the result set narrows as the preset narrows (ablation (a) above is exactly that case, and it goes red).

    The ban is replaced, not deleted: a dashboard windowing a datetime field is still gated — on evidence now. A new datetime-windowed dashboard whose object has no fixture in that file fails with "add a fixture here".

    Item 3 — filterBindings: { dateRange: false } stays, deliberately

    Measured on today's main, the {30_days_ago} floor is no longer inert (#546 recorded it as inert): 13 fixture rows, $gte {30_days_ago} returns exactly the 7 inside the window. So the widget's title "New cases created over the last 30 days" is true again — which is precisely why it must not follow the picker. The dashboard range is ANDed into every bound widget, so a reader selecting "last 7 days" would get a 7-day chart still labelled "last 30 days". Self-described windows opt out; that is the rule the Executive dashboard's YTD tile follows and test/action-references.test.ts enforces.

    The alternative — bind it and retitle — was considered and costs the widget's own filter plus retitling in all four locale bundles, to make the dashboard's one time-series chart stop being a stable 30-day trend. A fixed 30-day volume series beside a 90-day case load is the intended reading, and the browser run shows both behaviours side by side.

    Contradicting the card

    Nothing contradicts the card's measurements; they reproduced independently at 6bf85045 (the card measured d4ddee0). Two corrections of detail:

    1. The guard's location. The card and PR fix(dashboards): drop the Service dashboard date range — datetime filters match nothing (#460) #546's own comment name test/metadata-references.test.ts; the guard actually lives in test/action-references.test.ts — test/metadata-references.test.ts 距 100KB hygiene 上限只剩 817 字节 —— 下一个往里加测试的 PR 会被 gate 拦住 #814 split the former by family and that file records the move.
    2. objectstack#8690 does not throw in-process either. The card describes it as HTTP 200 with zero rows. Measured through ctx.api directly, { created_date: { $gte: 'last_30_days' } } also returns 0 with no error on both driver-sqlite-wasm and driver-memory — a bare preset name is not brace-wrapped, so the token resolver never classifies it and never rejects it. That is why the new static guard is a grep over metadata rather than a runtime assertion.

    Generated by Claude Code

  8. added and removed on Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

metadataDeclarative metadata — schema, security posture, UI surfacespriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions