@xpert-ai/plugin-live-dashboard 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.xpertai-plugin/plugin.json +10 -2
  2. package/README.md +6 -0
  3. package/assets/branding/cover-v1.prompt.txt +27 -0
  4. package/assets/branding/cover-v1.webp +0 -0
  5. package/assets/previews/customer-brief.png +0 -0
  6. package/assets/previews/profit-atlas.png +0 -0
  7. package/assets/previews/trend-workspace.png +0 -0
  8. package/dist/index.js +6 -3
  9. package/dist/lib/agent.schema.d.ts +43 -37
  10. package/dist/lib/agent.schema.js +2 -1
  11. package/dist/lib/app-config.d.ts +62 -0
  12. package/dist/lib/app-config.js +38 -0
  13. package/dist/lib/artifact-type.provider.d.ts +15 -0
  14. package/dist/lib/artifact-type.provider.js +78 -0
  15. package/dist/lib/assistant-template.d.ts +1 -2
  16. package/dist/lib/assistant-template.js +3 -52
  17. package/dist/lib/content.provider.js +13 -5
  18. package/dist/lib/dashboard-navigation.d.ts +58 -14
  19. package/dist/lib/dashboard-navigation.js +11 -10
  20. package/dist/lib/dashboard-view.schema.d.ts +197 -46
  21. package/dist/lib/dashboard-view.schema.js +8 -1
  22. package/dist/lib/dashboard.service.d.ts +39 -2
  23. package/dist/lib/dashboard.service.js +52 -28
  24. package/dist/lib/document-libraries.d.ts +1 -1
  25. package/dist/lib/document-libraries.js +3 -2
  26. package/dist/lib/document-network.d.ts +1 -1
  27. package/dist/lib/document-network.js +2 -6
  28. package/dist/lib/editing-feedback.d.ts +5 -0
  29. package/dist/lib/editing-feedback.js +19 -0
  30. package/dist/lib/editing-patch.d.ts +22 -0
  31. package/dist/lib/editing-patch.js +46 -0
  32. package/dist/lib/editing-session.d.ts +167 -0
  33. package/dist/lib/editing-session.js +53 -0
  34. package/dist/lib/editing.d.ts +92 -60
  35. package/dist/lib/editing.js +36 -24
  36. package/dist/lib/html-guidance.d.ts +1 -1
  37. package/dist/lib/html-guidance.js +1 -1
  38. package/dist/lib/middleware.js +42 -18
  39. package/dist/lib/monitor.d.ts +70 -0
  40. package/dist/lib/monitor.js +101 -0
  41. package/dist/lib/plugin.js +4 -1
  42. package/dist/lib/project.d.ts +106 -0
  43. package/dist/lib/project.js +17 -0
  44. package/dist/lib/projects.schema.d.ts +49 -0
  45. package/dist/lib/projects.schema.js +7 -0
  46. package/dist/lib/projects.service.d.ts +23 -0
  47. package/dist/lib/projects.service.js +65 -0
  48. package/dist/lib/resource-card.js +1 -1
  49. package/dist/lib/runtime-context.d.ts +3 -0
  50. package/dist/lib/runtime-context.js +13 -0
  51. package/dist/lib/save-evidence.d.ts +1 -1
  52. package/dist/lib/templates.d.ts +2 -0
  53. package/dist/lib/templates.js +55 -0
  54. package/dist/lib/view.provider.d.ts +97 -7
  55. package/dist/lib/view.provider.js +39 -10
  56. package/dist/libraries/TREND-WORKSPACE-ICONS-LICENSE.txt +43 -0
  57. package/dist/libraries/catalog.json +1 -1
  58. package/dist/remote/app.css +1 -1
  59. package/dist/remote/app.js +33 -33
  60. package/dist/remote/build-manifest.json +115 -32
  61. package/dist/sandbox-actions/action.json +1 -1
  62. package/dist/sandbox-actions/bundle/host.js +2 -2
  63. package/dist/sandbox-actions/bundle/resource-cache.mjs +2 -1
  64. package/dist/sandbox-actions/bundle/validate.mjs +1 -1
  65. package/docs/docs.json +7 -0
  66. package/docs/frontend-libraries.mdx +12 -4
  67. package/docs/html-mode.md +3 -3
  68. package/docs/quick-start-acceptance.mdx +50 -0
  69. package/docs/quick-start.mdx +64 -0
  70. package/docs/superstore.mdx +38 -0
  71. package/docs/team-sharing.mdx +6 -2
  72. package/docs/trend-workspace-acceptance.mdx +43 -0
  73. package/docs/trend-workspace.mdx +30 -0
  74. package/docs/verification.mdx +4 -2
  75. package/docs/visual-styles-acceptance.mdx +40 -0
  76. package/docs/visual-styles.mdx +46 -0
  77. package/package.json +23 -17
  78. package/skills/live-dashboard/SKILL.md +27 -3
  79. package/skills/live-dashboard/examples/README.md +25 -0
  80. package/skills/live-dashboard/examples/catalog.json +79 -0
  81. package/skills/live-dashboard/examples/production-quality/README.md +25 -0
  82. package/skills/live-dashboard/examples/production-quality/dashboard.html +129 -0
  83. package/skills/live-dashboard/examples/production-quality/definition.json +67 -0
  84. package/skills/live-dashboard/examples/production-quality/demo.csv +7 -0
  85. package/skills/live-dashboard/examples/production-quality/demo.json +44 -0
  86. package/skills/live-dashboard/examples/production-quality/expected.json +26 -0
  87. package/skills/live-dashboard/examples/production-quality/preview.png +0 -0
  88. package/skills/live-dashboard/examples/production-quality/query.json +53 -0
  89. package/skills/live-dashboard/examples/regional-sales/README.md +25 -0
  90. package/skills/live-dashboard/examples/regional-sales/dashboard-create.json +35 -0
  91. package/skills/live-dashboard/examples/regional-sales/definition.json +35 -0
  92. package/skills/live-dashboard/examples/regional-sales/demo.csv +5 -0
  93. package/skills/live-dashboard/examples/regional-sales/demo.json +18 -0
  94. package/skills/live-dashboard/examples/regional-sales/expected.json +19 -0
  95. package/skills/live-dashboard/examples/regional-sales/preview.png +0 -0
  96. package/skills/live-dashboard/examples/regional-sales/query.json +45 -0
  97. package/skills/live-dashboard/examples/sales-overview/README.md +25 -0
  98. package/skills/live-dashboard/examples/sales-overview/dashboard.html +129 -0
  99. package/skills/live-dashboard/examples/sales-overview/definition.json +57 -0
  100. package/skills/live-dashboard/examples/sales-overview/demo.csv +7 -0
  101. package/skills/live-dashboard/examples/sales-overview/demo.json +38 -0
  102. package/skills/live-dashboard/examples/sales-overview/expected.json +22 -0
  103. package/skills/live-dashboard/examples/sales-overview/preview.png +0 -0
  104. package/skills/live-dashboard/examples/sales-overview/query.json +52 -0
  105. package/skills/live-dashboard/examples/superstore/README.md +64 -0
  106. package/skills/live-dashboard/examples/superstore/THIRD_PARTY_LICENSE.txt +207 -0
  107. package/skills/live-dashboard/examples/superstore/build_data.py +155 -0
  108. package/skills/live-dashboard/examples/superstore/build_pages.py +129 -0
  109. package/skills/live-dashboard/examples/superstore/checks.json +104 -0
  110. package/skills/live-dashboard/examples/superstore/customers-mobile-dark.png +0 -0
  111. package/skills/live-dashboard/examples/superstore/customers-select.png +0 -0
  112. package/skills/live-dashboard/examples/superstore/customers.html +316 -0
  113. package/skills/live-dashboard/examples/superstore/customers.png +0 -0
  114. package/skills/live-dashboard/examples/superstore/customers.queries.json +512 -0
  115. package/skills/live-dashboard/examples/superstore/expected.json +4069 -0
  116. package/skills/live-dashboard/examples/superstore/import.json +200 -0
  117. package/skills/live-dashboard/examples/superstore/orders.csv +10195 -0
  118. package/skills/live-dashboard/examples/superstore/overview-mobile-dark.png +0 -0
  119. package/skills/live-dashboard/examples/superstore/overview-select.png +0 -0
  120. package/skills/live-dashboard/examples/superstore/overview.html +310 -0
  121. package/skills/live-dashboard/examples/superstore/overview.png +0 -0
  122. package/skills/live-dashboard/examples/superstore/overview.queries.json +636 -0
  123. package/skills/live-dashboard/examples/superstore/page.js +109 -0
  124. package/skills/live-dashboard/examples/superstore/profitability-mobile-dark.png +0 -0
  125. package/skills/live-dashboard/examples/superstore/profitability-select.png +0 -0
  126. package/skills/live-dashboard/examples/superstore/profitability.html +307 -0
  127. package/skills/live-dashboard/examples/superstore/profitability.png +0 -0
  128. package/skills/live-dashboard/examples/superstore/profitability.queries.json +334 -0
  129. package/skills/live-dashboard/examples/superstore/provenance.json +24 -0
  130. package/skills/live-dashboard/examples/superstore/semantic-model.json +220 -0
  131. package/skills/live-dashboard/examples/superstore/test_data.py +62 -0
  132. package/skills/live-dashboard/references/html-runtime.md +1 -1
  133. package/skills/live-dashboard/themes/README.md +50 -0
  134. package/skills/live-dashboard/themes/customer-brief/README.md +47 -0
  135. package/skills/live-dashboard/themes/customer-brief/charts.js +17 -0
  136. package/skills/live-dashboard/themes/customer-brief/layout.html +22 -0
  137. package/skills/live-dashboard/themes/customer-brief/theme.css +108 -0
  138. package/skills/live-dashboard/themes/profit-atlas/README.md +43 -0
  139. package/skills/live-dashboard/themes/profit-atlas/charts.js +30 -0
  140. package/skills/live-dashboard/themes/profit-atlas/layout.html +19 -0
  141. package/skills/live-dashboard/themes/profit-atlas/theme.css +75 -0
  142. package/skills/live-dashboard/themes/trend-workspace/ICON_LICENSE.txt +43 -0
  143. package/skills/live-dashboard/themes/trend-workspace/README.md +144 -0
  144. package/skills/live-dashboard/themes/trend-workspace/charts.js +60 -0
  145. package/skills/live-dashboard/themes/trend-workspace/icons.js +11 -0
  146. package/skills/live-dashboard/themes/trend-workspace/layout.html +45 -0
  147. package/skills/live-dashboard/themes/trend-workspace/select.js +127 -0
  148. package/skills/live-dashboard/themes/trend-workspace/theme.css +137 -0
@@ -0,0 +1,50 @@
1
+ # Dashboard visual styles — choose by the analysis task
2
+
3
+ These are three distinct authoring systems, not one mandatory appearance for every
4
+ dashboard. User direction wins. Preserve a saved dashboard's theme when changing
5
+ data or copy; only change it when the user asks for a redesign.
6
+
7
+ | Theme | Use for | Composition | Library / global |
8
+ | --- | --- | --- | --- |
9
+ | [Trend Workspace](trend-workspace/README.md) | Operating overview, time trends | Pale-blue KPI rail, aligned independent trend scales, lower comparisons | `trend-workspace` / `window.TrendWorkspace` |
10
+ | [Profit Atlas](profit-atlas/README.md) | Profitability, contribution, losses | Dark analysis desk, horizontal metrics, dominant scatter and signed ranking | `profit-atlas` / `window.ProfitAtlas` |
11
+ | [Customer Brief](customer-brief/README.md) | Customer structure, returns, management briefing | Warm paper, serif hierarchy, metric ribbon, grouped return metrics | `customer-brief` / `window.CustomerBrief` |
12
+
13
+ For the three-page Superstore suite, use each row for its corresponding page.
14
+ For a different dataset, first establish the business question and fields; select
15
+ the matching composition, then adapt it. Do not force every dataset into a scatter
16
+ plot, invent missing return metrics, or infer theme selection from a title at runtime.
17
+ Record the explicit choice in `data-dashboard-theme="<id>@1"` and save with
18
+ `libraries:["echarts","<id>"]`. Choose exactly one visual theme; do not stack theme
19
+ libraries. Call `dashboard_libraries` to verify availability before authoring.
20
+
21
+ All three expose `enhanceSelect(nativeSelect)` and `charts.line/horizontal/scatter`.
22
+ The host injects pinned CSS/JS; you author the business layout and governed query
23
+ runtime. Complete examples and query plans are in `examples/superstore/`. No local
24
+ repository, Python generator or this developer's computer is required by the Agent.
25
+
26
+ ## Shared behavior and editing rules
27
+
28
+ - Read the selected theme guide and its layout fragment, plus the complete relevant
29
+ example. The existing [typed select contract](trend-workspace/README.md#select-component-contract)
30
+ and [data adaptation contract](trend-workspace/README.md#adapt-to-a-new-dataset)
31
+ apply to every theme. The shared source is `trend-workspace/select.js`, `icons.js`
32
+ and `charts.js`; each theme bundle already contains them and their icon license.
33
+ - Keep business values out of the HTML. Discover and validate the actual source,
34
+ use independent backend totals, query every affected Binding on a filter change,
35
+ and clear stale values on loading, empty, truncated or failed queries.
36
+ - For existing Artifacts, use `dashboard_open`, read its current editing copy and
37
+ retain `saveArguments`, Binding IDs, source, controls, title and all existing DOM
38
+ IDs. Moving a chart does not require changing its ID. Do not create duplicates.
39
+ - Use bounded exact SandboxFile edits to the returned `htmlPath`; never overwrite
40
+ an existing editing copy with `sandbox_write_file`. Keep old/new replacements
41
+ around 4,000 UTF-8 bytes or less. New long documents can be written then appended.
42
+ - Save through `dashboard_update` with the explicit new theme library only when
43
+ redesign was requested. Successful draft receipts are required; publishing is a
44
+ separate human decision. Existing saved library bytes remain frozen.
45
+ - Verify a real filter and refresh across KPIs, charts and detail rows. Check keyboard
46
+ selection, Escape, focus, menu bounds, 390px layout and theme contrast. Rendered
47
+ visual checks are required; report them as pending if no browser is available.
48
+
49
+ The host project/dashboard cascade and Publish/More toolbar stay outside authored
50
+ HTML. These styles do not add capabilities, data access, project sharing or jobs.
@@ -0,0 +1,47 @@
1
+ # Customer Brief v1 — editorial customer and returns report
2
+
3
+ Use this style for the approved warm customer brief or an explicitly chosen
4
+ management report. Read [theme selection and shared rules](../README.md) first.
5
+ Keep the current theme on unrelated edits; this is not a mandatory default.
6
+
7
+ ## Installed assets and runtime
8
+
9
+ Read `theme.css`, `charts.js`, `layout.html` and the complete
10
+ `examples/superstore/customers.html`. Call `dashboard_libraries`, then use
11
+ `libraries:["echarts","customer-brief"]`; the global is `window.CustomerBrief`.
12
+ Shared select, icons/license and chart primitives are already bundled. Do not load
13
+ another theme library or retype its component implementation.
14
+
15
+ ## Visual contract
16
+
17
+ - Warm paper-white canvas, forest ink, sage comparisons and terracotta returns.
18
+ Headings and large metrics use a local serif stack (Songti SC / Noto Serif CJK /
19
+ SimSun / system serif); body, controls and tables remain readable sans-serif.
20
+ No external font request is required. Low-light mode has matching warm tokens.
21
+ - Large title, a horizontal customer/order/value ribbon and a single grouped return
22
+ area. Put return percentage before return count visually, retaining metric IDs.
23
+ No KPI sidebar, card grid, decorative avatars, gauges or unsupported risk badges.
24
+ - Two numbered sections compare customer groups and regions; keep common zero
25
+ baselines. Numbers are direct labels. Color differentiates groups, not an invented
26
+ business threshold. Show exact values again in a complete compact table.
27
+ - At 760px the return group and plots stack; at 390px three compact filters remain
28
+ keyboard-accessible, and only the named table region may scroll horizontally.
29
+
30
+ ## Authoring
31
+
32
+ Set `<body data-dashboard-theme="customer-brief@1">`, then use `brief-header`,
33
+ `brief-metrics`, `brief-primary-metrics`, `brief-return-metrics` and `brief-plots`.
34
+ Use `charts.horizontal(chart, rows, {dimension,measure,label,palette:[...]})` with
35
+ explicit color token names: `['blue','segment-two','segment-three']` for the ordered
36
+ customer comparisons; `['returns']` and `decimals:0` for return-order counts.
37
+ Other datasets must supply their own real grouping, measures and units.
38
+
39
+ Customer counts must be independently deduplicated for the current scope. Return
40
+ share is returned orders divided by orders, not returned items divided by quantities.
41
+ Return-associated sales are not refunds or lost revenue. Do not sum group customer
42
+ counts to invent a total, or add these return metrics to sources that do not have them.
43
+
44
+ For existing HTML, keep Binding IDs and all metric/chart IDs while rearranging the
45
+ DOM. Read the current editing copy, make bounded exact edits and save the explicit
46
+ theme choice through `dashboard_update`. Test a real region filter and restoration,
47
+ including every KPI, plot and detail row. A save receipt alone is not visual QA.
@@ -0,0 +1,17 @@
1
+ // Explicit palette options distinguish ranked customer segments from return counts.
2
+ const CustomerBriefCharts = (() => {
3
+ function horizontal(chart, rows, options) {
4
+ TrendWorkspaceCharts.horizontal(chart, rows, options);
5
+ const token = name => getComputedStyle(document.body).getPropertyValue('--' + name).trim();
6
+ const narrow = chart.getWidth() < 420;
7
+ const colors = options.palette || ['blue'];
8
+ chart.setOption({grid: {left: narrow ? 80 : 104, right: options.decimals === 0 ? 40 : 112, top: 12, bottom: 28},
9
+ xAxis: {axisLabel: {fontSize: 12}}, yAxis: {axisLabel: {fontSize: narrow ? 12 : 15}},
10
+ series: [{id: options.measure, barMaxWidth: 28,
11
+ label: {fontSize: narrow ? 12 : 15},
12
+ data: rows.map((row, index) => ({value: Number(row[options.measure]),
13
+ itemStyle: {color: token(Number(row[options.measure]) < 0 ? 'red' : colors[index % colors.length])}}))}]
14
+ });
15
+ }
16
+ return {...TrendWorkspaceCharts, horizontal};
17
+ })();
@@ -0,0 +1,22 @@
1
+ <!-- Structural fragment; add verified controls, data bindings and the SDK runtime. -->
2
+ <body data-dashboard-theme="customer-brief@1">
3
+ <main class="workspace">
4
+ <header class="brief-header">
5
+ <div><h1>Customer report</h1><p class="description">Business question</p><span class="demo">Source and period</span></div>
6
+ <div class="filters" aria-label="Analysis filters"><!-- Typed filter-field controls --></div>
7
+ </header>
8
+ <section class="brief-metrics" aria-label="Current scope metrics">
9
+ <div class="brief-primary-metrics"><!-- Customer, order and value metrics --></div>
10
+ <div class="brief-return-metrics"><!-- Rate then count; keep original IDs --></div>
11
+ </section>
12
+ <div class="analysis">
13
+ <div class="brief-plots">
14
+ <section class="panel"><h2>01 Customer groups</h2><div class="chart" id="chart-primary"></div></section>
15
+ <section class="panel"><h2>02 Regional returns</h2><div class="chart" id="chart-secondary"></div></section>
16
+ </div>
17
+ <div class="full-details"><section class="details"><!-- Complete accessible detail table --></section></div>
18
+ <p id="status" class="status" role="status"></p>
19
+ </div>
20
+ <footer><span>Source period</span><span id="freshness"></span></footer>
21
+ </main>
22
+ </body>
@@ -0,0 +1,108 @@
1
+ /* Customer Brief: a warm editorial document, with a matching low-light palette. */
2
+ body[data-dashboard-theme="customer-brief@1"] {
3
+ color-scheme: light;
4
+ --paper: #f7f5ef;
5
+ --control: #eeeee5;
6
+ --ink: #163d34;
7
+ --muted: #697577;
8
+ --line: #d7d2c6;
9
+ --blue: #245247;
10
+ --teal: #567f6d;
11
+ --red: #9e4932;
12
+ --returns: #b76b52;
13
+ --returns-paper: #f1e7e0;
14
+ --segment-two: #8b9f80;
15
+ --segment-three: #b2b597;
16
+ --selected: #e3e9df;
17
+ --focus: #316455;
18
+ --shadow: 0 8px 24px #183d331a;
19
+ --radius: 12px;
20
+ --editorial: "Songti SC", "Noto Serif CJK SC", "SimSun", ui-serif, serif;
21
+ }
22
+ body[data-dashboard-theme="customer-brief@1"].dark {
23
+ color-scheme: dark;
24
+ --paper: #19231f;
25
+ --control: #27342b;
26
+ --ink: #e7ede2;
27
+ --muted: #bac5ba;
28
+ --line: #4a5649;
29
+ --blue: #bad1b9;
30
+ --teal: #abc4a7;
31
+ --red: #f4b59d;
32
+ --returns: #d69a80;
33
+ --returns-paper: #3b3028;
34
+ --segment-two: #9bb193;
35
+ --segment-three: #7f9878;
36
+ --selected: #344b3d;
37
+ --focus: #c0d2b5;
38
+ }
39
+ body[data-dashboard-theme="customer-brief@1"] .workspace { display: block; padding: 28px 36px 0; }
40
+ .brief-header { display: flex; justify-content: space-between; align-items: center; gap: 24px; padding-bottom: 22px; border-bottom: 1px solid var(--line); }
41
+ .brief-header h1 { font: 700 58px/1.2 var(--editorial); letter-spacing: -1px; }
42
+ .brief-header .description { margin-top: 8px; font: 20px/1.4 var(--editorial); color: var(--ink); }
43
+ .brief-header .demo { margin: 8px 0 0; font-size: 13px; }
44
+ .brief-header .filters { flex-wrap: nowrap; padding: 0; gap: 14px; }
45
+ .brief-header .filter-field { width: 184px; }
46
+ .brief-header .filter-trigger { min-height: 70px; background: transparent; }
47
+ .brief-header .filter-value { font-size: 18px; }
48
+ .brief-metrics { display: grid; grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); gap: 20px; padding: 20px 0 16px; border-bottom: 1px solid var(--line); }
49
+ .brief-primary-metrics, .brief-return-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: center; }
50
+ .brief-return-metrics { grid-template-columns: 1fr 1fr; padding: 12px 16px; border-radius: 10px; background: var(--returns-paper); }
51
+ .brief-metrics .metric { grid-column: auto; display: flex; flex-direction: column; padding: 0 22px; border: 0; border-left: 1px solid var(--line); }
52
+ .brief-metrics .metric:first-child { border-left: 0; padding-left: 14px; }
53
+ .brief-metrics .metric:last-child { padding-right: 0; }
54
+ .brief-metrics .metric p { order: 2; font: 20px/1.35 var(--editorial); color: var(--ink); }
55
+ .brief-metrics .metric output { order: 1; font: 700 clamp(38px, 5.1vw, 74px)/1.15 var(--editorial); letter-spacing: -1px; margin: 0 0 4px; }
56
+ .brief-metrics .metric small { order: 3; margin-top: 5px; }
57
+ .brief-return-metrics .metric { padding-inline: 20px; }
58
+ .brief-return-metrics .metric output { color: var(--red); }
59
+ .brief-plots { display: grid; grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); gap: 24px; padding: 22px 0; }
60
+ .brief-plots h2 { font: 700 26px/1.3 var(--editorial); }
61
+ .brief-plots .section-number { margin-right: 14px; font-size: 30px; }
62
+ .brief-plots .panel:last-child { border-left: 1px solid var(--line); padding-left: 24px; }
63
+ .brief-plots .note { font-size: 13px; }
64
+ .brief-plots .chart { height: 275px; margin-top: 12px; }
65
+ body[data-dashboard-theme="customer-brief@1"] .details { padding: 16px 0 4px; }
66
+ body[data-dashboard-theme="customer-brief@1"] .details h2 { font: 700 26px/1.3 var(--editorial); }
67
+ body[data-dashboard-theme="customer-brief@1"] .details > .note { display: none; }
68
+ body[data-dashboard-theme="customer-brief@1"] .table-scroll { max-height: 300px; border: 1px solid var(--line); border-radius: 10px; margin-top: 12px; }
69
+ body[data-dashboard-theme="customer-brief@1"] table { font-size: 15px; }
70
+ body[data-dashboard-theme="customer-brief@1"] th, body[data-dashboard-theme="customer-brief@1"] td { padding: 10px 12px; }
71
+ body[data-dashboard-theme="customer-brief@1"] th { color: var(--ink); font-size: 14px; }
72
+ body[data-dashboard-theme="customer-brief@1"] td:nth-child(6) { color: var(--red); }
73
+ body[data-dashboard-theme="customer-brief@1"] .table-bar { display: none; }
74
+ body[data-dashboard-theme="customer-brief@1"] .status { padding: 8px 0 0; }
75
+ body[data-dashboard-theme="customer-brief@1"] .methodology { padding: 4px 0 12px; }
76
+ body[data-dashboard-theme="customer-brief@1"] footer { padding: 10px 0; }
77
+ @media (max-width: 1100px) {
78
+ .brief-header { flex-wrap: wrap; gap: 18px; }
79
+ .brief-header h1 { font-size: 40px; }
80
+ .brief-header .filters { margin-left: auto; }
81
+ .brief-metrics { gap: 12px; }
82
+ .brief-metrics .metric { padding-inline: 10px; }
83
+ .brief-metrics .metric output { font-size: 42px; }
84
+ .brief-metrics .metric p { font-size: 16px; }
85
+ }
86
+ @media (max-width: 760px) {
87
+ .brief-metrics { grid-template-columns: 1fr; }
88
+ .brief-return-metrics .metric:first-child { padding-left: 10px; }
89
+ .brief-plots { grid-template-columns: 1fr; }
90
+ .brief-plots .panel:last-child { border-left: 0; border-top: 1px solid var(--line); padding: 20px 0 0; }
91
+ }
92
+ @media (max-width: 680px) {
93
+ body[data-dashboard-theme="customer-brief@1"] .workspace { padding: 20px 16px 0; }
94
+ .brief-header { display: block; }
95
+ .brief-header h1 { font-size: 34px; }
96
+ .brief-header .description { font-size: 18px; }
97
+ .brief-header .filters { display: grid; margin-top: 20px; gap: 8px; }
98
+ .brief-header .filter-field { width: auto; }
99
+ .brief-header .filter-trigger { min-height: 62px; }
100
+ .brief-header .filter-value { font-size: 13px; }
101
+ .brief-metrics .metric output { font-size: 32px; }
102
+ .brief-metrics .metric small { font-size: 12px; }
103
+ .brief-primary-metrics .metric:first-child { grid-column: auto; padding-left: 0; }
104
+ .brief-return-metrics .metric:first-child { grid-column: auto; }
105
+ .brief-return-metrics .metric output { font-size: 42px; }
106
+ .brief-plots h2, body[data-dashboard-theme="customer-brief@1"] .details h2 { font-size: 23px; }
107
+ .brief-plots .chart { height: 260px; }
108
+ }
@@ -0,0 +1,43 @@
1
+ # Profit Atlas v1 — profitability analysis desk
2
+
3
+ Use this style for the approved dark profitability screen or an explicitly chosen
4
+ contribution/loss analysis. Read [theme selection and shared rules](../README.md)
5
+ first. This is one optional style; it does not replace Trend Workspace.
6
+
7
+ ## Installed assets and runtime
8
+
9
+ Read `theme.css`, `charts.js`, `layout.html` and the complete
10
+ `examples/superstore/profitability.html`. Call `dashboard_libraries`, then use
11
+ `libraries:["echarts","profit-atlas"]`; the global is `window.ProfitAtlas`.
12
+ The bundle includes the shared typed select, Lucide icons/license and base chart
13
+ primitives. Do not also load `trend-workspace` or copy public component code into HTML.
14
+
15
+ ## Visual contract
16
+
17
+ - Graphite canvas, off-white text, quiet grid lines; mint positive profit, coral
18
+ negative profit. The document deliberately stays dark in either host theme.
19
+ - Full-width header and a continuous five-or-fewer metric ribbon. No left KPI rail,
20
+ repeated floating cards, neon glow, gradients or invented growth indicators.
21
+ - Primary scatter occupies roughly 65% of the analysis width; the other 35% holds
22
+ a signed ranked bar chart. Both have explicit units and a visible zero baseline.
23
+ - Positive and negative ranking values remain visible; dense positive scatter clusters use hover details to avoid overlapping labels.
24
+ On narrow screens, preserve labels for negative items and stack the plots.
25
+ - The compact detail table is ordered by the business metric (profit ascending in
26
+ this example), so losses appear first. Keep all rows in its named scroll region;
27
+ do not silently truncate the query to the three rows seen in the design reference.
28
+ - Use system sans typography, 36px desktop title, 25–37px tabular KPIs, 14px body.
29
+ Dropdowns retain the two-line caption/value contract with dark matching surfaces.
30
+
31
+ ## Authoring
32
+
33
+ Set `<body data-dashboard-theme="profit-atlas@1">`, compose `atlas-header`,
34
+ `atlas-metrics`, `atlas-plots`, and a full-width details section as in `layout.html`.
35
+ `charts.scatter(chart, rows, {dimension,x,y,xLabel,yLabel})` takes explicit fields;
36
+ `charts.horizontal(chart, rows, {dimension,measure,label,color:'teal'})` draws signed
37
+ rankings. Sorting and backend totals remain the business runtime's responsibility.
38
+
39
+ Do not manufacture profit or financial meaning for unrelated data. For another
40
+ two-measure comparison, supply its real units and decide whether the zero reference
41
+ and negative labels are meaningful. Preserve all Binding and component IDs during
42
+ a style-only upgrade. After saving, verify the exact target Artifact and a real
43
+ filter; report visual acceptance separately from a successful save receipt.
@@ -0,0 +1,30 @@
1
+ // Extend the shared chart primitives; fields and labels remain explicit inputs.
2
+ const ProfitAtlasCharts = (() => {
3
+ const token = name => getComputedStyle(document.body).getPropertyValue('--' + name).trim();
4
+ function horizontal(chart, rows, options) {
5
+ TrendWorkspaceCharts.horizontal(chart, rows, options);
6
+ chart.setOption({grid: {left: 82, right: 78},
7
+ series: [{id: options.measure, barMaxWidth: 16, label: {fontSize: 12}}],
8
+ xAxis: {splitLine: {show: false}},
9
+ yAxis: {axisLabel: {fontSize: 12}}});
10
+ }
11
+ function scatter(chart, rows, options) {
12
+ TrendWorkspaceCharts.scatter(chart, rows, options);
13
+ const narrow = chart.getWidth() < 600;
14
+ const maximum = Math.max(0, ...rows.map(row => Number(row[options.x])));
15
+ chart.setOption({grid: {left: 46, right: 32, top: 30, bottom: 46},
16
+ xAxis: {splitLine: {lineStyle: {type: 'dashed'}}},
17
+ yAxis: {splitLine: {lineStyle: {type: 'dashed'}}},
18
+ series: [{id: 'scatter', symbolSize: 10, labelLayout: {hideOverlap: true},
19
+ data: rows.map(row => {
20
+ const negative = Number(row[options.y]) < 0;
21
+ return {value: [Number(row[options.x]), Number(row[options.y])], name: row[options.dimension],
22
+ itemStyle: {color: token(negative ? 'red' : 'teal'), opacity: 1},
23
+ label: {show: negative || (!narrow && (Number(row[options.x]) >= maximum * .15 || Number(row[options.y]) >= 12000)), formatter: String(row[options.dimension]),
24
+ position: Number(row[options.x]) > maximum * .9 ? 'left' : 'right',
25
+ color: token(negative ? 'red' : 'ink'), fontSize: 12}};
26
+ })}]
27
+ });
28
+ }
29
+ return {line: TrendWorkspaceCharts.line, horizontal, scatter};
30
+ })();
@@ -0,0 +1,19 @@
1
+ <!-- Structural fragment; add verified controls, data bindings and the SDK runtime. -->
2
+ <body data-dashboard-theme="profit-atlas@1">
3
+ <main class="workspace">
4
+ <header class="atlas-header">
5
+ <div><h1>Analysis title</h1><p class="description">Business question</p><span class="demo">Source and period</span></div>
6
+ <div class="filters" aria-label="Analysis filters"><!-- Typed filter-field controls --></div>
7
+ </header>
8
+ <section class="atlas-metrics" aria-label="Current scope metrics"><!-- Metric outputs --></section>
9
+ <div class="analysis">
10
+ <div class="atlas-plots">
11
+ <section class="panel"><h2>Measure relationship</h2><div class="chart" id="chart-secondary"></div></section>
12
+ <section class="panel"><h2>Contribution ranking</h2><div class="chart" id="chart-primary"></div></section>
13
+ </div>
14
+ <div class="full-details"><section class="details"><!-- Complete accessible detail table --></section></div>
15
+ <p id="status" class="status" role="status"></p>
16
+ </div>
17
+ <footer><span>Source period</span><span id="freshness"></span></footer>
18
+ </main>
19
+ </body>
@@ -0,0 +1,75 @@
1
+ /* Profit Atlas: a deliberate dark document theme, independent of the host chrome. */
2
+ body[data-dashboard-theme="profit-atlas@1"] {
3
+ color-scheme: dark;
4
+ --paper: #10181f;
5
+ --control: #17222c;
6
+ --ink: #f0f4f7;
7
+ --muted: #aebdcb;
8
+ --line: #30414e;
9
+ --blue: #62d9c1;
10
+ --teal: #53d6ba;
11
+ --red: #ff8b90;
12
+ --selected: #203c39;
13
+ --focus: #89e4d0;
14
+ --shadow: 0 8px 24px #0006;
15
+ --radius: 8px;
16
+ }
17
+ body[data-dashboard-theme="profit-atlas@1"] .workspace { display: block; }
18
+ .atlas-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 28px; padding: 22px 34px 18px; }
19
+ .atlas-header h1 { font-size: 36px; line-height: 1.25; letter-spacing: -.6px; }
20
+ .atlas-header .description { margin-top: 4px; font-size: 15px; }
21
+ .atlas-header .demo { margin: 6px 0 0; font-size: 13px; }
22
+ .atlas-header .filters { padding: 6px 0 0; flex-wrap: nowrap; gap: 14px; }
23
+ .atlas-header .filter-field { width: 168px; }
24
+ .atlas-header .filter-trigger { min-height: 62px; }
25
+ .atlas-metrics { display: grid; grid-template-columns: 1.15fr 1.1fr .95fr .9fr .75fr; margin: 0; padding: 18px 34px; border-block: 1px solid var(--line); }
26
+ .atlas-metrics .metric { grid-column: auto; padding: 0 26px; border: 0; border-left: 1px solid var(--line); }
27
+ .atlas-metrics .metric:first-child { padding-left: 0; border-left: 0; }
28
+ .atlas-metrics .metric:last-child { padding-right: 0; }
29
+ .atlas-metrics .metric output { font-size: clamp(25px, 2.55vw, 37px); letter-spacing: -.6px; margin: 6px 0 0; }
30
+ .atlas-metrics .metric:nth-child(2) output { color: var(--teal); }
31
+ .atlas-metrics .metric small { display: none; }
32
+ .atlas-plots { display: grid; grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr); gap: 32px; padding: 24px 34px 20px; }
33
+ .atlas-plots .panel { min-width: 0; }
34
+ .atlas-plots .panel:last-child { padding-left: 28px; border-left: 1px solid var(--line); }
35
+ .atlas-plots h2 { font-size: 22px; }
36
+ .atlas-plots .note { font-size: 13px; }
37
+ .atlas-plots .chart { height: 448px; margin-top: 12px; }
38
+ body[data-dashboard-theme="profit-atlas@1"] .details { padding: 12px 34px 14px; }
39
+ body[data-dashboard-theme="profit-atlas@1"] .details h2 { display: inline; font-size: 20px; }
40
+ body[data-dashboard-theme="profit-atlas@1"] .details > .note { display: inline; margin-left: 18px; }
41
+ body[data-dashboard-theme="profit-atlas@1"] .table-scroll { max-height: 150px; margin-top: 10px; border: 1px solid var(--line); border-radius: 7px; }
42
+ body[data-dashboard-theme="profit-atlas@1"] th, body[data-dashboard-theme="profit-atlas@1"] td { padding: 7px 16px; }
43
+ body[data-dashboard-theme="profit-atlas@1"] .table-bar { display: none; }
44
+ body[data-dashboard-theme="profit-atlas@1"] .status { padding: 0 34px; }
45
+ body[data-dashboard-theme="profit-atlas@1"] .methodology { padding: 4px 34px 12px; }
46
+ body[data-dashboard-theme="profit-atlas@1"] footer { padding: 10px 34px; }
47
+ @media (max-width: 1100px) {
48
+ .atlas-header { flex-wrap: wrap; gap: 14px; }
49
+ .atlas-header .filters { margin-left: auto; }
50
+ .atlas-metrics .metric { padding-inline: 14px; }
51
+ .atlas-metrics .metric output { font-size: 25px; }
52
+ .atlas-plots { grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 18px; }
53
+ .atlas-plots .panel:last-child { padding-left: 18px; }
54
+ }
55
+ @media (max-width: 760px) {
56
+ .atlas-plots { grid-template-columns: 1fr; }
57
+ .atlas-plots .panel:last-child { padding: 20px 0 0; border-left: 0; border-top: 1px solid var(--line); }
58
+ }
59
+ @media (max-width: 680px) {
60
+ .atlas-header { display: block; padding: 20px 16px; }
61
+ .atlas-header h1 { font-size: 28px; }
62
+ .atlas-header .filters { display: grid; margin-top: 20px; padding: 0; gap: 8px; }
63
+ .atlas-header .filter-field { width: auto; }
64
+ .atlas-metrics { grid-template-columns: 1fr 1fr; gap: 18px 12px; padding: 18px 16px; }
65
+ .atlas-metrics .metric, .atlas-metrics .metric:last-child { padding: 0; border: 0; }
66
+ .atlas-metrics .metric:first-child { grid-column: 1 / -1; }
67
+ .atlas-metrics .metric output { font-size: 28px; }
68
+ .atlas-plots { padding: 20px 16px; }
69
+ .atlas-plots .chart { height: 390px; }
70
+ .atlas-plots .panel:last-child .chart { height: 448px; }
71
+ body[data-dashboard-theme="profit-atlas@1"] .details { padding: 16px; }
72
+ body[data-dashboard-theme="profit-atlas@1"] .details > .note { display: block; margin: 6px 0 0; }
73
+ body[data-dashboard-theme="profit-atlas@1"] .table-scroll { max-height: 250px; }
74
+ body[data-dashboard-theme="profit-atlas@1"] .status, body[data-dashboard-theme="profit-atlas@1"] .methodology, body[data-dashboard-theme="profit-atlas@1"] footer { padding-inline: 16px; }
75
+ }
@@ -0,0 +1,43 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 Lucide Icons and Contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
+
17
+ ---
18
+
19
+ The following Lucide icons are derived from the Feather project:
20
+
21
+ airplay, alert-circle, alert-octagon, alert-triangle, aperture, arrow-down-circle, arrow-down-left, arrow-down-right, arrow-down, arrow-left-circle, arrow-left, arrow-right-circle, arrow-right, arrow-up-circle, arrow-up-left, arrow-up-right, arrow-up, at-sign, calendar, cast, check, chevron-down, chevron-left, chevron-right, chevron-up, chevrons-down, chevrons-left, chevrons-right, chevrons-up, circle, clipboard, clock, code, columns, command, compass, corner-down-left, corner-down-right, corner-left-down, corner-left-up, corner-right-down, corner-right-up, corner-up-left, corner-up-right, crosshair, database, divide-circle, divide-square, dollar-sign, download, external-link, feather, frown, hash, headphones, help-circle, info, italic, key, layout, life-buoy, link-2, link, loader, lock, log-in, log-out, maximize, meh, minimize, minimize-2, minus-circle, minus-square, minus, monitor, moon, more-horizontal, more-vertical, move, music, navigation-2, navigation, octagon, pause-circle, percent, plus-circle, plus-square, plus, power, radio, rss, search, server, share, shopping-bag, sidebar, smartphone, smile, square, table-2, tablet, target, terminal, trash-2, trash, triangle, tv, type, upload, x-circle, x-octagon, x-square, x, zoom-in, zoom-out
22
+
23
+ The MIT License (MIT) (for the icons listed above)
24
+
25
+ Copyright (c) 2013-present Cole Bemis
26
+
27
+ Permission is hereby granted, free of charge, to any person obtaining a copy
28
+ of this software and associated documentation files (the "Software"), to deal
29
+ in the Software without restriction, including without limitation the rights
30
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
31
+ copies of the Software, and to permit persons to whom the Software is
32
+ furnished to do so, subject to the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
38
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
39
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
40
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
41
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
42
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
43
+ SOFTWARE.
@@ -0,0 +1,144 @@
1
+ # Trend Workspace v1 — Agent authoring contract
2
+
3
+ Use this theme when the user asks for the enterprise BI style, the left KPI rail,
4
+ the trend-first design, or the polished two-line dropdown filters. It is a reusable
5
+ design system, not a fixed Superstore screenshot. Author the business HTML in the
6
+ current Project and save it through dashboard tools yourself.
7
+
8
+ ## Read and reuse
9
+
10
+ Read these files with `read_skill_file`, resolving paths relative to the installed
11
+ live-dashboard skill. Read complete files; do not use a truncated preview as source.
12
+
13
+ 1. `theme.css`: tokens, layout, typography, responsive/dark states, filter appearance.
14
+ 2. `icons.js`: two exact Lucide library icons; keep `ICON_LICENSE.txt` attribution.
15
+ 3. `select.js`: keyboard-accessible dropdown enhancement of a typed native select.
16
+ 4. `charts.js`: ECharts line, ranked bar and scatter recipes with explicit fields.
17
+ 5. `layout.html`: structural fragment explaining placement, not a standalone page.
18
+ 6. The main skill's `references/html-runtime.md` and lifecycle example.
19
+
20
+ Call `dashboard_libraries` and save with `libraries:["echarts","trend-workspace"]`.
21
+ The host injects the theme CSS, icons, select and chart code from its versioned,
22
+ SHA-256-pinned document library. Do not retype those shared modules in every HTML.
23
+ They remain in the skill for inspection and adaptation. No CDN or relative URLs.
24
+
25
+ Use `window.TrendWorkspace.enhanceSelect(select)` and
26
+ `window.TrendWorkspace.charts.line/horizontal/scatter(...)`. The library does not
27
+ query data or insert business content. You write the layout, configuration and
28
+ SDK query/subscription runtime, then save the draft. Existing saved library bytes
29
+ remain frozen when a later plugin version is installed; opt in to library upgrades.
30
+ Keep source lines below 500 UTF-8 bytes; do not minify.
31
+
32
+ Full worked examples are `examples/superstore/overview.html`, `profitability.html`
33
+ and `customers.html`. Their `build_pages.py` is a developer packaging helper; the
34
+ Agent does not need Python, the repository or this developer's computer. The
35
+ installed skill contains every CSS/JS asset and complete HTML example.
36
+
37
+ ## Visual invariants
38
+
39
+ - White analysis surface, pale blue KPI rail, navy text. Sales blue, profit teal,
40
+ negative values red. Never encode a business threshold by an invented color rule.
41
+ - Desktop rail is about 22% of the content width. Put title, source/demo label and
42
+ five or fewer important KPIs in it; large first KPI, quieter supporting totals.
43
+ - Top-right filter strip; the analysis area contains one main chart region and
44
+ compact supporting ranking/table regions. Avoid equal-size card grids.
45
+ - Sales and profit time series use aligned time axes with **separate, clearly
46
+ labelled value scales**. Do not smooth away negative months or seasonal spikes.
47
+ - Ranked bars are descending with direct values; bars use zero as baseline.
48
+ Profit rankings preserve negative values. Scatter plots retain a zero-profit
49
+ reference and name negative items; keep numeric detail in the accompanying table.
50
+ - Use `system-ui, "PingFang SC", sans-serif`, tabular numbers, headings 24–26px,
51
+ KPI values 28–40px, controls/body 14px, supporting labels at least 12px.
52
+ - No browser chrome, decorative charts, fabricated growth badges or chart images.
53
+ Real ECharts remains interactive; the design reference is not a data source.
54
+ - At 680px the rail becomes a top summary; charts stack and filters retain three
55
+ accessible compact fields. Only the named table region may scroll horizontally.
56
+ - Dark mode comes from `await live.ready()` and `.dark`; do not read localStorage.
57
+
58
+ ## Select component contract
59
+
60
+ Use one `.filter-field` per verified control:
61
+
62
+ ```html
63
+ <div class="filter-field">
64
+ <label for="filter-region">区域</label>
65
+ <select id="filter-region" data-dashboard-control="region" disabled></select>
66
+ </div>
67
+ ```
68
+
69
+ After receiving `live.ready()`, populate it from verified allowed values. Preserve
70
+ JSON types in option values, e.g. `new Option(label, JSON.stringify(value))` and
71
+ `JSON.parse(select.value)` in the change handler. Call
72
+ `window.TrendWorkspace.enhanceSelect(select)` only after populating options and current value.
73
+ The component renders the two-line caption/value trigger, focus ring and menu.
74
+
75
+ Keep the native select and its ID as the typed data-control contract. It becomes
76
+ hidden after enhancement; do not render another independent data control with the
77
+ same ID. Selecting a menu row dispatches the select's normal `change` event.
78
+ Every change must query **all** affected Bindings; visual selection is not proof
79
+ of a data query. Set native `select.disabled` while querying; the custom trigger
80
+ tracks it. The menu supports arrows, Home/End, Enter/Space, Escape, Tab, typeahead,
81
+ click outside and an explicit selected check. Do not replace it with a CSS-only
82
+ fake menu. Four options do not need search or an Apply button.
83
+
84
+ The project/dashboard selector and Publish/More toolbar belong to the host View.
85
+ Do not duplicate those controls inside the authored HTML.
86
+
87
+ ## Adapt to a new dataset
88
+
89
+ 1. Discover the real source and field schema. Define the business question, grain,
90
+ units, source period, denominator and allowed filters before choosing charts.
91
+ 2. Select layout by task: time trend uses overview's main region + lower band;
92
+ product contribution uses two related plots; customer structure uses compact
93
+ comparisons and a table. Keep the same rail, controls and tokens.
94
+ 3. Map actual fields explicitly into `window.TrendWorkspace.charts.line`, `.horizontal`
95
+ or `.scatter`; arguments name `dimension`, `measure` or `x`/`y` and labels.
96
+ Do not infer semantics from a field name or reuse Superstore field names blindly.
97
+ 4. Build KPI calculations from independent backend totals. Ratios use the same
98
+ filtered numerator/denominator. Distinct counts must not be summed across groups.
99
+ 5. Subscribe to every saved Binding; render only when the complete current result
100
+ set has arrived. Clear previous results on filter changes, denied/failed queries,
101
+ truncation and empty scopes. Source timestamps must come from query freshness.
102
+ 6. Preserve decimal strings for financial display; only validated finite numbers
103
+ enter charts/ratios. Use DOM textContent for labels, not untrusted innerHTML.
104
+ 7. Write the complete HTML with SandboxFile, preview/test each query and filter,
105
+ then save a real draft. Do not deliver a static screenshot instead.
106
+
107
+ ## Upgrade an existing dashboard to this theme
108
+
109
+ Use `dashboard_list` in the current project and exact Artifact IDs. Open each
110
+ explicit target with `dashboard_open`, read its current editing copy, and retain
111
+ the returned `saveArguments`. Preserve Binding IDs, sources, controls, units,
112
+ title/Artifact identity and all existing HTML IDs. Add `chart-profit` only when
113
+ splitting an existing sales/profit trend; retain `chart-primary` and `chart-secondary`.
114
+
115
+ For the unmodified Superstore recipe, the current full HTML already composes this
116
+ theme and the same binding contract. You may reuse that page after verifying the
117
+ opened dashboard has the expected bindings and no user customizations. Replace
118
+ the **opened editing file** with exact SandboxFile edits, then use
119
+ `dashboard_update` with its saved envelope, htmlPath and
120
+ `libraries:["echarts","trend-workspace"]`. Never create a duplicate
121
+ Artifact to avoid editing. For customized or other-data dashboards, transplant
122
+ the theme assets and structural sections while retaining their business runtime.
123
+
124
+ For existing files, split edits into logical CSS, layout and script sections;
125
+ keep each old/new text under about 4,000 UTF-8 bytes. Never send a whole old + new
126
+ page in one tool call. Use `sandbox_multi_edit_file` with a few bounded edits, or
127
+ insert complete sections before a unique temporary marker. Remove temporary
128
+ markers before saving. Never use sandbox_write_file on an existing editing copy.
129
+ For a new long file, create the first section and append the remaining sections
130
+ with sandbox_append_file. Complete and inspect the document before any save. A failed or partial file edit
131
+ is not a saved dashboard. Keep three-page upgrades sequential, one page per save.
132
+
133
+ ## Acceptance before reporting success
134
+
135
+ - Verify actual successful draft receipts, title and intended Artifact IDs.
136
+ - Open the saved page: rail, separate trend scales, direct-value ranking and
137
+ two-line select triggers must be present; do not rely on a theme-name attribute.
138
+ - Change a real filter; all KPIs, plots and detail rows must reflect the new scope.
139
+ - Check open-menu selected state, keyboard selection/Escape, viewport edges,
140
+ loading, empty/error/recovery, 390px layout and dark-mode contrast.
141
+ - Confirm refresh does not reset the controls and the page uses current results.
142
+ - Preserve user customizations and leave formal publication to the human.
143
+ - If no rendered inspection capability is available, report that visual QA is
144
+ pending. Never claim a screenshot comparison from source code alone.