Skip to content

[Decision] Dashboard numbers cannot be opened: should a tile lead to the records behind it? #30080

Closed

Description

@objectstack-fleet

epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

The maintainer: 「等我决定的都要立卡,并且从业务的角度给我具体解释和我沟通」 (2026-11-04). Measured by the seat on main 63b86b385d, on the example CRM unless noted. No domain:* label is set: triage routes this card.

The question

A manager sees 在途商机总额 ¥814,400 and asks which deals make that up. Nothing on the dashboard can be clicked. Reports already drill down (ADR-0021 D2); dashboards do not.

Governing text: ADR-0021 D2 and ui/packages/plugin-report/src/DatasetReportRenderer.tsx:2181-2182 for reports. packages/spec/src/ui/dashboard.zod.ts declares header actions and no widget drill.

Measured

On pipeline_dashboard: no links in the page, no click key on any widget, and clicking a tile changes nothing.

Options

  • A. Tile opens the matching records, reusing the report drill path, by default where the dataset's filter can be expressed as a list filter.
  • B. A per-widget declared link.
  • C. Leave it: open the object's list and rebuild the filter by hand.

Recommendation: A, after the higher-priority Studio work

四轴分析

实际业务需求。 看到数字后追问明细,是看板最自然的下一步,经理每周开会都会用。今天要去列表重新设条件,还不一定能和数字对上。

项目长远合理性。 报表已经定了下钻做法,复用才不会有两套。要处理不能转成列表筛选的数据集:这种情况不提供点击,不能给出对不上的结果。

防 AI 写错。 A 不要求作者写任何东西。B 要手写链接和筛选,AI 容易写出和数字对不上的条件。

创业阶段不扩散。 能力面中等,不阻塞任何工作,所以排在 #30059 这些之后。

棱 A B C
业务 强 中 弱
长远 强 弱 弱
防错 强 弱 中
不扩散 中 中 强

Not measured

How many dashboard tiles can be turned into a list filter, and whether period-over-period tiles have a sensible target.

Re-check

git grep -n "drillSink" origin/main -- ui/packages/plugin-report/src/DatasetReportRenderer.tsx

Activity

  1. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#26676#1 · zhuangjianguo · session_cdb7a717def74e719988f089fbaa18ff

    The maintainer, 2026-11-04: 「30079这种不是应该在元数据应用中自己去开发自定义操作吗?你的意见是批量换负责人放到内核吗?其他全部同意」.

    Ruling: option A, ordered after the Studio customization work. A dashboard tile opens the matching records, reusing the report drill path. A tile whose dataset filter cannot be expressed as a list filter offers no click, rather than a list that does not match the number. needs-user-decision is removed; triage routes this card.

  2. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 4, 2026
  3. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p3 kept · domain:ui · 2026-11-04T13:14Z

    ⛔ Not a claim.

    • Read fresh. Ruled A, ordered after the Studio customization work: a dashboard tile opens its matching records through the report drill path. A tile whose filter cannot become a list filter offers no click. p3 kept.

    Generated by Claude Code

  4. added
    domain:uiDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 4, 2026
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

    Premise corrected before dispatch · 2026-11-05T13:58Z · read at origin/main 48231b02

    • Original premise (card body, Measured): "no links in the page, no click key on any widget, and clicking a tile changes nothing", and "Reports already drill down (ADR-0021 D2); dashboards do not."
    • Reading: on a dataset-bound dashboard, table and pivot rows already open the drill drawer (ui/packages/plugin-dashboard/src/DatasetWidget.tsx, openDrill, test id dataset-drill-row), and so do chart widgets (handleChartDrill). Only the metric branch of DatasetWidget draws a plain tile, with no click and no drill drawer. On the example CRM's pipeline_dashboard, the 在途商机总额 tile (total_pipeline) is such a metric tile.
    • Correction: the ruled target is unchanged (ruling A, 6631202970): a tile opens its matching records through the existing drill path, and a tile whose filter cannot be expressed as a list filter offers no click. The work narrows to dataset-bound metric tiles. The ordering condition reads as met for this lane: Studio: customize an installed app's navigation through the existing app override, so a new object can get a menu entry #30059 (p1, Studio customization) is closed, and this lane has no unclaimed Studio card ahead of this p3.
    • Touched published text: the WIDGET_DRILL_NEAR_KEYS prescription in packages/spec/src/ui/dashboard.zod.ts says "metric/chart render the aggregate only". That is already false for charts and becomes false for metric tiles with this card, so the PR corrects that sentence.

    Generated by Claude Code

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Claim: PM loop round 1 · 2026-11-05T13:58Z
    Session: session_01L6bhgWn6zhNa4LL7VB1rtL
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: the card thread's designated claude/project-thread-* branch, named in a claim amendment once the thread reports it
    Worktree: objectstack-issue-30080
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the dashboard KPI tiles' click in ui/packages/plugin-dashboard/src/** (the dataset-bound metric tile drawn by DatasetWidget.tsx, and ObjectMetricWidget.tsx if the object-metric tile has the same gap), reusing the report drill path (ADR-0021 D2, ui/packages/plugin-report/src/DatasetReportRenderer.tsx drillSink / the dashboard's existing openDrill / DrillDownDrawer); an export added to @object-ui/plugin-report or a new cross-package dependency comes only by amendment after the dev measures it; their tests, one root .changeset/ file. ⛔ MetricWidget.tsx's value-formatting region (domain:ui#5's #30126, PR #30495; its existing onClick prop may be passed, not edited). ⛔ packages/**: the ruling asks for no author key, so if an opt-out or any new dashboard key looks needed, stop and report (a new key is spec-lane work and Clause-②: yes) (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no (no new dashboard key and no new export by this claim; either one is declared by amendment and this line is re-read at ACCEPT)
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6631595575
    Premise: holds on origin/main 48231b02 by code reading: the dataset metric family (METRIC_TYPES, DatasetWidget.tsx:443) draws its tile with no click handler, while the same file already drills chart points and table rows (openDrill, :1658, :1901); not re-measured in a browser. The dev re-measures on pipeline_dashboard (example CRM) first, and states which tiles today draw no click.
    Ruling (verbatim, comment 6631202970): "Ruling: option A, ordered after the Studio customization work. A dashboard tile opens the matching records, reusing the report drill path. A tile whose dataset filter cannot be expressed as a list filter offers no click, rather than a list that does not match the number." The ordering holds at claim time: no Studio customization card waits undispatched in the domain:ui queue (#30059 closed 2026-11-04T17:52:46Z; the rest are in flight or blocked).
    Serial constraints cleared: PR #30495 (#30126, domain:ui#5) touches ui/packages/plugin-dashboard/src/MetricWidget.tsx (value formatting, ready, in landing): this claim does not edit that region and merges main again after it lands. No other open PR touches ui/packages/plugin-dashboard/** or ui/packages/plugin-report/** (read over the 33 open PRs' file lists at claim time).

  7. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · os-sales · session_01L6bhgVfPeFZkN1FP93imKU

    ⛔ Not a claim. domain:ui#1's claim 6659842701 holds this card; seat 4 yields. The premise reading 6659841672 above was taken for a dispatch this seat no longer makes, and is handed to the claiming seat as-is (re-measure what you rely on).


    Generated by Claude Code

  8. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    席位核对 · Amendment · 2026-11-05T14:02Z · claim 6659842701 branch named

    Branch: claude/project-thread-pezzx8

    The card thread's harness-designated branch (a project-thread platform exception to the claude/issue-<n>-<slug> convention); the dev thread is session session_01L6bhgWjsoG4R5yUYFw3T6s. Everything else in claim 6659842701 stands.

  9. 2 remaining items

  10. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Release: 2026-11-05T14:23Z — the domain:ui#1 claim 6659842701 (session_01L6bhgWn6zhNa4LL7VB1rtL, card thread session_01L6bhgWjsoG4R5yUYFw3T6s) ends with no PR (os-dev-report 6660360476, status: blocked, premise_still_valid: true).

    • Premise holds, measured live on the example CRM pipeline_dashboard (main fa9c0aa2, zh-CN): the three dataset metric tiles total_pipeline, won_this_quarter and avg_deal_size_yoy draw no pointer and no drill hook, and a click opens nothing; the 各阶段商机 chart's bar on the same board does open its drawer (3 records).
    • Why it stops: the ruling asks that "A tile whose dataset filter cannot be expressed as a list filter offers no click, rather than a list that does not match the number." A tile's number is computed under the dataset's filter ∧ the measure's filter ∧ the widget filter, and the client sees only the last one, so a ui-only drill would list more records than the number for a filtered measure (the spec's own conditional-count spelling, e.g. the showcase's paid_count). Seat decision: the route the ruling names, the report drill path (ADR-0021 D2), extended so the server sends the drill scope — filed as Drill-through lists more records than the number when a dataset or one of its measures declares a filter: the analytics answer carries no per-measure drill scope #30526 (spec + server, triage routes). A client-side read of dataset definitions (a second spelling of the drill scope) and a tile-filter-only drill (against the ruling) were not taken.
    • Work in hand: the tile click shell (keyboard, test ids, no click when the scope is absent) is pushed as a WIP commit, no PR. Its one seam is readMeasureDrillScope in ui/packages/plugin-dashboard/src/metricDrill.ts, which reads the answer key Drill-through lists more records than the number when a dataset or one of its measures declares a filter: the analytics answer carries no per-measure drill scope #30526 settles; what is left after that is the click path's component tests, the changeset, the gates and the PR.

    Blocked-by: #30526
    Resume-from: claude/project-thread-pezzx8@2fc2a28cb218b075a9c0ad34c353dd03c459bc45

    pm:dispatched → pm:blocked, assignee cleared, in this stroke. When #30526 lands, the card returns to pm:queue; the seat that takes it starts from the commit above.

  11. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 5, 2026
  12. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P

    Unblocked · Blocked-by: #30526 closed completed by PR #30596 (merge 55d9dd448f00d1498ba3ae830368306aa0dd8d8a) · 2026-11-05T18:07Z

    pm:blocked → pm:queue in this stroke; PR #30596 merged at 2026-11-05T18:01:48Z. The release comment 6660397832 still holds: the seat that takes this card resumes from claude/project-thread-pezzx8@2fc2a28cb218b075a9c0ad34c353dd03c459bc45.

    The wire key that readMeasureDrillScope (ui/packages/plugin-dashboard/src/metricDrill.ts) reads is drillScopes on the dataset answer: AnalyticsResult.drillScopes?: Record<string, FilterCondition>, keyed by the selected measure name. @object-ui/data-objectstack queryDataset now passes it through.

    • An entry is the list filter for that measure: the dataset filter ∧ the measure filter ∧ the aggregated column holding a value. {} means no extra leg.
    • A measure with no entry (a derived measure, or a scope that names a relationship path) offers no click, as the ruling asks.
    • The tile drill composes the bucket ∧ the widget filter ∧ drillScopes[measure].

    The report, chart and table drills are a separate card, #30615.


    Generated by Claude Code

  13. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Claim: PM loop round 4 · 2026-11-05T18:41Z · resume after #30526
    Session: session_01L6bhgWn6zhNa4LL7VB1rtL
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-pezzx8 (Resume-from 2fc2a28cb218b075a9c0ad34c353dd03c459bc45, release 6660397832), worked by the same card thread that pushed it (session_01L6bhgWjsoG4R5yUYFw3T6s)
    Worktree: objectstack-issue-30080
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: the dataset metric tile's click in ui/packages/plugin-dashboard/src/** (metricDrill.ts from the WIP and the metric branch of DatasetWidget.tsx), reading drillScopes from the dataset answer as PR #30596 landed it (AnalyticsResult.drillScopes, passed through by @object-ui/data-objectstack queryDataset: read only); their tests; one root .changeset/ file. ⛔ chart, table and report drills (#30615), ⛔ MetricWidget.tsx's value formatting, ⛔ packages/**. Stop on breach and explain in the report.
    Container & model: M, mode:thread, model: default (opus) (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: high
    Clause-②: no (plugin-dashboard internals; no new dashboard key and no new export by this claim; either one comes by amendment and this line is re-read at ACCEPT)
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6665495378
    Premise: holds. The WIP's measure on main fa9c0aa2 (os-dev-report 6660360476): the three dataset metric tiles of the example CRM pipeline_dashboard open nothing on click, while a chart bar on the same board opens its drawer. #30526 closed completed by PR #30596 (55d9dd448f, merged 2026-11-05T18:01:48Z); the unblock note 6665495378 names the wire key the WIP's readMeasureDrillScope must read. The branch merges with origin/main without conflict (git merge-tree, read at claim time).
    Ruling (verbatim, comment 6631202970): "Ruling: option A, ordered after the Studio customization work. A dashboard tile opens the matching records, reusing the report drill path. A tile whose dataset filter cannot be expressed as a list filter offers no click, rather than a list that does not match the number." A measure with no drillScopes entry therefore offers no click.
    Serial constraints cleared: the file lists of all 37 open PRs, read at claim time: none touches ui/packages/plugin-dashboard/** or ui/packages/data-objectstack/src/** (PR #29963, the version PR, only deletes a landed changeset).

  14. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 5, 2026
  15. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWjsoG4R5yUYFw3T6s
    os-dev-report

    {
      "issue": 30080,
      "status": "done",
      "branch": "claude/project-thread-pezzx8",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30634",
      "head": "8f485138",
      "session": "session_01L6bhgWjsoG4R5yUYFw3T6s",
      "premise_still_valid": true,
      "summary": "Resumed from 2fc2a28c per claim 6666212190, merged origin/main (0e39caca, no conflict). The dataset metric tile reads its measure's drillScopes entry (PR #30596) and opens the shared DrillDownDrawer with the widget filter AND the answer date window AND that scope; a measure with no entry, an answer with no object, or a filter the drill sink refuses offers no click and keeps the previous markup. The drawer renders as the tile's sibling so portal events never reach the tile's handlers. Live on the example CRM pipeline_dashboard all three tiles open, and each list matches its number: 710,000 = 4 records summed, 235,000 = 3 records summed, 81,000 = 405,000 over 5 records.",
      "tests": "head 8f485138: pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-dashboard/ -> Test Files 203 passed (203), Tests 1948 passed, 6 skipped (VERDICT command-exit 0). pnpm --filter @object-ui/plugin-dashboard run type-check -> VERDICT command-exit 0. Ablation via scripts/ablation-replace.mjs on metricDrill.ts dropping the measure-scope leg (anchor 1 -> 0, blob bca877be1179 -> 4a067a960ce7): 3 failed, 72 passed in metricDrill.test.ts + DatasetWidget.test.tsx, the three scope pins named; restored with git checkout HEAD, hash-object equals the HEAD blob bca877be1179. ui vitest resolves object-ui packages to source, so no dist leg.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack on 8f485138: 35 derived, each run with exit captured before any pipe, all 35 exit 0; plus pnpm --dir ui run check:spec-symbols exit 0. --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. CI check-runs on PR 30634: in_progress at report time.",
      "line_budget": "5 files vs origin/main (git diff --shortstat origin/main...HEAD): 260 insertions, 8 deletions",
      "files_changed": ["ui/packages/plugin-dashboard/src/DatasetWidget.tsx", "ui/packages/plugin-dashboard/src/metricDrill.ts", "ui/packages/plugin-dashboard/src/__tests__/DatasetWidget.test.tsx", "ui/packages/plugin-dashboard/src/__tests__/metricDrill.test.ts", ".changeset/30080-dashboard-tile-drill.md"],
      "deviations": ["Commits carry only the Co-Authored-By trailer, as the dispatch order says, not the Claude-Session pair AGENTS.md names; the merge commit 0e39caca carries none.", "The derived-measure no-click arm is pinned by the widget test only: no tile on pipeline_dashboard binds a derived measure, so it was not measured live.", "The package-local DatasetResult type gained drillScopes (not exported); no new export, no new dashboard key."],
      "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "0",
      "api_writes": "3: git push x3 of claude/project-thread-pezzx8 (not REST); pr_create via fleet-write relay (PR 30634, read-back identical, assignees none); this comment via scripts/pm/post-stamped.mjs",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": ["carrier: #30615 · chart, table and report drills still compose only bucket and widget or report filter"]
    }
  16. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    ACCEPT → PR #30634 comment 6667374993 · head 8f485138 · 2026-11-05T19:35Z — pr_ready + automerge_enable relayed; the card closes on the merge (Fixes).

  17. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Landed 2026-11-05T19:52Z: PR #30634 merged as 14c67d446e (seat ACCEPT 6667374993 on head 8f485138), 2026-11-05T19:48:15Z on the PR. Its body says Fixes #30080, yet the platform left the card open after the merge, so the seat closes it in this stroke.

    probe reading
    git grep -n "export function metricDrillFilter" origin/main -- ui/packages/plugin-dashboard/src/metricDrill.ts :46 (the tile's list filter, or no click)
    git grep -n "readMeasureDrillScope(state" origin/main -- ui/packages/plugin-dashboard/src/DatasetWidget.tsx :1403 (the metric tile reads its measure's drillScopes entry)

    State: closed as completed, pm:dispatched removed in the same stroke. A tile whose measure has no drillScopes entry offers no click, as ruled (6631202970). The chart, table and report drills are #30615, 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

domain:uiDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions