dslinter 0.2.3 → 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.
- package/CHANGELOG.md +29 -0
- package/bin/lib/infer-prop-types-from-ts.mjs +14 -1
- package/bin/lib/infer-prop-types-from-ts.test.mjs +32 -0
- package/dashboard-dist/assets/{DashboardLayoutAuto-h0gP_iKd.js → DashboardLayoutAuto-BWuyjHPD.js} +1 -1
- package/dashboard-dist/assets/DashboardLayoutAuto-CAO6F6-q.css +1 -0
- package/dashboard-dist/assets/{axe-DDaE9JTN.js → axe-DHHCqGjV.js} +1 -1
- package/dashboard-dist/assets/index-Bxk7tA3F.js +219 -0
- package/dashboard-dist/assets/index-D0O_5w5V.css +1 -0
- package/dashboard-dist/dslinter-report.json +23929 -0
- package/dashboard-dist/index.html +2 -2
- package/index.cjs +52 -52
- package/package.json +6 -6
- package/src/components/CatalogPane.tsx +94 -0
- package/src/components/ComponentInspectPane.tsx +125 -125
- package/src/components/ComponentPlaygroundPane.tsx +42 -27
- package/src/components/DashboardCommandPalette.tsx +9 -0
- package/src/components/GovernanceInventoryTabs.tsx +51 -0
- package/src/components/GovernancePane.tsx +18 -5
- package/src/components/PlaygroundA11yAndCode.tsx +0 -52
- package/src/components/PlaygroundControlField.tsx +2 -0
- package/src/components/ScoreGauge.test.ts +22 -0
- package/src/components/ScoreGauge.tsx +179 -0
- package/src/components/Sidebar.tsx +97 -23
- package/src/components/TokensPane.tsx +11 -13
- package/src/components/controlApiTable.test.ts +15 -0
- package/src/components/controlApiTable.ts +4 -0
- package/src/components/ui/badge.tsx +5 -5
- package/src/dashboard/ComponentCatalog.tsx +10 -1
- package/src/dashboard/ComponentPropUsageDetail.tsx +127 -42
- package/src/dashboard/ComponentUsageDetails.tsx +39 -9
- package/src/dashboard/DashboardBody.tsx +83 -12
- package/src/dashboard/ScannedTokenWall.tsx +9 -6
- package/src/dashboard/UnusedComponentsList.tsx +74 -0
- package/src/dashboard/aggregate.test.ts +381 -12
- package/src/dashboard/aggregate.ts +167 -30
- package/src/dashboard/mergeTokenCatalog.ts +5 -0
- package/src/dashboard/paths.test.ts +18 -1
- package/src/dashboard/paths.ts +8 -0
- package/src/mcp/agent-query.ts +1 -1
- package/src/mcp/css-color.test.ts +52 -0
- package/src/mcp/css-color.ts +73 -0
- package/src/mcp/rule-catalog.json +3 -3
- package/src/mcp/verify-loop.test.ts +24 -0
- package/src/mcp/verify-loop.ts +28 -6
- package/src/playground/buildPlaygroundEntriesFromReport.test.ts +3 -3
- package/src/playground/controls.ts +16 -3
- package/src/playground/enrichKitControls.ts +5 -5
- package/src/playground/inferKitJsx.test.ts +0 -11
- package/src/playground/inferPropTypesFromTs.d.mts +1 -1
- package/src/playground/inferPropTypesFromTs.mjs +19 -3
- package/src/playground/inferPropTypesFromTs.test.ts +32 -0
- package/src/playground/inferPropTypesFromTs.ts +1 -1
- package/src/playground/playgroundJoin.ts +34 -0
- package/src/playground/propCoerce.ts +2 -2
- package/src/playground/snippet.ts +1 -0
- package/src/shell/DashboardLayout.tsx +21 -4
- package/src/shell/hashRoute.test.ts +9 -0
- package/src/shell/hashRoute.ts +6 -0
- package/src/types/controls.ts +12 -0
- package/src/types/report.ts +1 -1
- package/vite/embedTailwindSources.ts +8 -6
- package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +0 -1
- package/dashboard-dist/assets/index-B9sZ6wHm.css +0 -1
- package/dashboard-dist/assets/index-DIDBt5ed.js +0 -218
|
@@ -1,44 +1,100 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import {
|
|
3
|
-
Table,
|
|
4
|
-
TableBody,
|
|
5
|
-
TableCell,
|
|
6
|
-
TableHead,
|
|
7
|
-
TableHeader,
|
|
8
|
-
TableRow,
|
|
9
|
-
} from "./ui/table";
|
|
10
2
|
import {
|
|
11
3
|
aggregateDeclaredProps,
|
|
12
4
|
aggregateDefinitions,
|
|
5
|
+
catalogChildComponentsFor,
|
|
13
6
|
componentCatalogFamilyForName,
|
|
7
|
+
type DefinitionSite,
|
|
14
8
|
} from "../dashboard/aggregate";
|
|
15
9
|
import {
|
|
16
10
|
buildUnusedPropSetForComponent,
|
|
17
11
|
ComponentPropUsageDetail,
|
|
12
|
+
propFrequenciesForComponent,
|
|
18
13
|
} from "../dashboard/ComponentPropUsageDetail";
|
|
19
14
|
import { ComponentUsageDetails } from "../dashboard/ComponentUsageDetails";
|
|
20
15
|
import { FindingsList } from "../dashboard/FindingsList";
|
|
21
16
|
import { shortPath } from "../dashboard/paths";
|
|
22
17
|
import { findingsForComponent } from "../report/findingsForComponent";
|
|
18
|
+
import {
|
|
19
|
+
findPlaygroundSpec,
|
|
20
|
+
playgroundJoinDetailMessage,
|
|
21
|
+
type PlaygroundJoinSkip,
|
|
22
|
+
} from "../playground/playgroundJoin";
|
|
23
23
|
import type { WorkspaceReport } from "../types/report";
|
|
24
|
-
import type { PlaygroundJoinSkip } from "../playground/playgroundJoin";
|
|
25
|
-
import { findPlaygroundSpec } from "../playground/playgroundJoin";
|
|
26
24
|
import { HideFromCatalogButton } from "./HideFromCatalogButton";
|
|
27
25
|
import { Section } from "./Section";
|
|
28
26
|
import { TruncatedPath } from "./TruncatedPath";
|
|
27
|
+
import {
|
|
28
|
+
Table,
|
|
29
|
+
TableBody,
|
|
30
|
+
TableCell,
|
|
31
|
+
TableHead,
|
|
32
|
+
TableHeader,
|
|
33
|
+
TableRow,
|
|
34
|
+
} from "./ui/table";
|
|
29
35
|
|
|
30
36
|
type Props = {
|
|
31
37
|
componentId: string;
|
|
32
38
|
workspaceReport: WorkspaceReport | null;
|
|
33
39
|
reportReady: boolean;
|
|
34
40
|
hasPlaygroundSpec: boolean;
|
|
35
|
-
/** When the report row exists but Vite could not load the module/export. */
|
|
36
41
|
playgroundJoinSkip?: PlaygroundJoinSkip;
|
|
37
|
-
onBackToGovernance: () => void;
|
|
38
42
|
onOpenComponent: (componentId: string) => void;
|
|
39
43
|
onHideFromCatalog?: (componentId: string) => void;
|
|
40
44
|
};
|
|
41
45
|
|
|
46
|
+
const PREVIEW_NOTE = {
|
|
47
|
+
missing: "No playable component definition was found",
|
|
48
|
+
unloadable:
|
|
49
|
+
"A preview was expected for this component but the module could not be loaded in the dashboard bundle (check the file path, export name, or that npx dslinter was run from the project root).",
|
|
50
|
+
} as const;
|
|
51
|
+
|
|
52
|
+
function reportPlaceholder(message: string) {
|
|
53
|
+
return <p className="text-sm text-muted-foreground">{message}</p>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function DefinitionsTable({
|
|
57
|
+
definitions,
|
|
58
|
+
root,
|
|
59
|
+
}: {
|
|
60
|
+
definitions: DefinitionSite[];
|
|
61
|
+
root: string | undefined;
|
|
62
|
+
}) {
|
|
63
|
+
if (definitions.length === 0) {
|
|
64
|
+
return (
|
|
65
|
+
<p className="text-sm text-muted-foreground">
|
|
66
|
+
No definition sites recorded — this name may appear only from JSX usage.
|
|
67
|
+
</p>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Table>
|
|
73
|
+
<TableHeader>
|
|
74
|
+
<TableRow>
|
|
75
|
+
<TableHead>File</TableHead>
|
|
76
|
+
<TableHead className="w-20">Line</TableHead>
|
|
77
|
+
<TableHead>Kind</TableHead>
|
|
78
|
+
</TableRow>
|
|
79
|
+
</TableHeader>
|
|
80
|
+
<TableBody>
|
|
81
|
+
{definitions.map((site) => (
|
|
82
|
+
<TableRow key={`${site.path}:${site.line}:${site.kind}`}>
|
|
83
|
+
<TableCell className="min-w-0 font-mono text-xs">
|
|
84
|
+
<TruncatedPath
|
|
85
|
+
path={root ? shortPath(root, site.path) : site.path}
|
|
86
|
+
className="text-xs"
|
|
87
|
+
/>
|
|
88
|
+
</TableCell>
|
|
89
|
+
<TableCell>{site.line}</TableCell>
|
|
90
|
+
<TableCell className="text-muted-foreground">{site.kind}</TableCell>
|
|
91
|
+
</TableRow>
|
|
92
|
+
))}
|
|
93
|
+
</TableBody>
|
|
94
|
+
</Table>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
42
98
|
export function ComponentInspectPane({
|
|
43
99
|
componentId,
|
|
44
100
|
workspaceReport,
|
|
@@ -48,69 +104,50 @@ export function ComponentInspectPane({
|
|
|
48
104
|
onOpenComponent,
|
|
49
105
|
onHideFromCatalog,
|
|
50
106
|
}: Props) {
|
|
107
|
+
const report = reportReady ? workspaceReport : null;
|
|
51
108
|
const playgroundSpec = findPlaygroundSpec(workspaceReport, componentId);
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}, [workspaceReport, componentId]);
|
|
56
|
-
|
|
57
|
-
const declared = useMemo(() => {
|
|
58
|
-
if (!workspaceReport) return [];
|
|
59
|
-
return aggregateDeclaredProps(workspaceReport).get(componentId) ?? [];
|
|
60
|
-
}, [workspaceReport, componentId]);
|
|
61
|
-
|
|
62
|
-
const unusedProps = useMemo(() => {
|
|
63
|
-
if (!workspaceReport) return new Set<string>();
|
|
64
|
-
return buildUnusedPropSetForComponent(
|
|
65
|
-
workspaceReport,
|
|
66
|
-
componentId,
|
|
67
|
-
declared,
|
|
68
|
-
);
|
|
69
|
-
}, [workspaceReport, componentId, declared]);
|
|
70
|
-
|
|
71
|
-
const findings = useMemo(
|
|
72
|
-
() => findingsForComponent(workspaceReport, componentId),
|
|
73
|
-
[workspaceReport, componentId],
|
|
74
|
-
);
|
|
75
|
-
const family = useMemo(
|
|
76
|
-
() => componentCatalogFamilyForName(workspaceReport, componentId),
|
|
77
|
-
[workspaceReport, componentId],
|
|
109
|
+
const joinDetail = playgroundJoinDetailMessage(
|
|
110
|
+
playgroundJoinSkip,
|
|
111
|
+
playgroundSpec,
|
|
78
112
|
);
|
|
79
|
-
const childComponents = family?.parent === componentId ? family.children : [];
|
|
80
|
-
|
|
81
|
-
const previewNote = hasPlaygroundSpec
|
|
82
|
-
? "A preview was expected for this component but the module could not be loaded in the dashboard bundle (check the file path, export name, or that npx dslinter was run from the project root)."
|
|
83
|
-
: "No playable component definition was found";
|
|
84
113
|
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
]
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.join("");
|
|
114
|
+
const {
|
|
115
|
+
definitions,
|
|
116
|
+
declared,
|
|
117
|
+
unusedProps,
|
|
118
|
+
propFrequencies,
|
|
119
|
+
findings,
|
|
120
|
+
childComponents,
|
|
121
|
+
} = useMemo(() => {
|
|
122
|
+
if (!workspaceReport) {
|
|
123
|
+
return {
|
|
124
|
+
definitions: [] as DefinitionSite[],
|
|
125
|
+
declared: [] as string[],
|
|
126
|
+
unusedProps: new Set<string>(),
|
|
127
|
+
propFrequencies: {},
|
|
128
|
+
findings: [],
|
|
129
|
+
childComponents: [] as string[],
|
|
130
|
+
};
|
|
105
131
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
132
|
+
|
|
133
|
+
const declared =
|
|
134
|
+
aggregateDeclaredProps(workspaceReport).get(componentId) ?? [];
|
|
135
|
+
const family = componentCatalogFamilyForName(workspaceReport, componentId);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
definitions:
|
|
139
|
+
aggregateDefinitions(workspaceReport).get(componentId) ?? [],
|
|
140
|
+
declared,
|
|
141
|
+
unusedProps: buildUnusedPropSetForComponent(
|
|
142
|
+
workspaceReport,
|
|
143
|
+
componentId,
|
|
144
|
+
declared,
|
|
145
|
+
),
|
|
146
|
+
propFrequencies: propFrequenciesForComponent(workspaceReport, componentId),
|
|
147
|
+
findings: findingsForComponent(workspaceReport, componentId),
|
|
148
|
+
childComponents: catalogChildComponentsFor(family, componentId),
|
|
149
|
+
};
|
|
150
|
+
}, [workspaceReport, componentId]);
|
|
114
151
|
|
|
115
152
|
return (
|
|
116
153
|
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-background">
|
|
@@ -125,7 +162,7 @@ export function ComponentInspectPane({
|
|
|
125
162
|
{componentId}
|
|
126
163
|
</h1>
|
|
127
164
|
<p className="mt-2 max-w-2xl text-sm text-muted-foreground">
|
|
128
|
-
{
|
|
165
|
+
{hasPlaygroundSpec ? PREVIEW_NOTE.unloadable : PREVIEW_NOTE.missing}
|
|
129
166
|
</p>
|
|
130
167
|
{joinDetail ? (
|
|
131
168
|
<p className="mt-2 max-w-2xl font-mono text-xs text-muted-foreground">
|
|
@@ -133,14 +170,12 @@ export function ComponentInspectPane({
|
|
|
133
170
|
</p>
|
|
134
171
|
) : null}
|
|
135
172
|
</div>
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
) : null}
|
|
143
|
-
</div>
|
|
173
|
+
{onHideFromCatalog ? (
|
|
174
|
+
<HideFromCatalogButton
|
|
175
|
+
componentName={componentId}
|
|
176
|
+
onHidden={onHideFromCatalog}
|
|
177
|
+
/>
|
|
178
|
+
) : null}
|
|
144
179
|
</div>
|
|
145
180
|
</header>
|
|
146
181
|
|
|
@@ -150,42 +185,10 @@ export function ComponentInspectPane({
|
|
|
150
185
|
title="Definitions"
|
|
151
186
|
description="Source files where this component is defined."
|
|
152
187
|
>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<TableHead>File</TableHead>
|
|
158
|
-
<TableHead className="w-20">Line</TableHead>
|
|
159
|
-
<TableHead>Kind</TableHead>
|
|
160
|
-
</TableRow>
|
|
161
|
-
</TableHeader>
|
|
162
|
-
<TableBody>
|
|
163
|
-
{definitions.map((site) => (
|
|
164
|
-
<TableRow key={`${site.path}:${site.line}:${site.kind}`}>
|
|
165
|
-
<TableCell className="min-w-0 font-mono text-xs">
|
|
166
|
-
<TruncatedPath
|
|
167
|
-
path={
|
|
168
|
-
workspaceReport
|
|
169
|
-
? shortPath(workspaceReport.root, site.path)
|
|
170
|
-
: site.path
|
|
171
|
-
}
|
|
172
|
-
className="text-xs"
|
|
173
|
-
/>
|
|
174
|
-
</TableCell>
|
|
175
|
-
<TableCell>{site.line}</TableCell>
|
|
176
|
-
<TableCell className="text-muted-foreground">
|
|
177
|
-
{site.kind}
|
|
178
|
-
</TableCell>
|
|
179
|
-
</TableRow>
|
|
180
|
-
))}
|
|
181
|
-
</TableBody>
|
|
182
|
-
</Table>
|
|
183
|
-
) : (
|
|
184
|
-
<p className="text-sm text-muted-foreground">
|
|
185
|
-
No definition sites recorded — this name may appear only from
|
|
186
|
-
JSX usage.
|
|
187
|
-
</p>
|
|
188
|
-
)}
|
|
188
|
+
<DefinitionsTable
|
|
189
|
+
definitions={definitions}
|
|
190
|
+
root={workspaceReport?.root}
|
|
191
|
+
/>
|
|
189
192
|
</Section>
|
|
190
193
|
|
|
191
194
|
{childComponents.length > 0 ? (
|
|
@@ -214,16 +217,15 @@ export function ComponentInspectPane({
|
|
|
214
217
|
title="Props"
|
|
215
218
|
description="Declared props and workspace usage from the latest scan."
|
|
216
219
|
>
|
|
217
|
-
{
|
|
220
|
+
{report ? (
|
|
218
221
|
<ComponentPropUsageDetail
|
|
219
222
|
component={componentId}
|
|
220
223
|
declared={declared}
|
|
221
224
|
unusedProps={unusedProps}
|
|
225
|
+
propFrequencies={propFrequencies}
|
|
222
226
|
/>
|
|
223
227
|
) : (
|
|
224
|
-
|
|
225
|
-
Load the DSLinter report to see prop usage.
|
|
226
|
-
</p>
|
|
228
|
+
reportPlaceholder("Load the DSLinter report to see prop usage.")
|
|
227
229
|
)}
|
|
228
230
|
</Section>
|
|
229
231
|
|
|
@@ -243,12 +245,10 @@ export function ComponentInspectPane({
|
|
|
243
245
|
title="Findings"
|
|
244
246
|
description="DSLinter findings on files where this component is defined."
|
|
245
247
|
>
|
|
246
|
-
{
|
|
247
|
-
<FindingsList findings={findings} root={
|
|
248
|
+
{report ? (
|
|
249
|
+
<FindingsList findings={findings} root={report.root} />
|
|
248
250
|
) : (
|
|
249
|
-
|
|
250
|
-
Load the DSLinter report to see findings.
|
|
251
|
-
</p>
|
|
251
|
+
reportPlaceholder("Load the DSLinter report to see findings.")
|
|
252
252
|
)}
|
|
253
253
|
</Section>
|
|
254
254
|
</div>
|
|
@@ -16,6 +16,7 @@ import { tokenStyleFindingsForModule } from "../report/tokenStyleFindingsForModu
|
|
|
16
16
|
import type { WorkspaceReport } from "../types/report";
|
|
17
17
|
import {
|
|
18
18
|
aggregateDeclaredProps,
|
|
19
|
+
catalogChildComponentsFor,
|
|
19
20
|
componentCatalogFamilyForName,
|
|
20
21
|
usageMap,
|
|
21
22
|
} from "../dashboard/aggregate";
|
|
@@ -41,10 +42,9 @@ import {
|
|
|
41
42
|
} from "../playground/scanVariantA11y";
|
|
42
43
|
import { HideFromCatalogButton } from "./HideFromCatalogButton";
|
|
43
44
|
import { OpenInEditorButton } from "./OpenInEditorButton";
|
|
45
|
+
import { ScoreGauge } from "./ScoreGauge";
|
|
44
46
|
import { Section } from "./Section";
|
|
45
|
-
import {
|
|
46
|
-
resolveModuleAbsolutePath,
|
|
47
|
-
} from "../dashboard/editorLink";
|
|
47
|
+
import { resolveModuleAbsolutePath } from "../dashboard/editorLink";
|
|
48
48
|
|
|
49
49
|
type Props = {
|
|
50
50
|
entry: PlaygroundEntry;
|
|
@@ -139,7 +139,7 @@ export function ComponentPlaygroundPane({
|
|
|
139
139
|
entry,
|
|
140
140
|
workspaceReport,
|
|
141
141
|
reportReady,
|
|
142
|
-
onOpenComponent,
|
|
142
|
+
onOpenComponent: _onOpenComponent,
|
|
143
143
|
onHideFromCatalog,
|
|
144
144
|
}: Props) {
|
|
145
145
|
const { renderPreview } = entry;
|
|
@@ -377,7 +377,7 @@ export function ComponentPlaygroundPane({
|
|
|
377
377
|
() => componentCatalogFamilyForName(report, entry.id),
|
|
378
378
|
[report, entry.id],
|
|
379
379
|
);
|
|
380
|
-
const childComponents = family
|
|
380
|
+
const childComponents = catalogChildComponentsFor(family, entry.id);
|
|
381
381
|
const resetControls = () =>
|
|
382
382
|
setValues(defaultArgsFromControls(entry.controls));
|
|
383
383
|
|
|
@@ -401,27 +401,27 @@ export function ComponentPlaygroundPane({
|
|
|
401
401
|
<div className="min-h-0 flex-1 overflow-auto">
|
|
402
402
|
<header
|
|
403
403
|
id="source"
|
|
404
|
-
className="scroll-mt-20 border-b border-border bg-card p-6"
|
|
404
|
+
className="scroll-mt-20 border-b border-border bg-card p-6 flex flex-wrap items-center justify-between gap-4"
|
|
405
405
|
>
|
|
406
|
-
<div className="
|
|
407
|
-
<
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
<div className="flex
|
|
406
|
+
<div className="min-w-0">
|
|
407
|
+
<p className="text-sm font-medium text-muted-foreground">
|
|
408
|
+
Components
|
|
409
|
+
{entry.meta.group ? (
|
|
410
|
+
<>
|
|
411
|
+
{" "}
|
|
412
|
+
<span className="text-muted-foreground/40">/</span>{" "}
|
|
413
|
+
<span className="capitalize text-foreground/80">
|
|
414
|
+
{entry.meta.group}
|
|
415
|
+
</span>
|
|
416
|
+
</>
|
|
417
|
+
) : null}
|
|
418
|
+
</p>
|
|
419
|
+
<h1 className="text-3xl font-semibold tracking-tight text-foreground">
|
|
420
|
+
{entry.meta.title}
|
|
421
|
+
</h1>
|
|
422
|
+
</div>
|
|
423
|
+
<div className="flex shrink-0 flex-wrap items-center gap-4">
|
|
424
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
425
425
|
{sourceAbsolutePath ? (
|
|
426
426
|
<OpenInEditorButton filePath={sourceAbsolutePath} />
|
|
427
427
|
) : null}
|
|
@@ -432,6 +432,21 @@ export function ComponentPlaygroundPane({
|
|
|
432
432
|
/>
|
|
433
433
|
) : null}
|
|
434
434
|
</div>
|
|
435
|
+
<div className="flex items-start gap-5">
|
|
436
|
+
<ScoreGauge
|
|
437
|
+
label="Code score"
|
|
438
|
+
value={reportReady ? codeScore.score : null}
|
|
439
|
+
href="#code-score"
|
|
440
|
+
/>
|
|
441
|
+
<ScoreGauge
|
|
442
|
+
label="Accessibility"
|
|
443
|
+
value={
|
|
444
|
+
reportReady || variantScanComplete ? combinedA11y.score : null
|
|
445
|
+
}
|
|
446
|
+
href="#accessibility"
|
|
447
|
+
pending={variantScanPending}
|
|
448
|
+
/>
|
|
449
|
+
</div>
|
|
435
450
|
</div>
|
|
436
451
|
</header>
|
|
437
452
|
|
|
@@ -443,7 +458,7 @@ export function ComponentPlaygroundPane({
|
|
|
443
458
|
<div className="flex justify-center">
|
|
444
459
|
<div
|
|
445
460
|
ref={previewFrameRef}
|
|
446
|
-
className="relative min-w-0 shrink-0 select-none rounded-lg border border-border bg-
|
|
461
|
+
className="relative min-w-0 shrink-0 select-none rounded-lg border border-border bg-background shadow-xs will-change-[width]"
|
|
447
462
|
style={{ width: previewWidthPx }}
|
|
448
463
|
>
|
|
449
464
|
<PreviewResizeHandle
|
|
@@ -456,7 +471,7 @@ export function ComponentPlaygroundPane({
|
|
|
456
471
|
/>
|
|
457
472
|
<PlaygroundAppThemeWrapper
|
|
458
473
|
workspaceReport={report}
|
|
459
|
-
className="min-w-0 p-8
|
|
474
|
+
className="min-w-0 p-8"
|
|
460
475
|
>
|
|
461
476
|
<PlaygroundPreviewErrorBoundary
|
|
462
477
|
componentName={entry.meta.title}
|
|
@@ -59,6 +59,15 @@ export function DashboardCommandPalette({ catalogEntries, onNavigate, open, onOp
|
|
|
59
59
|
>
|
|
60
60
|
Governance
|
|
61
61
|
</CommandItem>
|
|
62
|
+
<CommandItem
|
|
63
|
+
value="catalog all components inventory"
|
|
64
|
+
onSelect={() => {
|
|
65
|
+
onNavigate({ view: "catalog" });
|
|
66
|
+
close();
|
|
67
|
+
}}
|
|
68
|
+
>
|
|
69
|
+
All components
|
|
70
|
+
</CommandItem>
|
|
62
71
|
</CommandGroup>
|
|
63
72
|
{catalogEntries.length > 0 ? (
|
|
64
73
|
<CommandGroup heading="Components">
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group";
|
|
2
|
+
import type { GovernanceInventoryTab } from "../dashboard/aggregate";
|
|
3
|
+
|
|
4
|
+
const tabs: { id: GovernanceInventoryTab; label: string }[] = [
|
|
5
|
+
{ id: "all", label: "All issues" },
|
|
6
|
+
{ id: "a11y", label: "Accessibility" },
|
|
7
|
+
{ id: "code", label: "Code quality" },
|
|
8
|
+
{ id: "token", label: "Tokens" },
|
|
9
|
+
{ id: "unused", label: "Unused" },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
function isGovernanceInventoryTab(value: string): value is GovernanceInventoryTab {
|
|
13
|
+
return tabs.some((tab) => tab.id === value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function GovernanceInventoryTabs({
|
|
17
|
+
value,
|
|
18
|
+
onChange,
|
|
19
|
+
counts,
|
|
20
|
+
}: {
|
|
21
|
+
value: GovernanceInventoryTab;
|
|
22
|
+
onChange: (value: GovernanceInventoryTab) => void;
|
|
23
|
+
counts: Record<GovernanceInventoryTab, number>;
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<ToggleGroup
|
|
27
|
+
type="single"
|
|
28
|
+
value={value}
|
|
29
|
+
onValueChange={(next) => {
|
|
30
|
+
if (isGovernanceInventoryTab(next)) onChange(next);
|
|
31
|
+
}}
|
|
32
|
+
variant="outline"
|
|
33
|
+
size="sm"
|
|
34
|
+
aria-label="Filter governance inventory"
|
|
35
|
+
className="contents"
|
|
36
|
+
>
|
|
37
|
+
{tabs.map((tab) => (
|
|
38
|
+
<ToggleGroupItem
|
|
39
|
+
key={tab.id}
|
|
40
|
+
value={tab.id}
|
|
41
|
+
className="rounded-full px-2.5 text-xs font-medium"
|
|
42
|
+
>
|
|
43
|
+
{tab.label}
|
|
44
|
+
<span className="ml-1 tabular-nums text-muted-foreground">
|
|
45
|
+
{counts[tab.id]}
|
|
46
|
+
</span>
|
|
47
|
+
</ToggleGroupItem>
|
|
48
|
+
))}
|
|
49
|
+
</ToggleGroup>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
componentCatalogNamesFromReport,
|
|
4
|
+
governanceTabCounts,
|
|
5
|
+
unusedComponentsFromReport,
|
|
6
|
+
} from "../dashboard/aggregate";
|
|
3
7
|
import { DashboardBody } from "../dashboard/DashboardBody";
|
|
4
8
|
import type { DslinterReportState } from "../dashboard/useWorkspaceReport";
|
|
5
9
|
|
|
@@ -10,6 +14,7 @@ type Props = {
|
|
|
10
14
|
dslinterReportHint?: string;
|
|
11
15
|
dslinterReport: DslinterReportState;
|
|
12
16
|
onOpenComponent?: (name: string) => void;
|
|
17
|
+
onOpenCatalog?: () => void;
|
|
13
18
|
};
|
|
14
19
|
|
|
15
20
|
export function GovernancePane({
|
|
@@ -18,11 +23,14 @@ export function GovernancePane({
|
|
|
18
23
|
dslinterReportHint = "npm run dslinter:report",
|
|
19
24
|
dslinterReport,
|
|
20
25
|
onOpenComponent,
|
|
26
|
+
onOpenCatalog,
|
|
21
27
|
}: Props) {
|
|
22
28
|
const { report, error, loading } = dslinterReport;
|
|
23
29
|
const componentCatalogCount = report
|
|
24
30
|
? componentCatalogNamesFromReport(report).length
|
|
25
31
|
: 0;
|
|
32
|
+
const unusedCount = report ? unusedComponentsFromReport(report).length : 0;
|
|
33
|
+
const findingCount = report ? governanceTabCounts(report).all : 0;
|
|
26
34
|
|
|
27
35
|
if (error) {
|
|
28
36
|
return (
|
|
@@ -71,16 +79,21 @@ export function GovernancePane({
|
|
|
71
79
|
Governance
|
|
72
80
|
<span className="font-normal text-muted-foreground">
|
|
73
81
|
{" "}
|
|
74
|
-
· {
|
|
82
|
+
· {findingCount} {findingCount === 1 ? "issue" : "issues"} ·{" "}
|
|
83
|
+
{unusedCount} unused · {componentCatalogCount} total
|
|
75
84
|
</span>
|
|
76
85
|
</h1>
|
|
77
86
|
<p className="text-sm text-muted-foreground">
|
|
78
|
-
|
|
79
|
-
DSLinter snapshot
|
|
87
|
+
Governance scores, workspace findings, and components with no usage
|
|
88
|
+
from the latest DSLinter snapshot
|
|
80
89
|
</p>
|
|
81
90
|
</header>
|
|
82
91
|
<div className="min-w-0 w-full px-6 py-8">
|
|
83
|
-
<DashboardBody
|
|
92
|
+
<DashboardBody
|
|
93
|
+
report={report}
|
|
94
|
+
onOpenComponent={onOpenComponent}
|
|
95
|
+
onOpenCatalog={onOpenCatalog}
|
|
96
|
+
/>
|
|
84
97
|
</div>
|
|
85
98
|
</div>
|
|
86
99
|
);
|
|
@@ -230,26 +230,11 @@ type ApiProps = {
|
|
|
230
230
|
governanceReportLoaded?: boolean;
|
|
231
231
|
};
|
|
232
232
|
|
|
233
|
-
function formatRepoLiteralChips(
|
|
234
|
-
byVal: Record<string, number> | undefined,
|
|
235
|
-
max = 6,
|
|
236
|
-
): string {
|
|
237
|
-
if (!byVal || Object.keys(byVal).length === 0) return "—";
|
|
238
|
-
const entries = Object.entries(byVal).sort((x, y) => y[1] - x[1]);
|
|
239
|
-
const shown = entries.slice(0, max);
|
|
240
|
-
const tail = Math.max(0, entries.length - max);
|
|
241
|
-
return (
|
|
242
|
-
shown.map(([val, n]) => `${JSON.stringify(val)} ×${n}`).join(" · ") +
|
|
243
|
-
(tail > 0 ? ` · +${tail}` : "")
|
|
244
|
-
);
|
|
245
|
-
}
|
|
246
|
-
|
|
247
233
|
export function PlaygroundApiReference({
|
|
248
234
|
controls,
|
|
249
235
|
values,
|
|
250
236
|
onChange,
|
|
251
237
|
onReset,
|
|
252
|
-
reportUsage,
|
|
253
238
|
declaredPropsFromScan: _declaredPropsFromScan = [],
|
|
254
239
|
governanceReportLoaded: _governanceReportLoaded = false,
|
|
255
240
|
}: ApiProps) {
|
|
@@ -263,18 +248,6 @@ export function PlaygroundApiReference({
|
|
|
263
248
|
);
|
|
264
249
|
|
|
265
250
|
const rows = controlsToApiRows(controls);
|
|
266
|
-
const showRepo = reportUsage != null;
|
|
267
|
-
const freqs = reportUsage?.prop_frequencies ?? {};
|
|
268
|
-
const valueFreqs = reportUsage?.prop_value_frequencies ?? {};
|
|
269
|
-
const controlKeys = new Set(rows.map((r) => r.prop));
|
|
270
|
-
const extraRepoProps = showRepo
|
|
271
|
-
? Object.keys(freqs)
|
|
272
|
-
.filter((k) => !controlKeys.has(k))
|
|
273
|
-
.sort((a, b) => a.localeCompare(b))
|
|
274
|
-
: [];
|
|
275
|
-
const repoUsageProps = showRepo
|
|
276
|
-
? [...rows.map((r) => r.prop), ...extraRepoProps]
|
|
277
|
-
: [];
|
|
278
251
|
return (
|
|
279
252
|
<Section
|
|
280
253
|
id="api-reference"
|
|
@@ -357,31 +330,6 @@ export function PlaygroundApiReference({
|
|
|
357
330
|
})}
|
|
358
331
|
</TableBody>
|
|
359
332
|
</Table>
|
|
360
|
-
|
|
361
|
-
{showRepo ? (
|
|
362
|
-
<Section id="repo-usage" title="Repo usage" className="mt-4">
|
|
363
|
-
<Table>
|
|
364
|
-
<TableHeader>
|
|
365
|
-
<TableRow>
|
|
366
|
-
<TableHead>Prop</TableHead>
|
|
367
|
-
<TableHead>Count</TableHead>
|
|
368
|
-
<TableHead>Values</TableHead>
|
|
369
|
-
</TableRow>
|
|
370
|
-
</TableHeader>
|
|
371
|
-
<TableBody>
|
|
372
|
-
{repoUsageProps.map((prop) => (
|
|
373
|
-
<TableRow key={prop}>
|
|
374
|
-
<TableCell className="font-medium">{prop}</TableCell>
|
|
375
|
-
<TableCell>{freqs[prop] ?? 0}</TableCell>
|
|
376
|
-
<TableCell>
|
|
377
|
-
{formatRepoLiteralChips(valueFreqs[prop])}
|
|
378
|
-
</TableCell>
|
|
379
|
-
</TableRow>
|
|
380
|
-
))}
|
|
381
|
-
</TableBody>
|
|
382
|
-
</Table>
|
|
383
|
-
</Section>
|
|
384
|
-
) : null}
|
|
385
333
|
</Section>
|
|
386
334
|
);
|
|
387
335
|
}
|
|
@@ -58,6 +58,7 @@ export function PlaygroundControlField({
|
|
|
58
58
|
);
|
|
59
59
|
}
|
|
60
60
|
case "string":
|
|
61
|
+
case "node":
|
|
61
62
|
return (
|
|
62
63
|
<div className="flex min-w-0 flex-col gap-1.5">
|
|
63
64
|
<Label htmlFor={id} className={labelClass}>
|
|
@@ -153,6 +154,7 @@ export function PlaygroundControlField({
|
|
|
153
154
|
);
|
|
154
155
|
}
|
|
155
156
|
case "string":
|
|
157
|
+
case "node":
|
|
156
158
|
return (
|
|
157
159
|
<Input
|
|
158
160
|
id={id}
|