@contractspec/example.analytics-dashboard 3.8.0 → 3.9.2

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.
@@ -1,97 +1,186 @@
1
1
  // src/ui/AnalyticsQueriesTable.tsx
2
+ import { DataTable } from "@contractspec/lib.design-system";
3
+ import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
4
+ import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
5
+ import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
6
+ import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
2
7
  import { jsxDEV } from "react/jsx-dev-runtime";
3
8
  "use client";
4
9
  var QUERY_TYPE_COLORS = {
5
- SQL: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
6
- METRIC: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400",
7
- AGGREGATION: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
8
- CUSTOM: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
10
+ SQL: "default",
11
+ METRIC: "secondary",
12
+ AGGREGATION: "secondary",
13
+ CUSTOM: "outline"
9
14
  };
15
+ function formatJson(value) {
16
+ return JSON.stringify(value, null, 2);
17
+ }
10
18
  function AnalyticsQueriesTable({ queries }) {
11
- return /* @__PURE__ */ jsxDEV("div", {
12
- className: "rounded-lg border border-border",
13
- children: /* @__PURE__ */ jsxDEV("table", {
14
- className: "w-full",
15
- children: [
16
- /* @__PURE__ */ jsxDEV("thead", {
17
- className: "border-border border-b bg-muted/30",
18
- children: /* @__PURE__ */ jsxDEV("tr", {
19
- children: [
20
- /* @__PURE__ */ jsxDEV("th", {
21
- className: "px-4 py-3 text-left font-medium text-sm",
22
- children: "Query"
23
- }, undefined, false, undefined, this),
24
- /* @__PURE__ */ jsxDEV("th", {
25
- className: "px-4 py-3 text-left font-medium text-sm",
26
- children: "Type"
27
- }, undefined, false, undefined, this),
28
- /* @__PURE__ */ jsxDEV("th", {
29
- className: "px-4 py-3 text-left font-medium text-sm",
30
- children: "Cache TTL"
31
- }, undefined, false, undefined, this),
32
- /* @__PURE__ */ jsxDEV("th", {
33
- className: "px-4 py-3 text-left font-medium text-sm",
34
- children: "Shared"
35
- }, undefined, false, undefined, this)
36
- ]
37
- }, undefined, true, undefined, this)
38
- }, undefined, false, undefined, this),
39
- /* @__PURE__ */ jsxDEV("tbody", {
40
- className: "divide-y divide-border",
19
+ const controller = useContractTable({
20
+ data: queries,
21
+ columns: [
22
+ {
23
+ id: "query",
24
+ header: "Query",
25
+ label: "Query",
26
+ accessor: (query) => query.name,
27
+ cell: ({ item }) => /* @__PURE__ */ jsxDEV(VStack, {
28
+ gap: "xs",
41
29
  children: [
42
- queries.map((query) => /* @__PURE__ */ jsxDEV("tr", {
43
- className: "hover:bg-muted/50",
30
+ /* @__PURE__ */ jsxDEV(Text, {
31
+ className: "font-medium text-sm",
32
+ children: item.name
33
+ }, undefined, false, undefined, this),
34
+ /* @__PURE__ */ jsxDEV(Text, {
35
+ className: "text-muted-foreground text-xs",
44
36
  children: [
45
- /* @__PURE__ */ jsxDEV("td", {
46
- className: "px-4 py-3",
47
- children: [
48
- /* @__PURE__ */ jsxDEV("div", {
49
- className: "font-medium",
50
- children: query.name
51
- }, undefined, false, undefined, this),
52
- /* @__PURE__ */ jsxDEV("div", {
53
- className: "text-muted-foreground text-sm",
54
- children: query.description
55
- }, undefined, false, undefined, this)
56
- ]
57
- }, undefined, true, undefined, this),
58
- /* @__PURE__ */ jsxDEV("td", {
59
- className: "px-4 py-3",
60
- children: /* @__PURE__ */ jsxDEV("span", {
61
- className: `inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${QUERY_TYPE_COLORS[query.type] ?? ""}`,
62
- children: query.type
63
- }, undefined, false, undefined, this)
64
- }, undefined, false, undefined, this),
65
- /* @__PURE__ */ jsxDEV("td", {
66
- className: "px-4 py-3 text-muted-foreground text-sm",
67
- children: [
68
- query.cacheTtlSeconds,
69
- "s"
70
- ]
71
- }, undefined, true, undefined, this),
72
- /* @__PURE__ */ jsxDEV("td", {
73
- className: "px-4 py-3",
74
- children: query.isShared ? /* @__PURE__ */ jsxDEV("span", {
75
- className: "text-green-600 dark:text-green-400",
76
- children: "✓"
77
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV("span", {
78
- className: "text-muted-foreground",
79
- children: "—"
80
- }, undefined, false, undefined, this)
81
- }, undefined, false, undefined, this)
37
+ "Updated ",
38
+ item.updatedAt.toLocaleDateString()
82
39
  ]
83
- }, query.id, true, undefined, this)),
84
- queries.length === 0 && /* @__PURE__ */ jsxDEV("tr", {
85
- children: /* @__PURE__ */ jsxDEV("td", {
86
- colSpan: 4,
87
- className: "px-4 py-8 text-center text-muted-foreground",
88
- children: "No queries saved"
89
- }, undefined, false, undefined, this)
40
+ }, undefined, true, undefined, this)
41
+ ]
42
+ }, undefined, true, undefined, this),
43
+ size: 240,
44
+ minSize: 180,
45
+ canSort: true,
46
+ canPin: true,
47
+ canResize: true
48
+ },
49
+ {
50
+ id: "description",
51
+ header: "Description",
52
+ label: "Description",
53
+ accessor: (query) => query.description ?? "",
54
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV(Text, {
55
+ className: "line-clamp-2 text-muted-foreground text-sm",
56
+ children: String(value || "No description")
57
+ }, undefined, false, undefined, this),
58
+ size: 300,
59
+ minSize: 220,
60
+ canSort: false,
61
+ canHide: true,
62
+ canResize: true
63
+ },
64
+ {
65
+ id: "type",
66
+ header: "Type",
67
+ label: "Type",
68
+ accessorKey: "type",
69
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV(Badge, {
70
+ variant: QUERY_TYPE_COLORS[String(value)],
71
+ children: String(value)
72
+ }, undefined, false, undefined, this),
73
+ size: 150,
74
+ canSort: true,
75
+ canHide: true,
76
+ canResize: true
77
+ },
78
+ {
79
+ id: "cacheTtlSeconds",
80
+ header: "Cache TTL",
81
+ label: "Cache TTL",
82
+ accessorKey: "cacheTtlSeconds",
83
+ cell: ({ value }) => `${String(value)}s`,
84
+ align: "right",
85
+ size: 140,
86
+ canSort: true,
87
+ canResize: true
88
+ },
89
+ {
90
+ id: "isShared",
91
+ header: "Shared",
92
+ label: "Shared",
93
+ accessorKey: "isShared",
94
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV(Badge, {
95
+ variant: value ? "default" : "outline",
96
+ children: value ? "Shared" : "Private"
97
+ }, undefined, false, undefined, this),
98
+ size: 140,
99
+ canSort: true,
100
+ canHide: true,
101
+ canResize: true
102
+ }
103
+ ],
104
+ initialState: {
105
+ pagination: { pageIndex: 0, pageSize: 3 },
106
+ columnVisibility: { description: false },
107
+ columnPinning: { left: ["query"], right: [] }
108
+ },
109
+ renderExpandedContent: (query) => /* @__PURE__ */ jsxDEV(VStack, {
110
+ gap: "sm",
111
+ className: "py-2",
112
+ children: [
113
+ /* @__PURE__ */ jsxDEV(VStack, {
114
+ gap: "xs",
115
+ children: [
116
+ /* @__PURE__ */ jsxDEV(Text, {
117
+ className: "font-medium text-sm",
118
+ children: "Description"
119
+ }, undefined, false, undefined, this),
120
+ /* @__PURE__ */ jsxDEV(Text, {
121
+ className: "text-muted-foreground text-sm",
122
+ children: query.description ?? "No description"
123
+ }, undefined, false, undefined, this)
124
+ ]
125
+ }, undefined, true, undefined, this),
126
+ query.sql ? /* @__PURE__ */ jsxDEV(VStack, {
127
+ gap: "xs",
128
+ children: [
129
+ /* @__PURE__ */ jsxDEV(Text, {
130
+ className: "font-medium text-sm",
131
+ children: "SQL"
132
+ }, undefined, false, undefined, this),
133
+ /* @__PURE__ */ jsxDEV("pre", {
134
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
135
+ children: query.sql
136
+ }, undefined, false, undefined, this)
137
+ ]
138
+ }, undefined, true, undefined, this) : null,
139
+ /* @__PURE__ */ jsxDEV(VStack, {
140
+ gap: "xs",
141
+ children: [
142
+ /* @__PURE__ */ jsxDEV(Text, {
143
+ className: "font-medium text-sm",
144
+ children: "Definition"
145
+ }, undefined, false, undefined, this),
146
+ /* @__PURE__ */ jsxDEV("pre", {
147
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
148
+ children: formatJson(query.definition)
90
149
  }, undefined, false, undefined, this)
91
150
  ]
92
151
  }, undefined, true, undefined, this)
93
152
  ]
94
- }, undefined, true, undefined, this)
153
+ }, undefined, true, undefined, this),
154
+ getCanExpand: () => true
155
+ });
156
+ return /* @__PURE__ */ jsxDEV(DataTable, {
157
+ controller,
158
+ title: "Saved Queries",
159
+ description: "Client-mode table using the shared ContractSpec controller and renderer.",
160
+ toolbar: /* @__PURE__ */ jsxDEV(HStack, {
161
+ gap: "sm",
162
+ className: "flex-wrap",
163
+ children: [
164
+ /* @__PURE__ */ jsxDEV(Text, {
165
+ className: "text-muted-foreground text-sm",
166
+ children: [
167
+ queries.length,
168
+ " queries"
169
+ ]
170
+ }, undefined, true, undefined, this),
171
+ /* @__PURE__ */ jsxDEV(Text, {
172
+ className: "text-muted-foreground text-sm",
173
+ children: [
174
+ queries.filter((query) => query.isShared).length,
175
+ " shared"
176
+ ]
177
+ }, undefined, true, undefined, this)
178
+ ]
179
+ }, undefined, true, undefined, this),
180
+ emptyState: /* @__PURE__ */ jsxDEV("div", {
181
+ className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
182
+ children: "No queries saved"
183
+ }, undefined, false, undefined, this)
95
184
  }, undefined, false, undefined, this);
96
185
  }
97
186
  export {
@@ -682,99 +682,188 @@ function gridSpanClass(gridWidth) {
682
682
  }
683
683
 
684
684
  // src/ui/AnalyticsQueriesTable.tsx
685
+ import { DataTable } from "@contractspec/lib.design-system";
686
+ import { useContractTable } from "@contractspec/lib.presentation-runtime-react";
687
+ import { Badge } from "@contractspec/lib.ui-kit-web/ui/badge";
688
+ import { HStack, VStack } from "@contractspec/lib.ui-kit-web/ui/stack";
689
+ import { Text } from "@contractspec/lib.ui-kit-web/ui/text";
685
690
  import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
686
691
  "use client";
687
692
  var QUERY_TYPE_COLORS = {
688
- SQL: "bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400",
689
- METRIC: "bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400",
690
- AGGREGATION: "bg-indigo-100 text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-400",
691
- CUSTOM: "bg-gray-100 text-gray-700 dark:bg-gray-900/30 dark:text-gray-400"
693
+ SQL: "default",
694
+ METRIC: "secondary",
695
+ AGGREGATION: "secondary",
696
+ CUSTOM: "outline"
692
697
  };
698
+ function formatJson(value) {
699
+ return JSON.stringify(value, null, 2);
700
+ }
693
701
  function AnalyticsQueriesTable({ queries }) {
694
- return /* @__PURE__ */ jsxDEV2("div", {
695
- className: "rounded-lg border border-border",
696
- children: /* @__PURE__ */ jsxDEV2("table", {
697
- className: "w-full",
698
- children: [
699
- /* @__PURE__ */ jsxDEV2("thead", {
700
- className: "border-border border-b bg-muted/30",
701
- children: /* @__PURE__ */ jsxDEV2("tr", {
702
- children: [
703
- /* @__PURE__ */ jsxDEV2("th", {
704
- className: "px-4 py-3 text-left font-medium text-sm",
705
- children: "Query"
706
- }, undefined, false, undefined, this),
707
- /* @__PURE__ */ jsxDEV2("th", {
708
- className: "px-4 py-3 text-left font-medium text-sm",
709
- children: "Type"
710
- }, undefined, false, undefined, this),
711
- /* @__PURE__ */ jsxDEV2("th", {
712
- className: "px-4 py-3 text-left font-medium text-sm",
713
- children: "Cache TTL"
714
- }, undefined, false, undefined, this),
715
- /* @__PURE__ */ jsxDEV2("th", {
716
- className: "px-4 py-3 text-left font-medium text-sm",
717
- children: "Shared"
718
- }, undefined, false, undefined, this)
719
- ]
720
- }, undefined, true, undefined, this)
721
- }, undefined, false, undefined, this),
722
- /* @__PURE__ */ jsxDEV2("tbody", {
723
- className: "divide-y divide-border",
702
+ const controller = useContractTable({
703
+ data: queries,
704
+ columns: [
705
+ {
706
+ id: "query",
707
+ header: "Query",
708
+ label: "Query",
709
+ accessor: (query) => query.name,
710
+ cell: ({ item }) => /* @__PURE__ */ jsxDEV2(VStack, {
711
+ gap: "xs",
724
712
  children: [
725
- queries.map((query) => /* @__PURE__ */ jsxDEV2("tr", {
726
- className: "hover:bg-muted/50",
713
+ /* @__PURE__ */ jsxDEV2(Text, {
714
+ className: "font-medium text-sm",
715
+ children: item.name
716
+ }, undefined, false, undefined, this),
717
+ /* @__PURE__ */ jsxDEV2(Text, {
718
+ className: "text-muted-foreground text-xs",
727
719
  children: [
728
- /* @__PURE__ */ jsxDEV2("td", {
729
- className: "px-4 py-3",
730
- children: [
731
- /* @__PURE__ */ jsxDEV2("div", {
732
- className: "font-medium",
733
- children: query.name
734
- }, undefined, false, undefined, this),
735
- /* @__PURE__ */ jsxDEV2("div", {
736
- className: "text-muted-foreground text-sm",
737
- children: query.description
738
- }, undefined, false, undefined, this)
739
- ]
740
- }, undefined, true, undefined, this),
741
- /* @__PURE__ */ jsxDEV2("td", {
742
- className: "px-4 py-3",
743
- children: /* @__PURE__ */ jsxDEV2("span", {
744
- className: `inline-flex rounded-full px-2 py-0.5 font-medium text-xs ${QUERY_TYPE_COLORS[query.type] ?? ""}`,
745
- children: query.type
746
- }, undefined, false, undefined, this)
747
- }, undefined, false, undefined, this),
748
- /* @__PURE__ */ jsxDEV2("td", {
749
- className: "px-4 py-3 text-muted-foreground text-sm",
750
- children: [
751
- query.cacheTtlSeconds,
752
- "s"
753
- ]
754
- }, undefined, true, undefined, this),
755
- /* @__PURE__ */ jsxDEV2("td", {
756
- className: "px-4 py-3",
757
- children: query.isShared ? /* @__PURE__ */ jsxDEV2("span", {
758
- className: "text-green-600 dark:text-green-400",
759
- children: "✓"
760
- }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV2("span", {
761
- className: "text-muted-foreground",
762
- children: "—"
763
- }, undefined, false, undefined, this)
764
- }, undefined, false, undefined, this)
720
+ "Updated ",
721
+ item.updatedAt.toLocaleDateString()
765
722
  ]
766
- }, query.id, true, undefined, this)),
767
- queries.length === 0 && /* @__PURE__ */ jsxDEV2("tr", {
768
- children: /* @__PURE__ */ jsxDEV2("td", {
769
- colSpan: 4,
770
- className: "px-4 py-8 text-center text-muted-foreground",
771
- children: "No queries saved"
772
- }, undefined, false, undefined, this)
723
+ }, undefined, true, undefined, this)
724
+ ]
725
+ }, undefined, true, undefined, this),
726
+ size: 240,
727
+ minSize: 180,
728
+ canSort: true,
729
+ canPin: true,
730
+ canResize: true
731
+ },
732
+ {
733
+ id: "description",
734
+ header: "Description",
735
+ label: "Description",
736
+ accessor: (query) => query.description ?? "",
737
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Text, {
738
+ className: "line-clamp-2 text-muted-foreground text-sm",
739
+ children: String(value || "No description")
740
+ }, undefined, false, undefined, this),
741
+ size: 300,
742
+ minSize: 220,
743
+ canSort: false,
744
+ canHide: true,
745
+ canResize: true
746
+ },
747
+ {
748
+ id: "type",
749
+ header: "Type",
750
+ label: "Type",
751
+ accessorKey: "type",
752
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Badge, {
753
+ variant: QUERY_TYPE_COLORS[String(value)],
754
+ children: String(value)
755
+ }, undefined, false, undefined, this),
756
+ size: 150,
757
+ canSort: true,
758
+ canHide: true,
759
+ canResize: true
760
+ },
761
+ {
762
+ id: "cacheTtlSeconds",
763
+ header: "Cache TTL",
764
+ label: "Cache TTL",
765
+ accessorKey: "cacheTtlSeconds",
766
+ cell: ({ value }) => `${String(value)}s`,
767
+ align: "right",
768
+ size: 140,
769
+ canSort: true,
770
+ canResize: true
771
+ },
772
+ {
773
+ id: "isShared",
774
+ header: "Shared",
775
+ label: "Shared",
776
+ accessorKey: "isShared",
777
+ cell: ({ value }) => /* @__PURE__ */ jsxDEV2(Badge, {
778
+ variant: value ? "default" : "outline",
779
+ children: value ? "Shared" : "Private"
780
+ }, undefined, false, undefined, this),
781
+ size: 140,
782
+ canSort: true,
783
+ canHide: true,
784
+ canResize: true
785
+ }
786
+ ],
787
+ initialState: {
788
+ pagination: { pageIndex: 0, pageSize: 3 },
789
+ columnVisibility: { description: false },
790
+ columnPinning: { left: ["query"], right: [] }
791
+ },
792
+ renderExpandedContent: (query) => /* @__PURE__ */ jsxDEV2(VStack, {
793
+ gap: "sm",
794
+ className: "py-2",
795
+ children: [
796
+ /* @__PURE__ */ jsxDEV2(VStack, {
797
+ gap: "xs",
798
+ children: [
799
+ /* @__PURE__ */ jsxDEV2(Text, {
800
+ className: "font-medium text-sm",
801
+ children: "Description"
802
+ }, undefined, false, undefined, this),
803
+ /* @__PURE__ */ jsxDEV2(Text, {
804
+ className: "text-muted-foreground text-sm",
805
+ children: query.description ?? "No description"
806
+ }, undefined, false, undefined, this)
807
+ ]
808
+ }, undefined, true, undefined, this),
809
+ query.sql ? /* @__PURE__ */ jsxDEV2(VStack, {
810
+ gap: "xs",
811
+ children: [
812
+ /* @__PURE__ */ jsxDEV2(Text, {
813
+ className: "font-medium text-sm",
814
+ children: "SQL"
815
+ }, undefined, false, undefined, this),
816
+ /* @__PURE__ */ jsxDEV2("pre", {
817
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
818
+ children: query.sql
773
819
  }, undefined, false, undefined, this)
774
820
  ]
821
+ }, undefined, true, undefined, this) : null,
822
+ /* @__PURE__ */ jsxDEV2(VStack, {
823
+ gap: "xs",
824
+ children: [
825
+ /* @__PURE__ */ jsxDEV2(Text, {
826
+ className: "font-medium text-sm",
827
+ children: "Definition"
828
+ }, undefined, false, undefined, this),
829
+ /* @__PURE__ */ jsxDEV2("pre", {
830
+ className: "overflow-auto rounded-md bg-muted/40 p-3 text-xs",
831
+ children: formatJson(query.definition)
832
+ }, undefined, false, undefined, this)
833
+ ]
834
+ }, undefined, true, undefined, this)
835
+ ]
836
+ }, undefined, true, undefined, this),
837
+ getCanExpand: () => true
838
+ });
839
+ return /* @__PURE__ */ jsxDEV2(DataTable, {
840
+ controller,
841
+ title: "Saved Queries",
842
+ description: "Client-mode table using the shared ContractSpec controller and renderer.",
843
+ toolbar: /* @__PURE__ */ jsxDEV2(HStack, {
844
+ gap: "sm",
845
+ className: "flex-wrap",
846
+ children: [
847
+ /* @__PURE__ */ jsxDEV2(Text, {
848
+ className: "text-muted-foreground text-sm",
849
+ children: [
850
+ queries.length,
851
+ " queries"
852
+ ]
853
+ }, undefined, true, undefined, this),
854
+ /* @__PURE__ */ jsxDEV2(Text, {
855
+ className: "text-muted-foreground text-sm",
856
+ children: [
857
+ queries.filter((query) => query.isShared).length,
858
+ " shared"
859
+ ]
775
860
  }, undefined, true, undefined, this)
776
861
  ]
777
- }, undefined, true, undefined, this)
862
+ }, undefined, true, undefined, this),
863
+ emptyState: /* @__PURE__ */ jsxDEV2("div", {
864
+ className: "rounded-md border border-dashed p-8 text-center text-muted-foreground text-sm",
865
+ children: "No queries saved"
866
+ }, undefined, false, undefined, this)
778
867
  }, undefined, false, undefined, this);
779
868
  }
780
869
 
@@ -27,7 +27,7 @@ var analyticsDashboardDocBlocks = [
27
27
 
28
28
  ## UI / Presentations
29
29
 
30
- - Dashboard list, dashboard view, query builder, widget gallery.
30
+ - Dashboard list, dashboard view, query builder, widget gallery, and a shared ContractSpec table for saved queries.
31
31
  - Registered under \`analytics-dashboard\` template in Template Registry.
32
32
 
33
33
  ## Notes
@@ -35,6 +35,7 @@ var analyticsDashboardDocBlocks = [
35
35
  - Enforce org scoping for multi-tenant isolation.
36
36
  - Use Feature Flags for beta widgets; Metering to track query volume.
37
37
  - PostHog datasource can back query execution via HogQL for dashboard widgets.
38
+ - The saved-queries table demonstrates client-mode sorting, pagination, visibility, resizing, pinning, and row expansion on the shared table stack.
38
39
  `
39
40
  },
40
41
  {
@@ -27,7 +27,7 @@ var analyticsDashboardDocBlocks = [
27
27
 
28
28
  ## UI / Presentations
29
29
 
30
- - Dashboard list, dashboard view, query builder, widget gallery.
30
+ - Dashboard list, dashboard view, query builder, widget gallery, and a shared ContractSpec table for saved queries.
31
31
  - Registered under \`analytics-dashboard\` template in Template Registry.
32
32
 
33
33
  ## Notes
@@ -35,6 +35,7 @@ var analyticsDashboardDocBlocks = [
35
35
  - Enforce org scoping for multi-tenant isolation.
36
36
  - Use Feature Flags for beta widgets; Metering to track query volume.
37
37
  - PostHog datasource can back query execution via HogQL for dashboard widgets.
38
+ - The saved-queries table demonstrates client-mode sorting, pagination, visibility, resizing, pinning, and row expansion on the shared table stack.
38
39
  `
39
40
  },
40
41
  {