You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(objectstack-ui): standardize the inline-vs-dataset chart rule (#2502) (#3237)
Encode the chart-data expressibility rule into the objectstack-ui skill,
pinned to what the analytics/dataset engine actually supports rather than the
draft prose in the issue.
- SKILL.md (Dataset-Bound Widgets): reframe around the real boundary. Every
persisted chart is dataset-backed (ADR-0021 single-form cutover), so the
decision is not "inline vs dataset" but "does the need fit the dataset
envelope". Adds a Level A ceiling table (fits -> dataset; beyond -> Cube /
stored rollup field / app code), an iron-rule callout, standardized answers
for the recurring ambiguous cases (parent->child rollup, computed column,
lookup filter, dashboard filter -> #2501), and a Level B note that naming a
dataset is governance, not expressibility. Also corrects the Dashboards
intro that still described the removed inline widget shape.
- evals/analytics-inline-vs-dataset.json: first working evals for this skill,
encoding the key decisions in the proven must_contain/must_not_contain
format.
- dashboard.zod.ts: fix the @example that still showed the removed inline
widget shape (object/valueField/aggregate), which contradicted the
now-required `dataset` field declared a few lines below it.
Follow-ups intentionally left out of this change: service-analytics/README.md
documents a retired API surface (HAVING, old IAnalyticsService methods) and
needs a full rewrite; the inline-embedded-dataset schema change and the
save-as-dataset promotion flow are larger, cross-repo design items tracked on
the issue.
Claude-Session: https://claude.ai/code/session_016QR9X5jcv9tEQQX5V9UZDu
Co-authored-by: Claude <noreply@anthropic.com>
"description": "Inline-vs-dataset / dataset-envelope-ceiling decisions (issue #2502). Every persisted chart is dataset-backed (ADR-0021); the real decision is whether the data need fits the dataset envelope, and if not, which lower layer to escalate to. Naming an inline draft vs a saved dataset is governance, not expressibility.",
4
+
"evals": [
5
+
{
6
+
"id": 1,
7
+
"prompt": "On the sales dashboard, add several widgets that all report the same certified 'revenue' metric (sum of amount) — one grouped by region, one by month, one as a KPI total. Emit the ObjectStack metadata.",
8
+
"expected_output": "Defines the metric ONCE in a dataset (defineDataset with an amount_sum measure + region and a month-granularity date dimension), then binds each widget to that dataset and selects dimensions/values BY NAME. Does not restate the aggregate inline per widget (the ADR-0021 inline object/valueField/aggregate shape was removed).",
"prompt": "Add a dashboard chart showing 30-day rolling revenue (a moving average / running total over time). Model the data need.",
18
+
"expected_output": "Recognizes that a window/rolling calculation is BEYOND the dataset envelope (a dataset has no window/having grammar and compareTo only does previous-period/year). Escalates to a hand-authored Cube (raw SQL) or a stored/materialized rolling field — it does not pretend a dataset date-bucket or a plain measure expresses a moving window.",
19
+
"files": [],
20
+
"assertions": {
21
+
"must_contain": ["window", "Cube"],
22
+
"must_not_contain": ["dateGranularity"]
23
+
}
24
+
},
25
+
{
26
+
"id": 3,
27
+
"prompt": "On a projects dashboard I want each project row to show its count of related child tasks (task has a lookup to project). Model it.",
28
+
"expected_output": "Explains that a to-many rollup ONTO the parent grain is not expressible in a dataset (datasets only do to-one joins; to-many traversal is out of scope). Recommends a stored rollup/summary field on project, OR basing the dataset on the child task and grouping by the project lookup (grain = child). Does not author a to-many join in include.",
29
+
"files": [],
30
+
"assertions": {
31
+
"must_contain": ["rollup", "to-many"],
32
+
"must_not_contain": []
33
+
}
34
+
},
35
+
{
36
+
"id": 4,
37
+
"prompt": "A dashboard has a top-level date-range filter that must re-scope six charts built over different objects. How should each chart pick up the filter? Emit the metadata.",
38
+
"expected_output": "Uses a dashboard-level variable/filter plus per-chart filterBindings mapping the filter to each widget's own date field (e.g. invoice→created_at, account→signed_at), broadcast into each chart's WHERE (#2501). Charts stay self-contained; it does NOT invent a shared dataset for the filter (a dataset is only implied when a parameter reshapes the query grain/window/join).",
39
+
"files": [],
40
+
"assertions": {
41
+
"must_contain": ["filterBindings"],
42
+
"must_not_contain": []
43
+
}
44
+
},
45
+
{
46
+
"id": 5,
47
+
"prompt": "In a kind:'react' page, drop a quick one-off bar chart of tasks grouped by status. Write the block.",
48
+
"expected_output": "Uses the inline <ObjectChart objectName=\"task\" aggregate={{ field: '_id', function: 'count', groupBy: 'status' }} /> block directly — an ad-hoc in-page chart does not require a dataset, and the react ObjectChart block has no dataset prop. Naming a dataset would only be for reuse/governance (Level B), not needed here.",
0 commit comments