Skip to content

QA run · dashboards (FULL area) · a86db175 · 2026-08-11 · 6 PASS / 2 PARTIAL / 2 FAIL #7515

Description

@baozhoutao

Full dashboards area run of the checklist-test skill — all 10 items driven against a live showcase (2 opus subagents, isolated boot each). Labels: qa-run + bug.

Result: 6 PASS · 2 PARTIAL · 2 FAIL. Text-only per RUNNER.md.

Environment — framework a86db175 (PR #7304) · vendored console 09987b68 · showcase seed (10 tasks / 5 statuses / 4 priorities / 14 accounts) · isolated file DB + port per batch.

🔴 FAIL — 2 items

1. chart-type-matrixcombo widgets render as grouped bars

A widget authoring chartConfig.series = [{name:'task_count',type:'bar',yAxis:'left'},{name:'avg_progress',type:'line',yAxis:'right'}] plus two yAxis entries renders as two bar series on one shared 0–100 axis: .recharts-bar = 2, .recharts-bar-rectangle = 4, .recharts-line-curve = 0, .recharts-yAxis = 1. Reproduced 3× on independent fresh browser launches.

Repro: open /_console/apps/com.example.showcase/dashboard/showcase_chart_gallery, wait for the loader to appear then clear, settle, and count those selectors inside the "Task Count vs Avg Progress" card (combo_count_vs_progress).

Root cause (source-confirmed, two halves): objectui plugin-dashboard/src/DatasetWidget.tsx resolves the family with CHART_TYPE_MAP[widgetType] ?? 'bar' and CHART_TYPE_MAP has no combo entry; and chartConfigPresentation() in the same file deliberately does not forward series/xAxis/yAxis on the ADR-0021 dataset path ("they are DERIVED from the dataset selection"), so the authored per-series type and axis can never reach the renderer.

Not a stale-bundle artifact — verified against objectui origin/main, which is 138 commits ahead of the vendored pin and behaves identically. Tension worth a decision: widgetDispatch.ts on origin/main routes combo as a SERIES_CHART_TYPES member commenting "the chart renderer has always drawn it (it derives the base family from the series)" — but on the dataset path those series are exactly what DatasetWidget refuses to forward.

Honest caveat recorded, not ticked: kpi / gauge / solid-gauge / bullet all render value-only (pixel-identical to metric) — no dial, no arc, no bullet target band was drawn for any of them.

The other 19 variants pass with mark counts reconciled against API buckets, and compareTo renders its comparison series (with a loud prescriptive 400 when no dated window is supplied, rather than the pre-#5011 silent drop).

2. saved-report-ownershipDELETE /api/v1/reports/:id answers 500, and the mismatch leaks existence

Deleting another owner's report returns 500 REPORT_DELETE_FAILED, while deleting an id that does not exist returns 204. The 500-vs-204 split is an enumeration oracle over other users' report ids — precisely what the deny-as-404 posture exists to prevent. Reproduced 2× on two distinct reports.

Root cause: the DELETE ${dataPath}/reports/:id handler's catch goes straight to res.status(500) and never calls the file-local handleValidation(res, error) helper that maps REPORT_NOT_FOUND* → 404 — the sibling DELETE …/reports/schedules/:scheduleId handler does call it, which is why that route is correct. The service layer is already right (deleteReport() returns early for unknown ids and throws REPORT_NOT_FOUND for cross-owner, commented "others get a not-found so the delete neither fires nor reveals the report's existence") — the route discards that intent. Not stale-dist. One-line fix, plus aligning the unknown-id arm to the same status to close the side channel.

Good news on the same item: #6683's owner gate is live, and the item's revision-2 flip (expected-fail probe → positive deny assertion) is correct against today's server — cross-owner GET / run / upsert-overwrite all 404, schedule list returns empty, cross-owner unschedule 404, the owner's schedule survives, ownerId cannot be spoofed on create, anonymous 401. No cross-owner 2xx anywhere.

🟡 PARTIAL — 2 items

  • global-filters-rescope — everything proven except the persist-through-Studio half (blocked(environment): the showcase is installed as a read-only package so Studio Publish/Save-draft are disabled; the runner followed the UI's own prescription and created a writable package, still gated). The drive half was proven instead by authoring the exact shapes the inspector patches and confirming on reload that the re-targeted widget queries paid_on while siblings query issued_on, and the opted-out widget issues no query at all. Per-widget field mapping is exact on the wire and in the echoed SQL (region on invoice widgets vs sales_region on account widgets); dataset-sourced filter options are a real server GROUP BY, not a client top-N dedupe; client-side cosmetic filtering refuted outright.
  • system-overview-live-counts — tiles are live (each equals its own captured query and an independent direct aggregate; Active Sessions moved 25→26 together with the direct count). Partial because on the first capture the "Events by User" bar rendered its y-axis with zero bars and no empty state — its query had returned a single {user_id:null,…} row and the renderer drops the null-keyed category. Observed once, not reproduced, so recorded as a weakness per RUNNER rule 2 rather than promoted to a fail — but it is exactly the blank-widget shape this clause guards, and a board whose only audit actor is the system would meet it.

✅ PASS — 6 items

cube-query (6/6 — count and sum reconcile bucket-for-bucket with the direct /data aggregate, emitted SQL is a single-table GROUP BY so no join fan-out; the absent arm exercised on a deliberately crippled second boot → honest 404, never a silent 200-empty) · strict-widget-rejects-stray-keys (6/6 — all 11 legacy analytics keys rejected 422 with the key echoed and the ADR-0021 prescription verbatim; after 16 rejections the read-back is byte-identical to a never-existed stub) · dataset-report-authoring (8/8 — summary/matrix/joined/tabular each verified and reconciled; matrix all 8 cells + margins; joined blocks partition 244/10 exactly) · empty-null-bucket-boundaries (6/6 — empty window repaints to designed "No rows" with all 9 queries re-fired and all KPIs dropping to 0; the live null bucket is JSON null, not a '(null)' string sentinel) · chart-first-paint (4/4 — every chart had drawn marks at first paint with no resize, refuting the resize-to-draw failure this item exists for) · drill-through-range (5/5 — a month drill carries exactly that month's half-open bounds; a matrix-cell drill ANDs both dimensions using raw values, not display labels).

Smaller observations

  • The metadata door accepts a dataset binding that names nothing (200 on save and publish); reference integrity is enforced only at runtime — the widget then surfaces a visible error, which is why the negative still passes.
  • The ops dashboard's globalFilter is declared field:'status' with task status options while project-bound widgets carry no filterBindings, so it also lands on showcase_project.status and zeroes those tiles (showcase authoring gap, not a platform defect).
  • The System Overview board's default "Last 7 days" filter applies to sys_user.created_at, so "Total Users" is really users created in the last 7 days — indistinguishable from the true total only because the DB was fresh. Likewise sys_session_metrics carries no active-only filter, so the "Active Sessions" label overstates what it counts.
  • The dateRange "Custom…" picker did not open under automation — flagged for re-check against a fresh console build rather than filed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions