@datashow/super-visual-cli 0.0.0-stage → 0.1.0-rc.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 (30) hide show
  1. package/bin/supervisual.mjs +9 -0
  2. package/package.json +45 -4
  3. package/src/fabric-client.mjs +101 -0
  4. package/src/main.mjs +663 -0
  5. package/src/project-target.mjs +80 -0
  6. package/src/project.mjs +289 -0
  7. package/src/semantic-preview.mjs +528 -0
  8. package/templates/blank/README.md +26 -0
  9. package/templates/blank/contracts/page-set.yaml +78 -0
  10. package/templates/blank/contracts/report-source.json +30 -0
  11. package/templates/blank/contracts/semantic-model.yaml +39 -0
  12. package/templates/blank/contracts/widget-broker.json +33 -0
  13. package/templates/blank/deployment/CERTIFICATION.md +18 -0
  14. package/templates/blank/package.json +15 -0
  15. package/templates/blank/source/report-design.less +20 -0
  16. package/templates/blank/source/reportApplication.tsx +29 -0
  17. package/templates/blank/supervisual.yaml +24 -0
  18. package/templates/hello-world/README.md +29 -0
  19. package/templates/hello-world/contracts/page-set.yaml +74 -0
  20. package/templates/hello-world/contracts/report-source.json +45 -0
  21. package/templates/hello-world/contracts/semantic-model.yaml +32 -0
  22. package/templates/hello-world/contracts/widget-broker.json +37 -0
  23. package/templates/hello-world/deployment/CERTIFICATION.md +17 -0
  24. package/templates/hello-world/package.json +15 -0
  25. package/templates/hello-world/source/report-design.less +35 -0
  26. package/templates/hello-world/source/reportApplication.tsx +69 -0
  27. package/templates/hello-world/supervisual.yaml +24 -0
  28. package/templates/semantic-preview/index.html +14 -0
  29. package/templates/semantic-preview/src/main.tsx +172 -0
  30. package/README.md +0 -3
@@ -0,0 +1,172 @@
1
+ import { createElement } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import "../../solution/Visual/style/visual.less";
4
+ import { ReportApplication } from "../../solution/Visual/src/reportApplication";
5
+ import { materializeBrokerRows } from "../../solution/Visual/src/brokerRows";
6
+ import { dependentWidgets } from "../../solution/Visual/src/dependency";
7
+ import { filterValueFromLabel } from "../../solution/Visual/src/filter";
8
+ import { DATASET_KEYS, emptyDatasets, emptyStatuses, type ActiveSelections, type DashboardModel, type DatasetKey, type MetricPoint, type WidgetStatus } from "../../solution/Visual/src/model";
9
+ import type { ClaudeParams } from "../../solution/Visual/src/claudeDashboard/types";
10
+ import { PREVIEW_TOKEN } from "./config";
11
+
12
+ const COLORS = ["#167d6c", "#315d9b", "#cc7a29", "#7b5ea7", "#a94442", "#4f7f52", "#955f75", "#4f788e"];
13
+ const DATASET_SET = new Set<string>(DATASET_KEYS);
14
+
15
+ type Snapshot = {
16
+ status: "loading" | "ready" | "error";
17
+ requestSequence: number;
18
+ receivedWidgets: DatasetKey[];
19
+ receivedRowCount: number;
20
+ rowCounts: Record<DatasetKey, number>;
21
+ activeSelections: Record<string, string>;
22
+ context: string;
23
+ error: string | null;
24
+ };
25
+
26
+ declare global {
27
+ interface Window { __SUPERVISUAL_PREVIEW__: { snapshot: () => Snapshot } }
28
+ }
29
+
30
+ class SemanticPreviewHost {
31
+ private readonly root = createRoot(document.getElementById("root")!);
32
+ private readonly datasets = emptyDatasets();
33
+ private readonly statuses: Record<DatasetKey, WidgetStatus> = emptyStatuses();
34
+ private readonly selections: ActiveSelections = {};
35
+ private readonly parameters: ClaudeParams = {};
36
+ private visible: DatasetKey[] = [...DATASET_KEYS];
37
+ private context = "";
38
+ private warnings: string[] = [];
39
+ private requestSequence = 0;
40
+ private receivedRowCount = 0;
41
+ private error: string | null = null;
42
+ private controller: AbortController | null = null;
43
+ private scheduled: number | null = null;
44
+ private readonly handleSelect = (widget: DatasetKey, point: MetricPoint): void => this.select(widget, point);
45
+ private readonly handleClear = (widget: DatasetKey): void => this.clear(widget);
46
+ private readonly handleVisibleDatasets = (datasets: DatasetKey[]): void => this.schedule(datasets);
47
+ private readonly handleClaudeParams = (changes: ClaudeParams): void => this.applyClaudeParams(changes);
48
+ private readonly handleClaudeRefresh = (): void => this.schedule(this.visible);
49
+ private readonly handleClaudeLink = (): void => undefined;
50
+
51
+ constructor() {
52
+ for (const key of DATASET_KEYS) this.statuses[key] = "loading";
53
+ window.__SUPERVISUAL_PREVIEW__ = { snapshot: () => this.snapshot() };
54
+ this.render();
55
+ this.schedule(this.visible);
56
+ }
57
+
58
+ private schedule(datasets: DatasetKey[]): void {
59
+ this.visible = [...new Set(datasets)].filter((key) => DATASET_SET.has(key));
60
+ if (this.scheduled !== null) window.clearTimeout(this.scheduled);
61
+ this.scheduled = window.setTimeout(() => { this.scheduled = null; void this.request(); }, 0);
62
+ }
63
+
64
+ private async request(): Promise<void> {
65
+ const requested = [...this.visible];
66
+ if (!requested.length) return;
67
+ this.controller?.abort();
68
+ this.controller = new AbortController();
69
+ const sequence = ++this.requestSequence;
70
+ this.error = null;
71
+ for (const key of requested) this.statuses[key] = this.datasets[key] ? "stale" : "loading";
72
+ this.render();
73
+ try {
74
+ const response = await fetch("/__supervisual/query", {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/json", "X-Supervisual-Preview-Token": PREVIEW_TOKEN },
77
+ body: JSON.stringify({
78
+ widgets: requested,
79
+ selections: Object.fromEntries(Object.entries(this.selections).map(([key, selection]) => [key, selection?.value])),
80
+ parameters: this.parameters,
81
+ }),
82
+ signal: this.controller.signal,
83
+ });
84
+ const result = await response.json();
85
+ if (!response.ok || !result.ok) throw new Error(result.error || `Semantic preview query failed (${response.status})`);
86
+ if (sequence !== this.requestSequence) return;
87
+ const materialized = materializeBrokerRows(result.rows, (label) => COLORS[Math.abs([...label].reduce((sum, character) => sum + character.charCodeAt(0), 0)) % COLORS.length]);
88
+ for (const key of requested) {
89
+ this.datasets[key] = materialized.datasets[key] ?? { key, categoryDisplayName: "Label", measureDisplayName: "Widget Value", series: [], points: [] };
90
+ this.statuses[key] = "fresh";
91
+ }
92
+ this.context = materialized.context || this.context;
93
+ this.receivedRowCount = materialized.receivedRowCount;
94
+ this.warnings = materialized.warnings;
95
+ this.render();
96
+ } catch (error) {
97
+ if (error instanceof DOMException && error.name === "AbortError") return;
98
+ if (sequence !== this.requestSequence) return;
99
+ this.error = error instanceof Error ? error.message : String(error);
100
+ for (const key of requested) this.statuses[key] = "error";
101
+ this.render();
102
+ }
103
+ }
104
+
105
+ private select(widget: DatasetKey, point: MetricPoint): void {
106
+ const value = filterValueFromLabel(point.dimensions.at(-1)?.key || point.axisKey);
107
+ this.selections[widget] = { widget, pointKey: point.key, label: point.label, value };
108
+ for (const key of dependentWidgets(widget)) this.statuses[key] = "stale";
109
+ this.schedule(this.visible);
110
+ }
111
+
112
+ private clear(widget: DatasetKey): void {
113
+ delete this.selections[widget];
114
+ this.schedule(this.visible);
115
+ }
116
+
117
+ private applyClaudeParams(changes: ClaudeParams): void {
118
+ for (const [name, value] of Object.entries(changes)) {
119
+ if (value === null || value === undefined || value === "" || (Array.isArray(value) && value.length === 0)) delete this.parameters[name];
120
+ else this.parameters[name] = value;
121
+ }
122
+ this.schedule(this.visible);
123
+ }
124
+
125
+ private snapshot(): Snapshot {
126
+ const rowCounts = Object.fromEntries(DATASET_KEYS.map((key) => [key, this.datasets[key]?.points.length ?? 0])) as Record<DatasetKey, number>;
127
+ return {
128
+ status: this.error ? "error" : this.visible.some((key) => ["loading", "stale"].includes(this.statuses[key])) ? "loading" : "ready",
129
+ requestSequence: this.requestSequence,
130
+ receivedWidgets: DATASET_KEYS.filter((key) => Boolean(this.datasets[key])),
131
+ receivedRowCount: this.receivedRowCount,
132
+ rowCounts,
133
+ activeSelections: Object.fromEntries(Object.entries(this.selections).map(([key, value]) => [key, value?.label ?? ""])),
134
+ context: this.context,
135
+ error: this.error,
136
+ };
137
+ }
138
+
139
+ private model(): DashboardModel {
140
+ const snapshot = this.snapshot();
141
+ const valueSums = Object.fromEntries(DATASET_KEYS.map((key) => [key, this.datasets[key]?.points.reduce((sum, point) => sum + point.value, 0) ?? 0])) as Record<DatasetKey, number>;
142
+ return { datasets: this.datasets, status: this.statuses, warnings: [...this.warnings, ...(this.error ? [this.error] : [])], diagnostics: {
143
+ dataViewCount: 0, updateSequence: this.requestSequence, dataUpdateSequence: this.requestSequence, requestSequence: this.requestSequence,
144
+ receivedWidgets: snapshot.receivedWidgets, receivedRowCount: snapshot.receivedRowCount, widgetStatus: this.statuses,
145
+ rowCounts: snapshot.rowCounts, valueSums,
146
+ selectionCandidates: Object.fromEntries(DATASET_KEYS.map((key) => [key, (this.datasets[key]?.points ?? []).map((point) => ({ key: point.key, label: point.label }))])) as DashboardModel["diagnostics"]["selectionCandidates"],
147
+ activeSelections: snapshot.activeSelections, activeRequest: this.visible, context: this.context, lastRequestMs: null, events: [],
148
+ } };
149
+ }
150
+
151
+ private render(): void {
152
+ const snapshot = this.snapshot();
153
+ const status = document.getElementById("supervisual-preview-status")!;
154
+ status.dataset.state = snapshot.status;
155
+ status.textContent = snapshot.status === "error" ? `Semantic preview error: ${snapshot.error}` : snapshot.status === "loading" ? "Querying the live semantic model…" : `Live semantic model · ${snapshot.receivedRowCount} broker rows`;
156
+ this.root.render(createElement(ReportApplication, {
157
+ model: this.model(), selections: this.selections,
158
+ onSelect: this.handleSelect,
159
+ onClear: this.handleClear,
160
+ onVisibleDatasetsChange: this.handleVisibleDatasets,
161
+ onClaudeParamsChange: this.handleClaudeParams,
162
+ onClaudeRefresh: this.handleClaudeRefresh,
163
+ onClaudeLinkChange: this.handleClaudeLink,
164
+ }));
165
+ }
166
+ }
167
+
168
+ document.documentElement.style.height = "100%";
169
+ document.body.style.cssText = "height:100%;margin:0";
170
+ const banner = document.getElementById("supervisual-preview-status")!;
171
+ banner.style.cssText = "position:fixed;right:12px;bottom:12px;z-index:2147483647;padding:7px 10px;border-radius:5px;background:#173c36;color:white;font:12px/1.3 system-ui;box-shadow:0 2px 10px #0003";
172
+ new SemanticPreviewHost();
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.