dslinter 0.1.13 → 0.2.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.
- package/CHANGELOG.md +72 -0
- package/README.md +50 -29
- package/bin/dslinter.mjs +26 -5
- package/bin/lib/config-hide-component.mjs +44 -0
- package/bin/lib/config-hide-component.test.mjs +33 -0
- package/bin/lib/constants.mjs +20 -0
- package/bin/lib/dev-banner.mjs +16 -51
- package/bin/lib/dev-banner.test.mjs +20 -18
- package/bin/lib/enrich-playgrounds-from-ts.mjs +201 -0
- package/bin/lib/enrich-playgrounds-from-ts.test.mjs +74 -0
- package/bin/lib/enrich-report-cli.mjs +14 -0
- package/bin/lib/env.mjs +20 -0
- package/bin/lib/infer-prop-types-from-ts.mjs +381 -0
- package/bin/lib/infer-prop-types-from-ts.test.mjs +174 -0
- package/bin/lib/parse-args.mjs +13 -1
- package/bin/lib/parse-args.test.mjs +7 -1
- package/bin/lib/paths.mjs +8 -0
- package/bin/lib/project-root.mjs +72 -10
- package/bin/lib/project-root.test.mjs +32 -1
- package/bin/lib/prompt.mjs +31 -0
- package/bin/lib/resolve-project.mjs +78 -0
- package/bin/lib/resolve-project.test.mjs +74 -0
- package/bin/lib/run-scanner.mjs +40 -6
- package/bin/lib/scaffold-config.mjs +128 -9
- package/bin/lib/scaffold-config.test.mjs +24 -2
- package/bin/lib/scan-host.mjs +44 -0
- package/bin/lib/scan-host.test.mjs +41 -0
- package/bin/lib/setup-readiness.mjs +153 -0
- package/bin/lib/setup-readiness.test.mjs +32 -0
- package/bin/modes/build.mjs +31 -6
- package/bin/modes/dev.mjs +55 -21
- package/bin/modes/init.mjs +3 -22
- package/bin/modes/init.test.mjs +1 -1
- package/bin/modes/mcp.mjs +49 -0
- package/bin/modes/report.mjs +29 -4
- package/bin/modes/watch.mjs +85 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bja3BuZZ.css +1 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-h0gP_iKd.js +1 -0
- package/dashboard-dist/assets/axe-DDaE9JTN.js +20 -0
- package/dashboard-dist/assets/index-B9sZ6wHm.css +1 -0
- package/dashboard-dist/assets/index-DIDBt5ed.js +218 -0
- package/dashboard-dist/index.html +2 -2
- package/index.cjs +53 -52
- package/index.d.ts +3 -0
- package/package.json +18 -12
- package/shared/env.ts +15 -0
- package/shared/paths.ts +8 -0
- package/shared/reportPath.test.ts +19 -0
- package/shared/reportPath.ts +12 -0
- package/shared/servePort.ts +16 -0
- package/src/components/ComponentInspectPane.tsx +67 -19
- package/src/components/ComponentPlaygroundPane.tsx +262 -113
- package/src/components/DashboardCommandPalette.tsx +6 -11
- package/src/components/GovernancePane.tsx +2 -2
- package/src/components/HideFromCatalogButton.tsx +44 -0
- package/src/components/OpenInEditorButton.tsx +36 -0
- package/src/components/PlaygroundA11yAndCode.tsx +53 -53
- package/src/components/PlaygroundAppThemeWrapper.tsx +82 -0
- package/src/components/PlaygroundControls.tsx +5 -11
- package/src/components/PlaygroundPreviewErrorBoundary.tsx +54 -0
- package/src/components/PlaygroundUsageCode.tsx +6 -4
- package/src/components/PlaygroundVariantMatrix.tsx +101 -34
- package/src/components/Section.tsx +5 -2
- package/src/components/Sidebar.tsx +131 -46
- package/src/components/TruncatedPath.tsx +44 -0
- package/src/components/controlApiTable.test.ts +29 -0
- package/src/components/controlApiTable.ts +3 -0
- package/src/components/playgroundUsageHighlight.ts +14 -3
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/table.tsx +2 -2
- package/src/dashboard/ComponentCatalog.tsx +16 -23
- package/src/dashboard/ComponentUsageDetails.tsx +6 -15
- package/src/dashboard/DashboardBody.tsx +0 -35
- package/src/dashboard/FindingsList.tsx +65 -55
- package/src/dashboard/ScannedTokenWall.tsx +3 -3
- package/src/dashboard/aggregate.test.ts +74 -0
- package/src/dashboard/aggregate.ts +145 -21
- package/src/dashboard/catalogVisibility.test.ts +93 -0
- package/src/dashboard/catalogVisibility.ts +108 -0
- package/src/dashboard/editorLink.test.ts +57 -0
- package/src/dashboard/editorLink.ts +71 -0
- package/src/dashboard/paths.test.ts +49 -0
- package/src/dashboard/paths.ts +51 -3
- package/src/dashboard/updateDslintConfig.ts +22 -0
- package/src/dashboard/useWorkspaceReport.ts +21 -17
- package/src/index.ts +26 -0
- package/src/mcp/agent-context.ts +148 -0
- package/src/mcp/agent-query.test.ts +89 -0
- package/src/mcp/agent-query.ts +373 -0
- package/src/mcp/config.ts +53 -0
- package/src/mcp/index.ts +18 -0
- package/src/mcp/normalize-paths.ts +65 -0
- package/src/mcp/report-cache.ts +212 -0
- package/src/mcp/rule-catalog.json +156 -0
- package/src/mcp/rule-catalog.ts +33 -0
- package/src/mcp/schemas.ts +54 -0
- package/src/mcp/server.test.ts +44 -0
- package/src/mcp/server.ts +343 -0
- package/src/mcp/start.ts +29 -0
- package/src/mcp/verify-loop.test.ts +49 -0
- package/src/mcp/verify-loop.ts +149 -0
- package/src/playground/appPreviewTheme.test.ts +148 -0
- package/src/playground/appPreviewTheme.ts +137 -0
- package/src/playground/buildCompoundPlaygroundEntries.test.ts +348 -0
- package/src/playground/buildCompoundPlaygroundEntries.ts +625 -0
- package/src/playground/buildPlaygroundEntriesFromReport.test.ts +420 -6
- package/src/playground/buildPlaygroundEntriesFromReport.ts +206 -285
- package/src/playground/catalogIdFromPlaygroundExport.test.ts +15 -0
- package/src/playground/catalogIdFromPlaygroundExport.ts +8 -0
- package/src/playground/collectDefinedPlaygrounds.test.ts +59 -0
- package/src/playground/collectDefinedPlaygrounds.ts +68 -0
- package/src/playground/controls.ts +177 -0
- package/src/playground/createPlaygroundRegistry.ts +1 -1
- package/src/playground/definePlayground.tsx +88 -16
- package/src/playground/definePlaygroundFromKit.ts +17 -0
- package/src/playground/embedGlobKey.ts +8 -0
- package/src/playground/enrichKitControls.test.ts +25 -0
- package/src/playground/enrichKitControls.ts +197 -0
- package/src/playground/expandPlaygroundControls.test.ts +50 -0
- package/src/playground/expandPlaygroundControls.ts +97 -0
- package/src/playground/inferKitJsx.test.ts +77 -0
- package/src/playground/inferKitJsx.ts +165 -0
- package/src/playground/inferKitParams.test.ts +41 -0
- package/src/playground/inferKitParams.ts +113 -0
- package/src/playground/inferPropTypesFromTs.d.mts +47 -0
- package/src/playground/inferPropTypesFromTs.mjs +343 -0
- package/src/playground/inferPropTypesFromTs.test.ts +227 -0
- package/src/playground/inferPropTypesFromTs.ts +17 -0
- package/src/playground/mergePlaygroundEntries.test.ts +32 -0
- package/src/playground/mergePlaygroundEntries.ts +28 -0
- package/src/playground/playgroundJoin.test.ts +79 -19
- package/src/playground/playgroundJoin.ts +47 -22
- package/src/playground/playgroundModuleExport.test.ts +42 -0
- package/src/playground/playgroundModuleExport.ts +22 -0
- package/src/playground/playgroundSpecsKey.ts +8 -0
- package/src/playground/propCoerce.ts +91 -0
- package/src/playground/scanVariantA11y.test.ts +46 -0
- package/src/playground/scanVariantA11y.ts +107 -0
- package/src/playground/snippet.ts +83 -0
- package/src/playground/usePlaygroundFromReport.test.ts +18 -8
- package/src/playground/usePlaygroundFromReport.ts +3 -1
- package/src/report/a11yForModule.ts +2 -7
- package/src/report/a11yScoring.test.ts +24 -0
- package/src/report/a11yScoring.ts +17 -0
- package/src/report/index.ts +6 -0
- package/src/shell/DashboardLayout.tsx +71 -45
- package/src/shell/DashboardLayoutAuto.tsx +0 -4
- package/src/shell/hashRoute.test.ts +7 -15
- package/src/shell/hashRoute.ts +31 -31
- package/src/shell/useHashRoute.ts +38 -13
- package/src/styles/dashboard-theme.css +18 -7
- package/src/types/controls.ts +11 -0
- package/src/types/playground.ts +4 -0
- package/src/types/report.ts +32 -9
- package/templates/playground/buildRegistry.ts +1 -1
- package/templates/vite.dslinter.snippet.ts +15 -4
- package/vite/collectScanModules.test.ts +91 -3
- package/vite/collectScanModules.ts +94 -29
- package/vite/consumer.config.mjs +6 -3
- package/vite/consumerAlias.test.ts +47 -0
- package/vite/consumerAlias.ts +114 -0
- package/vite/embedTailwindSources.test.ts +74 -0
- package/vite/embedTailwindSources.ts +97 -0
- package/vite/loadConsumerAliases.test.ts +131 -0
- package/vite/loadConsumerAliases.ts +155 -0
- package/vite/openFileInEditor.mjs +196 -0
- package/vite/openFileInEditor.test.mjs +87 -0
- package/vite/plugin.resolve.test.ts +72 -0
- package/vite/plugin.ts +216 -19
- package/vite/reportPath.test.ts +19 -0
- package/vite/resolveWayfinderImport.ts +56 -0
- package/vite/shims/inertia-react.tsx +85 -0
- package/vite/shims/wayfinder-actions.ts +33 -0
- package/vite/shims/wayfinder-routes.ts +30 -0
- package/vite/shims/ziggy-js.ts +12 -0
- package/dashboard-dist/assets/DashboardLayoutAuto-Bm7yfyC-.css +0 -1
- package/dashboard-dist/assets/DashboardLayoutAuto-DgwO_itB.js +0 -1
- package/dashboard-dist/assets/index-Cbv7vXvH.css +0 -1
- package/dashboard-dist/assets/index-e20cwqnb.js +0 -206
- package/src/components/playgroundUsageTwoslash.ts +0 -69
- package/templates/vite.dslint-scan-alias.snippet.ts +0 -4
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
PlaygroundArgs,
|
|
4
|
+
PlaygroundControl,
|
|
5
|
+
PlaygroundValuesUpdater,
|
|
6
|
+
} from "../types/controls";
|
|
3
7
|
import type { PlaygroundEntry } from "../types/playground";
|
|
4
8
|
import type { A11yModuleSummary } from "../report/a11yForModule";
|
|
9
|
+
import type { PlaygroundA11yFinding } from "../playground/scanVariantA11y";
|
|
5
10
|
import type { CodeScoreModuleSummary } from "../report/codeScoreForModule";
|
|
6
11
|
import type { LintFinding, UsageSummary } from "../types/report";
|
|
7
12
|
import { Badge } from "./ui/badge";
|
|
@@ -27,16 +32,10 @@ type UsageProps = {
|
|
|
27
32
|
};
|
|
28
33
|
|
|
29
34
|
export function PlaygroundUsageSection({ entry, values }: UsageProps) {
|
|
30
|
-
const usage =
|
|
31
|
-
entry.usageSnippet?.(values) ??
|
|
32
|
-
`// Pass usageSnippet on this PlaygroundEntry, or derive snippets from dslint controls.\n<${entry.id} />`;
|
|
35
|
+
const usage = entry.usageSnippet?.(values) ?? `<${entry.id} />`;
|
|
33
36
|
|
|
34
37
|
return (
|
|
35
|
-
<Section
|
|
36
|
-
id="usage"
|
|
37
|
-
title="Usage"
|
|
38
|
-
description="Example usage for the current playground values."
|
|
39
|
-
>
|
|
38
|
+
<Section id="usage" title="Usage">
|
|
40
39
|
<PlaygroundUsageCode source={usage} />
|
|
41
40
|
</Section>
|
|
42
41
|
);
|
|
@@ -90,7 +89,7 @@ export function PlaygroundTokenStyleSection({
|
|
|
90
89
|
) : (
|
|
91
90
|
<EmptyCard>
|
|
92
91
|
Token findings update when{" "}
|
|
93
|
-
<span className="font-mono">
|
|
92
|
+
<span className="font-mono">dslinter-report.json</span> is available
|
|
94
93
|
(same fetch as Governance).
|
|
95
94
|
</EmptyCard>
|
|
96
95
|
)}
|
|
@@ -143,7 +142,7 @@ export function PlaygroundCodeScoreSection({
|
|
|
143
142
|
) : (
|
|
144
143
|
<EmptyCard>
|
|
145
144
|
Code score updates when{" "}
|
|
146
|
-
<span className="font-mono">
|
|
145
|
+
<span className="font-mono">dslinter-report.json</span> is available
|
|
147
146
|
(same fetch as Governance).
|
|
148
147
|
</EmptyCard>
|
|
149
148
|
)}
|
|
@@ -152,12 +151,22 @@ export function PlaygroundCodeScoreSection({
|
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
type A11yProps = {
|
|
155
|
-
a11y: A11yModuleSummary
|
|
154
|
+
a11y: Omit<A11yModuleSummary, "findings"> & {
|
|
155
|
+
findings: PlaygroundA11yFinding[];
|
|
156
|
+
};
|
|
156
157
|
reportReady: boolean;
|
|
158
|
+
variantScanPending?: boolean;
|
|
157
159
|
};
|
|
158
160
|
|
|
159
|
-
export function PlaygroundA11ySection({
|
|
161
|
+
export function PlaygroundA11ySection({
|
|
162
|
+
a11y,
|
|
163
|
+
reportReady,
|
|
164
|
+
variantScanPending = false,
|
|
165
|
+
}: A11yProps) {
|
|
160
166
|
const hasFindingRows = reportReady && a11y.findings.length > 0;
|
|
167
|
+
const showVariantColumn = a11y.findings.some(
|
|
168
|
+
(f) => f.variant_label != null && f.variant_label !== "",
|
|
169
|
+
);
|
|
161
170
|
|
|
162
171
|
return (
|
|
163
172
|
<>
|
|
@@ -165,6 +174,7 @@ export function PlaygroundA11ySection({ a11y, reportReady }: A11yProps) {
|
|
|
165
174
|
<Table>
|
|
166
175
|
<TableHeader>
|
|
167
176
|
<TableRow>
|
|
177
|
+
{showVariantColumn ? <TableHead>Variant</TableHead> : null}
|
|
168
178
|
<TableHead>Rule</TableHead>
|
|
169
179
|
<TableHead>Line</TableHead>
|
|
170
180
|
<TableHead>Severity</TableHead>
|
|
@@ -173,7 +183,14 @@ export function PlaygroundA11ySection({ a11y, reportReady }: A11yProps) {
|
|
|
173
183
|
</TableHeader>
|
|
174
184
|
<TableBody>
|
|
175
185
|
{a11y.findings.map((f, i) => (
|
|
176
|
-
<TableRow
|
|
186
|
+
<TableRow
|
|
187
|
+
key={`${f.rule_id}-${f.line ?? "x"}-${f.variant_label ?? ""}-${i}`}
|
|
188
|
+
>
|
|
189
|
+
{showVariantColumn ? (
|
|
190
|
+
<TableCell className="font-mono text-xs text-muted-foreground">
|
|
191
|
+
{f.variant_label ?? "—"}
|
|
192
|
+
</TableCell>
|
|
193
|
+
) : null}
|
|
177
194
|
<TableCell>{f.rule_id}</TableCell>
|
|
178
195
|
<TableCell>{f.line ?? "—"}</TableCell>
|
|
179
196
|
<TableCell>{f.severity}</TableCell>
|
|
@@ -184,12 +201,14 @@ export function PlaygroundA11ySection({ a11y, reportReady }: A11yProps) {
|
|
|
184
201
|
</Table>
|
|
185
202
|
) : reportReady && a11y.issueCount === 0 ? (
|
|
186
203
|
<EmptyCard>
|
|
187
|
-
|
|
204
|
+
{variantScanPending
|
|
205
|
+
? "Scanning variant previews for color contrast…"
|
|
206
|
+
: "No accessibility findings on this file or its variant previews in the current report."}
|
|
188
207
|
</EmptyCard>
|
|
189
208
|
) : (
|
|
190
209
|
<EmptyCard>
|
|
191
210
|
A11y score updates when{" "}
|
|
192
|
-
<span className="font-mono">
|
|
211
|
+
<span className="font-mono">dslinter-report.json</span> is available
|
|
193
212
|
(same fetch as Governance).
|
|
194
213
|
</EmptyCard>
|
|
195
214
|
)}
|
|
@@ -198,15 +217,16 @@ export function PlaygroundA11ySection({ a11y, reportReady }: A11yProps) {
|
|
|
198
217
|
}
|
|
199
218
|
|
|
200
219
|
type ApiProps = {
|
|
220
|
+
entry: PlaygroundEntry;
|
|
201
221
|
controls: PlaygroundControl[];
|
|
202
222
|
values: PlaygroundArgs;
|
|
203
|
-
onChange:
|
|
223
|
+
onChange: PlaygroundValuesUpdater;
|
|
204
224
|
onReset: () => void;
|
|
205
225
|
/** When set, adds columns for how often each prop appears at scanned JSX call sites. */
|
|
206
226
|
reportUsage?: UsageSummary;
|
|
207
227
|
/** Declared prop names from the scan (definitions + playground specs), used for “never passed” hints. */
|
|
208
228
|
declaredPropsFromScan?: string[];
|
|
209
|
-
/** True when `
|
|
229
|
+
/** True when `dslinter-report.json` is loaded (even if this component has no usage row). */
|
|
210
230
|
governanceReportLoaded?: boolean;
|
|
211
231
|
};
|
|
212
232
|
|
|
@@ -237,9 +257,9 @@ export function PlaygroundApiReference({
|
|
|
237
257
|
|
|
238
258
|
const patch = useCallback(
|
|
239
259
|
(key: string, value: string | number | boolean) => {
|
|
240
|
-
onChange({ ...
|
|
260
|
+
onChange((prev) => ({ ...prev, [key]: value }));
|
|
241
261
|
},
|
|
242
|
-
[onChange
|
|
262
|
+
[onChange],
|
|
243
263
|
);
|
|
244
264
|
|
|
245
265
|
const rows = controlsToApiRows(controls);
|
|
@@ -252,12 +272,13 @@ export function PlaygroundApiReference({
|
|
|
252
272
|
.filter((k) => !controlKeys.has(k))
|
|
253
273
|
.sort((a, b) => a.localeCompare(b))
|
|
254
274
|
: [];
|
|
255
|
-
|
|
275
|
+
const repoUsageProps = showRepo
|
|
276
|
+
? [...rows.map((r) => r.prop), ...extraRepoProps]
|
|
277
|
+
: [];
|
|
256
278
|
return (
|
|
257
279
|
<Section
|
|
258
280
|
id="api-reference"
|
|
259
281
|
title="API reference"
|
|
260
|
-
description=""
|
|
261
282
|
actions={
|
|
262
283
|
<Button type="button" variant="outline" size="sm" onClick={onReset}>
|
|
263
284
|
Reset defaults
|
|
@@ -270,20 +291,12 @@ export function PlaygroundApiReference({
|
|
|
270
291
|
<TableHead>Prop</TableHead>
|
|
271
292
|
<TableHead>Type</TableHead>
|
|
272
293
|
<TableHead>Value</TableHead>
|
|
273
|
-
{showRepo ? (
|
|
274
|
-
<>
|
|
275
|
-
<TableHead>Usage</TableHead>
|
|
276
|
-
<TableHead>Values</TableHead>
|
|
277
|
-
</>
|
|
278
|
-
) : null}
|
|
279
294
|
</TableRow>
|
|
280
295
|
</TableHeader>
|
|
281
296
|
<TableBody>
|
|
282
297
|
{controls.map((c) => {
|
|
283
298
|
const r = rows.find((row) => row.prop === c.key);
|
|
284
299
|
if (!r) return null;
|
|
285
|
-
const n = showRepo ? (freqs[r.prop] ?? 0) : 0;
|
|
286
|
-
const valueChips = formatRepoLiteralChips(valueFreqs[r.prop]);
|
|
287
300
|
return (
|
|
288
301
|
<TableRow key={r.prop}>
|
|
289
302
|
<TableCell className="font-medium">{r.prop}</TableCell>
|
|
@@ -322,9 +335,9 @@ export function PlaygroundApiReference({
|
|
|
322
335
|
) : (
|
|
323
336
|
<span className="font-mono text-xs flex items-center gap-1">
|
|
324
337
|
{r.type}
|
|
325
|
-
{r.
|
|
338
|
+
{r.defaultBadge ? (
|
|
326
339
|
<Badge variant="secondary" size="sm">
|
|
327
|
-
{r.
|
|
340
|
+
{r.defaultBadge}
|
|
328
341
|
</Badge>
|
|
329
342
|
) : null}
|
|
330
343
|
</span>
|
|
@@ -339,40 +352,27 @@ export function PlaygroundApiReference({
|
|
|
339
352
|
layout="table"
|
|
340
353
|
/>
|
|
341
354
|
</TableCell>
|
|
342
|
-
{showRepo ? (
|
|
343
|
-
<>
|
|
344
|
-
<TableCell>{n}</TableCell>
|
|
345
|
-
<TableCell>{valueChips}</TableCell>
|
|
346
|
-
</>
|
|
347
|
-
) : null}
|
|
348
355
|
</TableRow>
|
|
349
356
|
);
|
|
350
357
|
})}
|
|
351
358
|
</TableBody>
|
|
352
359
|
</Table>
|
|
353
360
|
|
|
354
|
-
{showRepo
|
|
355
|
-
<
|
|
356
|
-
<h3 className="text-sm font-semibold text-foreground">
|
|
357
|
-
Also seen in repo (not in playground)
|
|
358
|
-
</h3>
|
|
359
|
-
<p className="mt-1 text-xs text-muted-foreground">
|
|
360
|
-
These prop names appear in scanned JSX but are not wired as
|
|
361
|
-
playground controls on this page.
|
|
362
|
-
</p>
|
|
361
|
+
{showRepo ? (
|
|
362
|
+
<Section id="repo-usage" title="Repo usage" className="mt-4">
|
|
363
363
|
<Table>
|
|
364
364
|
<TableHeader>
|
|
365
365
|
<TableRow>
|
|
366
366
|
<TableHead>Prop</TableHead>
|
|
367
|
-
<TableHead>
|
|
368
|
-
<TableHead>
|
|
367
|
+
<TableHead>Count</TableHead>
|
|
368
|
+
<TableHead>Values</TableHead>
|
|
369
369
|
</TableRow>
|
|
370
370
|
</TableHeader>
|
|
371
371
|
<TableBody>
|
|
372
|
-
{
|
|
372
|
+
{repoUsageProps.map((prop) => (
|
|
373
373
|
<TableRow key={prop}>
|
|
374
|
-
<TableCell>{prop}</TableCell>
|
|
375
|
-
<TableCell
|
|
374
|
+
<TableCell className="font-medium">{prop}</TableCell>
|
|
375
|
+
<TableCell>{freqs[prop] ?? 0}</TableCell>
|
|
376
376
|
<TableCell>
|
|
377
377
|
{formatRepoLiteralChips(valueFreqs[prop])}
|
|
378
378
|
</TableCell>
|
|
@@ -380,7 +380,7 @@ export function PlaygroundApiReference({
|
|
|
380
380
|
))}
|
|
381
381
|
</TableBody>
|
|
382
382
|
</Table>
|
|
383
|
-
</
|
|
383
|
+
</Section>
|
|
384
384
|
) : null}
|
|
385
385
|
</Section>
|
|
386
386
|
);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { forwardRef, useMemo, type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { WorkspaceReport } from "../types/report";
|
|
3
|
+
import {
|
|
4
|
+
buildAppPreviewThemeFromReport,
|
|
5
|
+
cssVariablesForPreviewTheme,
|
|
6
|
+
} from "../playground/appPreviewTheme";
|
|
7
|
+
import { useDashboardTheme } from "../shell/DashboardLayout";
|
|
8
|
+
import { cn } from "../lib/utils";
|
|
9
|
+
|
|
10
|
+
type Props = {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
workspaceReport: WorkspaceReport | null;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const PlaygroundAppThemeWrapper = forwardRef<HTMLDivElement, Props>(
|
|
17
|
+
function PlaygroundAppThemeWrapper(
|
|
18
|
+
{ children, workspaceReport, className },
|
|
19
|
+
ref,
|
|
20
|
+
) {
|
|
21
|
+
const { resolvedTheme } = useDashboardTheme();
|
|
22
|
+
const previewTheme = useMemo(
|
|
23
|
+
() => buildAppPreviewThemeFromReport(workspaceReport),
|
|
24
|
+
[workspaceReport],
|
|
25
|
+
);
|
|
26
|
+
const isDark = resolvedTheme === "dark";
|
|
27
|
+
const hasDarkTokens =
|
|
28
|
+
previewTheme != null && Object.keys(previewTheme.dark).length > 0;
|
|
29
|
+
|
|
30
|
+
if (!previewTheme) {
|
|
31
|
+
return (
|
|
32
|
+
<div
|
|
33
|
+
ref={ref}
|
|
34
|
+
className={cn(isDark && "dark", className)}
|
|
35
|
+
data-app-preview-theme={resolvedTheme}
|
|
36
|
+
>
|
|
37
|
+
{children}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// App CSS has light tokens only — don't inject them in dark mode; inherit
|
|
43
|
+
// dashboard dark tokens from the surrounding [data-dashboard-theme] tree.
|
|
44
|
+
if (isDark && !hasDarkTokens) {
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
ref={ref}
|
|
48
|
+
className={cn("ds-playground-app-preview", className)}
|
|
49
|
+
data-app-preview-theme={resolvedTheme}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const vars = cssVariablesForPreviewTheme(
|
|
57
|
+
previewTheme,
|
|
58
|
+
isDark ? "dark" : "light",
|
|
59
|
+
);
|
|
60
|
+
const usesDarkTokens = isDark && hasDarkTokens;
|
|
61
|
+
|
|
62
|
+
const style = {
|
|
63
|
+
...vars,
|
|
64
|
+
colorScheme: usesDarkTokens ? "dark" : "light",
|
|
65
|
+
} as CSSProperties;
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
ref={ref}
|
|
70
|
+
className={cn(
|
|
71
|
+
"ds-playground-app-preview",
|
|
72
|
+
isDark && "dark",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
style={style}
|
|
76
|
+
data-app-preview-theme={resolvedTheme}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
},
|
|
82
|
+
);
|
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { useCallback } from "react";
|
|
2
|
-
import type { PlaygroundArgs, PlaygroundControl } from "../types/controls";
|
|
2
|
+
import type { PlaygroundArgs, PlaygroundControl, PlaygroundValuesUpdater } from "../types/controls";
|
|
3
3
|
import { Button } from "./ui/button";
|
|
4
4
|
import { PlaygroundControlField } from "./PlaygroundControlField";
|
|
5
5
|
|
|
6
6
|
type Props = {
|
|
7
7
|
controls: PlaygroundControl[];
|
|
8
8
|
values: PlaygroundArgs;
|
|
9
|
-
onChange:
|
|
9
|
+
onChange: PlaygroundValuesUpdater;
|
|
10
10
|
onReset: () => void;
|
|
11
11
|
/** Omit the outer card wrapper (doc-style pages provide their own section chrome). */
|
|
12
12
|
bare?: boolean;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
export function PlaygroundControls({
|
|
16
|
-
controls,
|
|
17
|
-
values,
|
|
18
|
-
onChange,
|
|
19
|
-
onReset,
|
|
20
|
-
bare,
|
|
21
|
-
}: Props) {
|
|
15
|
+
export function PlaygroundControls({ controls, values, onChange, onReset, bare }: Props) {
|
|
22
16
|
const patch = useCallback(
|
|
23
17
|
(key: string, value: string | number | boolean) => {
|
|
24
|
-
onChange({ ...
|
|
18
|
+
onChange((prev) => ({ ...prev, [key]: value }));
|
|
25
19
|
},
|
|
26
|
-
[onChange
|
|
20
|
+
[onChange],
|
|
27
21
|
);
|
|
28
22
|
|
|
29
23
|
if (controls.length === 0) return null;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Component, type ErrorInfo, type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
type Props = {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
componentName?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
type State = {
|
|
9
|
+
error: Error | null;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class PlaygroundPreviewErrorBoundary extends Component<Props, State> {
|
|
13
|
+
state: State = { error: null };
|
|
14
|
+
|
|
15
|
+
static getDerivedStateFromError(error: Error): State {
|
|
16
|
+
return { error };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
20
|
+
if (process.env.NODE_ENV === "development") {
|
|
21
|
+
console.warn(
|
|
22
|
+
"[dslinter] preview render failed",
|
|
23
|
+
error,
|
|
24
|
+
info.componentStack,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentDidUpdate(prevProps: Props) {
|
|
30
|
+
if (prevProps.children !== this.props.children && this.state.error) {
|
|
31
|
+
this.setState({ error: null });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
if (this.state.error) {
|
|
37
|
+
const name = this.props.componentName ?? "Component";
|
|
38
|
+
return (
|
|
39
|
+
<div className="rounded-md border border-border bg-muted/40 px-4 py-6 text-sm text-muted-foreground">
|
|
40
|
+
<p className="font-medium text-foreground">
|
|
41
|
+
Preview could not render {name}
|
|
42
|
+
</p>
|
|
43
|
+
<p className="mt-2">
|
|
44
|
+
This component may need Inertia page props, a provider, or other app
|
|
45
|
+
context. The scanner snapshot and governance panels still reflect
|
|
46
|
+
static analysis.
|
|
47
|
+
</p>
|
|
48
|
+
<p className="mt-2 font-mono text-xs">{this.state.error.message}</p>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return this.props.children;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
import { useDashboardTheme } from "../shell/DashboardLayout";
|
|
2
3
|
import { renderPlaygroundUsageHtml } from "./playgroundUsageHighlight";
|
|
3
4
|
|
|
4
5
|
const shellClass =
|
|
5
|
-
"playground-usage-shiki mt-4 overflow-x-auto rounded-lg border
|
|
6
|
+
"playground-usage-shiki mt-4 overflow-x-auto rounded-lg border border-border p-4 text-sm leading-relaxed text-foreground " +
|
|
6
7
|
"[&_.shiki]:!bg-transparent [&_pre.shiki]:!m-0 [&_pre.shiki]:!bg-transparent [&_pre.shiki]:!p-0";
|
|
7
8
|
|
|
8
9
|
const plainPreClass =
|
|
9
|
-
"m-0 whitespace-pre font-mono text-sm leading-relaxed text-
|
|
10
|
+
"m-0 whitespace-pre font-mono text-sm leading-relaxed text-foreground";
|
|
10
11
|
|
|
11
12
|
type Props = {
|
|
12
13
|
source: string;
|
|
13
14
|
};
|
|
14
15
|
|
|
15
16
|
export function PlaygroundUsageCode({ source }: Props) {
|
|
17
|
+
const { theme } = useDashboardTheme();
|
|
16
18
|
const [html, setHtml] = useState<string | null>(null);
|
|
17
19
|
const seq = useRef(0);
|
|
18
20
|
|
|
@@ -23,7 +25,7 @@ export function PlaygroundUsageCode({ source }: Props) {
|
|
|
23
25
|
|
|
24
26
|
void (async () => {
|
|
25
27
|
try {
|
|
26
|
-
const next = await renderPlaygroundUsageHtml(source, ac.signal);
|
|
28
|
+
const next = await renderPlaygroundUsageHtml(source, theme, ac.signal);
|
|
27
29
|
if (id !== seq.current) return;
|
|
28
30
|
setHtml(next);
|
|
29
31
|
} catch (e) {
|
|
@@ -34,7 +36,7 @@ export function PlaygroundUsageCode({ source }: Props) {
|
|
|
34
36
|
})();
|
|
35
37
|
|
|
36
38
|
return () => ac.abort();
|
|
37
|
-
}, [source]);
|
|
39
|
+
}, [source, theme]);
|
|
38
40
|
|
|
39
41
|
if (html) {
|
|
40
42
|
return (
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
1
3
|
import type { PlaygroundArgs } from "../types/controls";
|
|
2
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
scanVariantPreviews,
|
|
6
|
+
type PlaygroundA11yFinding,
|
|
7
|
+
} from "../playground/scanVariantA11y";
|
|
3
8
|
import { Badge } from "./ui/badge";
|
|
4
9
|
import { PLAYGROUND_VARIANT_MATRIX_CAP } from "../playground/enumerateControlCombinations";
|
|
5
10
|
|
|
6
11
|
type Props = {
|
|
7
|
-
|
|
12
|
+
renderPreview: (values: PlaygroundArgs) => ReactNode;
|
|
8
13
|
combinations: PlaygroundArgs[];
|
|
9
14
|
finiteAxisKeys: string[];
|
|
10
15
|
totalCount: number;
|
|
11
16
|
capped: boolean;
|
|
17
|
+
onVariantA11yScan?: (findings: PlaygroundA11yFinding[]) => void;
|
|
12
18
|
};
|
|
13
19
|
|
|
14
20
|
function formatValue(value: string | number | boolean): string {
|
|
@@ -16,52 +22,113 @@ function formatValue(value: string | number | boolean): string {
|
|
|
16
22
|
return JSON.stringify(value);
|
|
17
23
|
}
|
|
18
24
|
|
|
25
|
+
function variantKey(combo: PlaygroundArgs, axisKeys: string[]): string {
|
|
26
|
+
return axisKeys.map((k) => `${k}:${formatValue(combo[k] ?? "")}`).join("|");
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
export function PlaygroundVariantMatrix({
|
|
20
|
-
|
|
30
|
+
renderPreview,
|
|
21
31
|
combinations,
|
|
22
32
|
finiteAxisKeys,
|
|
23
33
|
totalCount,
|
|
24
34
|
capped,
|
|
35
|
+
onVariantA11yScan,
|
|
25
36
|
}: Props) {
|
|
26
|
-
|
|
37
|
+
const previewRefs = useRef(new Map<string, HTMLDivElement>());
|
|
38
|
+
|
|
39
|
+
const visibleCombinations = combinations.filter(
|
|
40
|
+
(combo) => combo.asChild !== true,
|
|
41
|
+
);
|
|
42
|
+
const visibleAxisKeys = finiteAxisKeys.filter((key) => key !== "asChild");
|
|
43
|
+
const skipsAsChildAxis = finiteAxisKeys.includes("asChild");
|
|
44
|
+
const adjustedTotalCount = skipsAsChildAxis
|
|
45
|
+
? Math.ceil(totalCount / 2)
|
|
46
|
+
: totalCount;
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!onVariantA11yScan || visibleCombinations.length === 0) return;
|
|
50
|
+
|
|
51
|
+
let cancelled = false;
|
|
52
|
+
const frameId = requestAnimationFrame(() => {
|
|
53
|
+
void (async () => {
|
|
54
|
+
await new Promise<void>((resolve) => {
|
|
55
|
+
requestAnimationFrame(() => resolve());
|
|
56
|
+
});
|
|
57
|
+
if (cancelled) return;
|
|
58
|
+
|
|
59
|
+
const targets = visibleCombinations.flatMap((combo) => {
|
|
60
|
+
const key = variantKey(combo, visibleAxisKeys);
|
|
61
|
+
const element = previewRefs.current.get(key);
|
|
62
|
+
if (!element) return [];
|
|
63
|
+
return [{ element, combo, axisKeys: visibleAxisKeys }];
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (targets.length === 0) {
|
|
67
|
+
if (!cancelled) onVariantA11yScan([]);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const findings = await scanVariantPreviews(targets);
|
|
73
|
+
if (!cancelled) onVariantA11yScan(findings);
|
|
74
|
+
} catch {
|
|
75
|
+
if (!cancelled) onVariantA11yScan([]);
|
|
76
|
+
}
|
|
77
|
+
})();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return () => {
|
|
81
|
+
cancelled = true;
|
|
82
|
+
cancelAnimationFrame(frameId);
|
|
83
|
+
};
|
|
84
|
+
}, [onVariantA11yScan, visibleCombinations, visibleAxisKeys]);
|
|
85
|
+
|
|
86
|
+
if (visibleCombinations.length === 0) return null;
|
|
27
87
|
|
|
28
88
|
return (
|
|
29
89
|
<>
|
|
30
90
|
{capped ? (
|
|
31
|
-
<p className="rounded-md border border-border
|
|
32
|
-
Showing {
|
|
33
|
-
{PLAYGROUND_VARIANT_MATRIX_CAP}). Reduce select
|
|
34
|
-
controls to preview more here.
|
|
91
|
+
<p className="rounded-md border border-border px-3 py-2 text-sm text-muted-foreground">
|
|
92
|
+
Showing {visibleCombinations.length} of {adjustedTotalCount}{" "}
|
|
93
|
+
combinations (limit {PLAYGROUND_VARIANT_MATRIX_CAP}). Reduce select
|
|
94
|
+
options or split controls to preview more here.
|
|
35
95
|
</p>
|
|
36
96
|
) : null}
|
|
37
97
|
<div className="mt-4 flex flex-col gap-4">
|
|
38
|
-
{
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
98
|
+
{visibleCombinations.map((combo) => {
|
|
99
|
+
const key = variantKey(combo, visibleAxisKeys);
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
key={key}
|
|
103
|
+
className="flex min-w-0 flex-col overflow-hidden rounded-lg border border-border bg-card text-card-foreground shadow-xs"
|
|
104
|
+
>
|
|
105
|
+
<div className="flex flex-wrap gap-1 border-b p-2">
|
|
106
|
+
{visibleAxisKeys.map((k) => {
|
|
107
|
+
const v = combo[k];
|
|
108
|
+
return (
|
|
109
|
+
<Badge
|
|
110
|
+
key={k}
|
|
111
|
+
variant="outline"
|
|
112
|
+
size="sm"
|
|
113
|
+
className="font-mono text-xs"
|
|
114
|
+
>
|
|
115
|
+
{k}={v === undefined ? "?" : formatValue(v)}
|
|
116
|
+
</Badge>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</div>
|
|
120
|
+
<div
|
|
121
|
+
ref={(el) => {
|
|
122
|
+
if (el) previewRefs.current.set(key, el);
|
|
123
|
+
else previewRefs.current.delete(key);
|
|
124
|
+
}}
|
|
125
|
+
className="min-w-0 p-3"
|
|
126
|
+
>
|
|
127
|
+
{renderPreview(combo)}
|
|
128
|
+
</div>
|
|
62
129
|
</div>
|
|
63
|
-
|
|
64
|
-
)
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
65
132
|
</div>
|
|
66
133
|
</>
|
|
67
134
|
);
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
2
3
|
|
|
3
4
|
export function Section({
|
|
4
5
|
id,
|
|
6
|
+
className,
|
|
5
7
|
children,
|
|
6
8
|
title,
|
|
7
9
|
description,
|
|
8
10
|
actions,
|
|
9
11
|
}: {
|
|
10
12
|
id: string;
|
|
13
|
+
className?: string;
|
|
11
14
|
children: ReactNode;
|
|
12
15
|
title: string;
|
|
13
|
-
description
|
|
16
|
+
description?: string;
|
|
14
17
|
actions?: ReactNode;
|
|
15
18
|
}) {
|
|
16
19
|
return (
|
|
17
|
-
<section id={id} className="scroll-mt-20">
|
|
20
|
+
<section id={id} className={cn("scroll-mt-20", className)}>
|
|
18
21
|
<div className="flex items-center justify-between gap-2">
|
|
19
22
|
<div>
|
|
20
23
|
<h2 className="text-lg/none font-semibold tracking-tight text-foreground">
|