design-system-enforcer 1.0.0 → 1.0.3

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/dist/index.cjs CHANGED
@@ -48,8 +48,8 @@ __export(index_exports, {
48
48
  module.exports = __toCommonJS(index_exports);
49
49
 
50
50
  // src/config/loader.ts
51
- var import_node_fs = __toESM(require("fs"), 1);
52
- var import_node_path = __toESM(require("path"), 1);
51
+ var import_node_fs3 = __toESM(require("fs"), 1);
52
+ var import_node_path3 = __toESM(require("path"), 1);
53
53
  var import_node_url = require("url");
54
54
 
55
55
  // src/config/presets/berserk.ts
@@ -138,6 +138,9 @@ var berserkPreset = {
138
138
  disallowRawHtml: {
139
139
  button: { replaceWith: "Button", importFrom: "@/components/ui/button", checkRawElement: true },
140
140
  select: { replaceWith: "Dropdown", importFrom: "@/components/ui/dropdown", checkRawElement: true },
141
+ textarea: { replaceWith: "Textarea", importFrom: "@/components/ui/textarea", checkRawElement: true },
142
+ progress: { replaceWith: "ProgressBar", importFrom: "@/components/ui/progress", checkRawElement: true },
143
+ dialog: { replaceWith: "Modal or Dialog", importFrom: "@/components/ui/modal", checkRawElement: true },
141
144
  table: { replaceWith: "Table", importFrom: "@/components/ui/table", checkRawElement: true },
142
145
  thead: { replaceWith: "TableHeader", importFrom: "@/components/ui/table", checkRawElement: true },
143
146
  tbody: { replaceWith: "TableBody", importFrom: "@/components/ui/table", checkRawElement: true },
@@ -189,7 +192,8 @@ var berserkPreset = {
189
192
  "table-no-horizontal-stacking": "error",
190
193
  "table-windowed-pagination": "error",
191
194
  "tabs-component-required": "error",
192
- "component-inventory-active": "error"
195
+ "component-inventory-active": "error",
196
+ "no-wrapping-compact-elements": "error"
193
197
  }
194
198
  };
195
199
 
@@ -226,7 +230,10 @@ var defaultPreset = {
226
230
  components: {
227
231
  disallowRawHtml: {
228
232
  button: { replaceWith: "Button", importFrom: "@/components/ui/button", checkRawElement: true },
229
- select: { replaceWith: "Dropdown", importFrom: "@/components/ui/dropdown", checkRawElement: true }
233
+ select: { replaceWith: "Dropdown", importFrom: "@/components/ui/dropdown", checkRawElement: true },
234
+ textarea: { replaceWith: "Textarea", importFrom: "@/components/ui/textarea", checkRawElement: true },
235
+ progress: { replaceWith: "ProgressBar", importFrom: "@/components/ui/progress", checkRawElement: true },
236
+ dialog: { replaceWith: "Modal or Dialog", importFrom: "@/components/ui/modal", checkRawElement: true }
230
237
  },
231
238
  disallowPseudoChips: true
232
239
  },
@@ -237,7 +244,8 @@ var defaultPreset = {
237
244
  "no-pseudo-chips": "warn",
238
245
  "no-chip-colored-buttons": "error",
239
246
  "no-text-arrows": "error",
240
- "no-backdrop-blur": "warn"
247
+ "no-backdrop-blur": "warn",
248
+ "no-wrapping-compact-elements": "warn"
241
249
  }
242
250
  };
243
251
 
@@ -301,6 +309,221 @@ var pythonPreset = {
301
309
  }
302
310
  };
303
311
 
312
+ // src/detector/index.ts
313
+ var import_node_fs2 = __toESM(require("fs"), 1);
314
+ var import_node_path2 = __toESM(require("path"), 1);
315
+
316
+ // src/detector/ignores.ts
317
+ var import_node_fs = __toESM(require("fs"), 1);
318
+ var import_node_path = __toESM(require("path"), 1);
319
+ var DEFAULT_IGNORES = [
320
+ "**/node_modules/**",
321
+ "**/.next/**",
322
+ "**/dist/**",
323
+ "**/build/**",
324
+ "**/out/**",
325
+ "**/.turbo/**",
326
+ "**/.git/**",
327
+ "**/coverage/**",
328
+ "**/.venv/**",
329
+ "**/venv/**",
330
+ "**/env/**",
331
+ "**/__pycache__/**",
332
+ "**/.pytest_cache/**",
333
+ "**/*.pyc",
334
+ // Non-UI code
335
+ "**/api/**",
336
+ "**/*.test.*",
337
+ "**/*.spec.*",
338
+ "**/__tests__/**",
339
+ "**/*.stories.*",
340
+ "**/generated/**"
341
+ ];
342
+ function getSmartIgnores(cwd) {
343
+ const result = new Set(DEFAULT_IGNORES);
344
+ const gitignorePath = import_node_path.default.resolve(cwd, ".gitignore");
345
+ if (import_node_fs.default.existsSync(gitignorePath)) {
346
+ try {
347
+ const content = import_node_fs.default.readFileSync(gitignorePath, "utf-8");
348
+ const lines = content.split(/\r?\n/);
349
+ for (let line of lines) {
350
+ line = line.trim();
351
+ if (!line || line.startsWith("#")) continue;
352
+ if (line.startsWith("/")) {
353
+ line = line.substring(1);
354
+ }
355
+ if (line.endsWith("/")) {
356
+ line = line.slice(0, -1);
357
+ }
358
+ if (line) {
359
+ result.add(`**/${line}/**`);
360
+ result.add(`**/${line}`);
361
+ }
362
+ }
363
+ } catch {
364
+ }
365
+ }
366
+ return Array.from(result);
367
+ }
368
+
369
+ // src/detector/index.ts
370
+ function safeReadJson(filePath) {
371
+ try {
372
+ if (import_node_fs2.default.existsSync(filePath)) {
373
+ const raw = import_node_fs2.default.readFileSync(filePath, "utf-8");
374
+ return JSON.parse(raw);
375
+ }
376
+ } catch {
377
+ }
378
+ return null;
379
+ }
380
+ function detectProject(cwd = process.cwd()) {
381
+ const pkg = safeReadJson(import_node_path2.default.resolve(cwd, "package.json"));
382
+ const deps = { ...pkg?.dependencies || {}, ...pkg?.devDependencies || {} };
383
+ const hasRequirements = import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "requirements.txt"));
384
+ const hasPyproject = import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "pyproject.toml"));
385
+ const hasPython = hasRequirements || hasPyproject || import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "setup.py"));
386
+ let isStreamlit = false;
387
+ if (hasPython) {
388
+ if (hasRequirements) {
389
+ try {
390
+ const reqContent = import_node_fs2.default.readFileSync(import_node_path2.default.resolve(cwd, "requirements.txt"), "utf-8");
391
+ if (reqContent.toLowerCase().includes("streamlit")) {
392
+ isStreamlit = true;
393
+ }
394
+ } catch {
395
+ }
396
+ }
397
+ }
398
+ const isPnpmMonorepo = import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "pnpm-workspace.yaml"));
399
+ const isTurbo = import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "turbo.json"));
400
+ let monorepoWorkspaces = [];
401
+ if (Array.isArray(pkg?.workspaces)) {
402
+ monorepoWorkspaces = pkg.workspaces;
403
+ } else if (pkg?.workspaces?.packages) {
404
+ monorepoWorkspaces = pkg.workspaces.packages;
405
+ }
406
+ const isMonorepo = isPnpmMonorepo || isTurbo || monorepoWorkspaces.length > 0;
407
+ let framework = "unknown";
408
+ if (deps["next"]) {
409
+ framework = "nextjs";
410
+ } else if (deps["vite"]) {
411
+ framework = "vite";
412
+ } else if (deps["@remix-run/react"]) {
413
+ framework = "remix";
414
+ } else if (deps["astro"]) {
415
+ framework = "astro";
416
+ } else if (deps["react-scripts"]) {
417
+ framework = "react-cra";
418
+ } else if (isStreamlit) {
419
+ framework = "streamlit";
420
+ } else if (hasPython) {
421
+ framework = "python-general";
422
+ }
423
+ const libraries = [];
424
+ const hasShadcnConfig = import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "components.json"));
425
+ if (hasShadcnConfig || deps["@radix-ui/react-slot"]) {
426
+ libraries.push("shadcn");
427
+ }
428
+ if (Object.keys(deps).some((d) => d.startsWith("@radix-ui/"))) {
429
+ if (!libraries.includes("shadcn")) libraries.push("radix");
430
+ }
431
+ if (deps["tailwindcss"]) {
432
+ libraries.push("tailwind");
433
+ }
434
+ if (deps["lucide-react"] || deps["lucide"]) {
435
+ libraries.push("lucide");
436
+ }
437
+ if (deps["@mui/material"] || deps["@material-ui/core"]) {
438
+ libraries.push("mui");
439
+ }
440
+ if (deps["@chakra-ui/react"]) {
441
+ libraries.push("chakra");
442
+ }
443
+ const hasTailwind = Boolean(deps["tailwindcss"] || import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "tailwind.config.js")) || import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "tailwind.config.ts")));
444
+ let tailwindVersion;
445
+ if (hasTailwind) {
446
+ const rawVersion = deps["tailwindcss"] || "";
447
+ if (rawVersion.includes("^4") || rawVersion.includes("~4") || rawVersion.startsWith("4")) {
448
+ tailwindVersion = "v4";
449
+ } else {
450
+ tailwindVersion = "v3";
451
+ }
452
+ }
453
+ const hasTypeScript = Boolean(deps["typescript"] || import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "tsconfig.json")));
454
+ const suggestedIncludes = [];
455
+ const extGroup = hasTypeScript ? "{tsx,jsx,ts,js}" : "{jsx,js}";
456
+ const candidateDirs = [
457
+ "frontend/src",
458
+ "frontend",
459
+ "apps/web/src",
460
+ "apps/web",
461
+ "client/src",
462
+ "client",
463
+ "src",
464
+ "app",
465
+ "pages",
466
+ "components"
467
+ ];
468
+ let foundUiDir = false;
469
+ for (const dir of candidateDirs) {
470
+ if (import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, dir))) {
471
+ if (dir === "frontend" && import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "frontend/src"))) continue;
472
+ if (dir === "client" && import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "client/src"))) continue;
473
+ if (dir === "apps/web" && import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, "apps/web/src"))) continue;
474
+ suggestedIncludes.push(`${dir}/**/*.${extGroup}`);
475
+ foundUiDir = true;
476
+ break;
477
+ }
478
+ }
479
+ if (hasPython) {
480
+ suggestedIncludes.push("**/*.py");
481
+ } else if (!foundUiDir) {
482
+ suggestedIncludes.push(`**/*.${extGroup}`);
483
+ }
484
+ const suggestedIgnores = getSmartIgnores(cwd);
485
+ if (hasShadcnConfig || libraries.includes("shadcn")) {
486
+ suggestedIgnores.push("**/components/ui/**");
487
+ }
488
+ const tokenCandidates = [
489
+ "frontend/src/lib/tokens.ts",
490
+ "frontend/src/tokens.ts",
491
+ "src/lib/tokens.ts",
492
+ "src/tokens.ts",
493
+ "src/design/tokens.ts",
494
+ "styles/tokens.css",
495
+ "src/app/globals.css",
496
+ "frontend/src/index.css",
497
+ "src/index.css"
498
+ ];
499
+ let tokensFile;
500
+ for (const candidate of tokenCandidates) {
501
+ if (import_node_fs2.default.existsSync(import_node_path2.default.resolve(cwd, candidate))) {
502
+ tokensFile = candidate;
503
+ break;
504
+ }
505
+ }
506
+ let preset = "berserk";
507
+ if (framework === "streamlit" || framework === "python-general") {
508
+ preset = "python";
509
+ }
510
+ return {
511
+ root: cwd,
512
+ isMonorepo,
513
+ monorepoWorkspaces,
514
+ framework,
515
+ libraries,
516
+ hasTailwind,
517
+ tailwindVersion,
518
+ hasTypeScript,
519
+ hasPython,
520
+ suggestedIncludes,
521
+ suggestedIgnores,
522
+ tokensFile,
523
+ preset
524
+ };
525
+ }
526
+
304
527
  // src/config/loader.ts
305
528
  var CONFIG_FILES = [
306
529
  "dse.config.js",
@@ -315,17 +538,18 @@ function defineConfig(config) {
315
538
  return config;
316
539
  }
317
540
  async function loadConfig(cwd = process.cwd(), customPath) {
541
+ const detected = detectProject(cwd);
318
542
  let userConfig = {};
319
543
  let foundConfigPath = null;
320
544
  if (customPath) {
321
- const resolved = import_node_path.default.resolve(cwd, customPath);
322
- if (import_node_fs.default.existsSync(resolved)) {
545
+ const resolved = import_node_path3.default.resolve(cwd, customPath);
546
+ if (import_node_fs3.default.existsSync(resolved)) {
323
547
  foundConfigPath = resolved;
324
548
  }
325
549
  } else {
326
550
  for (const filename of CONFIG_FILES) {
327
- const candidate = import_node_path.default.resolve(cwd, filename);
328
- if (import_node_fs.default.existsSync(candidate)) {
551
+ const candidate = import_node_path3.default.resolve(cwd, filename);
552
+ if (import_node_fs3.default.existsSync(candidate)) {
329
553
  foundConfigPath = candidate;
330
554
  break;
331
555
  }
@@ -333,7 +557,7 @@ async function loadConfig(cwd = process.cwd(), customPath) {
333
557
  }
334
558
  if (foundConfigPath) {
335
559
  if (foundConfigPath.endsWith(".json") || foundConfigPath.endsWith(".dserc")) {
336
- const content = import_node_fs.default.readFileSync(foundConfigPath, "utf-8");
560
+ const content = import_node_fs3.default.readFileSync(foundConfigPath, "utf-8");
337
561
  try {
338
562
  userConfig = JSON.parse(content);
339
563
  } catch (err) {
@@ -346,7 +570,7 @@ async function loadConfig(cwd = process.cwd(), customPath) {
346
570
  userConfig = mod.default || mod;
347
571
  } catch {
348
572
  try {
349
- const content = import_node_fs.default.readFileSync(foundConfigPath, "utf-8");
573
+ const content = import_node_fs3.default.readFileSync(foundConfigPath, "utf-8");
350
574
  const cjsReq = createCjsEvaluator(foundConfigPath, content);
351
575
  userConfig = cjsReq;
352
576
  } catch (innerErr) {
@@ -355,18 +579,19 @@ async function loadConfig(cwd = process.cwd(), customPath) {
355
579
  }
356
580
  }
357
581
  }
358
- let presetName = userConfig.preset;
359
- if (!presetName) {
360
- const isPython = import_node_fs.default.existsSync(import_node_path.default.resolve(cwd, "requirements.txt")) || import_node_fs.default.existsSync(import_node_path.default.resolve(cwd, "pyproject.toml")) || import_node_fs.default.existsSync(import_node_path.default.resolve(cwd, ".streamlit")) || import_node_fs.default.existsSync(import_node_path.default.resolve(cwd, "manage.py"));
361
- presetName = isPython ? "python" : "berserk";
362
- }
582
+ let presetName = userConfig.preset || detected.preset;
363
583
  let basePreset = berserkPreset;
364
584
  if (presetName === "python") {
365
585
  basePreset = pythonPreset;
366
586
  } else if (presetName === "recommended") {
367
587
  basePreset = defaultPreset;
368
588
  }
369
- return mergeConfigs(basePreset, userConfig);
589
+ const autonomousDefaults = {
590
+ include: detected.suggestedIncludes,
591
+ exclude: detected.suggestedIgnores
592
+ };
593
+ const withAutonomous = mergeConfigs(basePreset, autonomousDefaults);
594
+ return mergeConfigs(withAutonomous, userConfig);
370
595
  }
371
596
  function createCjsEvaluator(filePath, content) {
372
597
  if (content.includes("export default")) {
@@ -416,8 +641,8 @@ function mergeConfigs(base, overrides) {
416
641
  }
417
642
 
418
643
  // src/rules/runner.ts
419
- var import_node_fs2 = __toESM(require("fs"), 1);
420
- var import_node_path2 = __toESM(require("path"), 1);
644
+ var import_node_fs4 = __toESM(require("fs"), 1);
645
+ var import_node_path4 = __toESM(require("path"), 1);
421
646
  var import_fast_glob = __toESM(require("fast-glob"), 1);
422
647
 
423
648
  // src/parser/ast.ts
@@ -830,16 +1055,52 @@ function checkRawHtmlElements(tagName, attributes, pos, filePath, addViolation,
830
1055
  pos.line,
831
1056
  pos.col,
832
1057
  "no-raw-html-elements",
833
- `Raw browser default '<input type="checkbox">' detected. Use standard design system '<Checkbox>' or '<Toggle>'.`,
1058
+ `Raw browser default '<input type="checkbox">' detected. Never use unstyled browser checkboxes. Use standard design system '<Checkbox>' or '<Toggle>'.`,
1059
+ lineSnippet
1060
+ );
1061
+ } else if (typeVal === "radio") {
1062
+ addViolation(
1063
+ filePath,
1064
+ pos.line,
1065
+ pos.col,
1066
+ "no-raw-html-elements",
1067
+ `Raw browser default '<input type="radio">' detected. Never use unstyled browser radio buttons. Use standard design system '<RadioGroup>' or styled toggle.`,
1068
+ lineSnippet
1069
+ );
1070
+ } else if (typeVal === "range") {
1071
+ addViolation(
1072
+ filePath,
1073
+ pos.line,
1074
+ pos.col,
1075
+ "no-raw-html-elements",
1076
+ `Raw browser default '<input type="range">' detected. Never use unstyled browser sliders. Use standard design system '<Slider>'.`,
1077
+ lineSnippet
1078
+ );
1079
+ } else if (typeVal === "file") {
1080
+ addViolation(
1081
+ filePath,
1082
+ pos.line,
1083
+ pos.col,
1084
+ "no-raw-html-elements",
1085
+ `Raw browser default '<input type="file">' detected. Never display native unstyled file picker inputs. Use a custom styled dropzone or trigger.`,
1086
+ lineSnippet
1087
+ );
1088
+ } else if (typeVal === "color") {
1089
+ addViolation(
1090
+ filePath,
1091
+ pos.line,
1092
+ pos.col,
1093
+ "no-raw-html-elements",
1094
+ `Raw browser default '<input type="color">' detected. Never use native browser color pickers. Use a custom color picker component.`,
834
1095
  lineSnippet
835
1096
  );
836
- } else if (["text", "email", "search"].includes(typeVal)) {
1097
+ } else if (["text", "email", "search", "password", "number", "tel", "url"].includes(typeVal)) {
837
1098
  addViolation(
838
1099
  filePath,
839
1100
  pos.line,
840
1101
  pos.col,
841
1102
  "no-raw-html-elements",
842
- `Raw '<input type="${typeVal}">' element detected. Use standard design system '<Input>' component.`,
1103
+ `Raw browser default '<input type="${typeVal}">' element detected. Use standard design system '<Input>' component.`,
843
1104
  lineSnippet
844
1105
  );
845
1106
  }
@@ -1097,6 +1358,9 @@ function checkPseudoChips(tagName, attributes, pos, filePath, addViolation, line
1097
1358
  function checkElementClasses(node, tagName, attributes, pos, filePath, addViolation, sourceFile, config) {
1098
1359
  if (!attributes.className) return;
1099
1360
  const isButton = ["Button", "IconButton", "ButtonyIcon", "FloatingActionButton"].includes(tagName);
1361
+ const isChip = ["Chip", "Badge", "Tag", "StatusBadge"].includes(tagName);
1362
+ const isTab = ["Tab", "TabTrigger", "TabsTrigger"].includes(tagName);
1363
+ const isCompactElement = isButton || isChip || isTab;
1100
1364
  const isCheckbox = tagName === "Checkbox" || attributes.type?.initializer?.getText(sourceFile)?.includes("checkbox") || attributes.role?.initializer?.getText(sourceFile)?.includes("checkbox") || Boolean(attributes["aria-checked"]) || attributes.className && /\b(?:size-5|w-5\s+h-5)\b/.test(attributes.className.rawText);
1101
1365
  const lineSnippet = sourceFile.getFullText().split("\n")[pos.line - 1] || "";
1102
1366
  const classStrings = extractClassNames(attributes.className.initializer, sourceFile);
@@ -1136,8 +1400,33 @@ function checkElementClasses(node, tagName, attributes, pos, filePath, addViolat
1136
1400
  );
1137
1401
  }
1138
1402
  }
1403
+ if (isCompactElement && config.rules?.["no-wrapping-compact-elements"] !== "off") {
1404
+ if (dec.core === "flex-wrap" || dec.core === "whitespace-normal" || dec.core === "whitespace-pre-wrap") {
1405
+ addViolation(
1406
+ filePath,
1407
+ nodePos.line,
1408
+ nodePos.col,
1409
+ "no-wrapping-compact-elements",
1410
+ `Disallowed text wrapping class '${dec.core}' on <${tagName}>. Compact interactive elements (buttons, chips, badges, tabs) must never wrap text across multiple lines.`,
1411
+ lineSnippet
1412
+ );
1413
+ }
1414
+ }
1139
1415
  });
1140
1416
  });
1417
+ if (tagName === "TableHead" && config.rules?.["no-wrapping-compact-elements"] !== "off") {
1418
+ const rawClass = attributes.className?.rawText || "";
1419
+ if (/\b(?:flex-wrap|whitespace-normal|whitespace-pre-wrap)\b/.test(rawClass)) {
1420
+ addViolation(
1421
+ filePath,
1422
+ pos.line,
1423
+ pos.col,
1424
+ "no-wrapping-compact-elements",
1425
+ `Disallowed text wrapping on <TableHead>. Column headers must remain single-line ('whitespace-nowrap') to preserve table column alignment.`,
1426
+ lineSnippet
1427
+ );
1428
+ }
1429
+ }
1141
1430
  }
1142
1431
 
1143
1432
  // src/rules/regex-rules.ts
@@ -1358,8 +1647,8 @@ function runPythonRules(content, filePath, addViolation, config) {
1358
1647
 
1359
1648
  // src/rules/runner.ts
1360
1649
  async function runDseScan(config, cwd = process.cwd()) {
1361
- let targetDir = import_node_path2.default.resolve(cwd, config.targetDir || "src");
1362
- if (!import_node_fs2.default.existsSync(targetDir)) {
1650
+ let targetDir = import_node_path4.default.resolve(cwd, config.targetDir || "src");
1651
+ if (!import_node_fs4.default.existsSync(targetDir)) {
1363
1652
  targetDir = cwd;
1364
1653
  }
1365
1654
  const includePatterns = config.include || ["**/*.{tsx,jsx,ts,js}"];
@@ -1391,7 +1680,7 @@ async function runDseScan(config, cwd = process.cwd()) {
1391
1680
  const violations = [];
1392
1681
  const seenViolationKeys = /* @__PURE__ */ new Set();
1393
1682
  const addViolation = (file, line, col, rule, message, snippet) => {
1394
- const normFile = import_node_path2.default.relative(cwd, file).replace(/\\/g, "/");
1683
+ const normFile = import_node_path4.default.relative(cwd, file).replace(/\\/g, "/");
1395
1684
  const key = `${normFile}:${line}:${rule}`;
1396
1685
  if (seenViolationKeys.has(key)) return;
1397
1686
  seenViolationKeys.add(key);
@@ -1409,7 +1698,7 @@ async function runDseScan(config, cwd = process.cwd()) {
1409
1698
  };
1410
1699
  let scannedJsxFiles = false;
1411
1700
  for (const filePath of files) {
1412
- const content = import_node_fs2.default.readFileSync(filePath, "utf-8");
1701
+ const content = import_node_fs4.default.readFileSync(filePath, "utf-8");
1413
1702
  const isJsx = filePath.endsWith(".tsx") || filePath.endsWith(".jsx") || filePath.endsWith(".ts") || filePath.endsWith(".js");
1414
1703
  const isPythonOrTemplate = filePath.endsWith(".py") || filePath.endsWith(".html") || filePath.endsWith(".jinja") || filePath.endsWith(".jinja2") || filePath.endsWith(".css");
1415
1704
  if (isJsx) {
@@ -1530,8 +1819,8 @@ function formatJson(result) {
1530
1819
  }
1531
1820
 
1532
1821
  // src/scaffold/index.ts
1533
- var import_node_fs3 = __toESM(require("fs"), 1);
1534
- var import_node_path3 = __toESM(require("path"), 1);
1822
+ var import_node_fs5 = __toESM(require("fs"), 1);
1823
+ var import_node_path5 = __toESM(require("path"), 1);
1535
1824
 
1536
1825
  // src/scaffold/templates/tokens.ts
1537
1826
  var tokensTemplate = `/**
@@ -1674,7 +1963,8 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
1674
1963
  ({ className = "", variant = "primary", size = "default", children, ...props }, ref) => {
1675
1964
  // Standard 36px height, 8px radius, font-medium, strictly neutral palette
1676
1965
  const baseClasses =
1677
- "inline-flex items-center justify-center font-medium text-sm rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 disabled:pointer-events-none disabled:opacity-50 select-none";
1966
+ "inline-flex items-center justify-center font-medium text-sm rounded-md whitespace-nowrap shrink-0 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-400 disabled:pointer-events-none disabled:opacity-50 select-none cursor-pointer";
1967
+
1678
1968
 
1679
1969
  const variantClasses = {
1680
1970
  primary: "bg-neutral-900 text-white hover:bg-neutral-800 shadow-2xs",
@@ -1715,7 +2005,8 @@ export const Chip: React.FC<ChipProps> = ({
1715
2005
  }) => {
1716
2006
  // Standard 36px height, 8px radius, 14px typography
1717
2007
  const baseClasses =
1718
- "inline-flex items-center h-[36px] min-h-[36px] px-3 rounded-md text-sm font-medium gap-1.5 select-none";
2008
+ "inline-flex items-center h-[36px] min-h-[36px] px-3 rounded-md text-sm font-medium whitespace-nowrap shrink-0 gap-1.5 select-none";
2009
+
1719
2010
 
1720
2011
  const variantClasses = {
1721
2012
  neutral: "bg-neutral-100 text-neutral-800 border border-neutral-200",
@@ -1885,12 +2176,12 @@ export const TableHead = React.forwardRef<HTMLTableCellElement, TableHeadProps>(
1885
2176
  ({ className = "", icon, resizable = true, children, ...props }, ref) => (
1886
2177
  <th
1887
2178
  ref={ref}
1888
- className={\`h-[36px] px-4 text-left font-medium text-sm text-neutral-900 select-none align-middle relative \${className}\`}
2179
+ className={\`h-[36px] px-4 text-left font-medium text-sm text-neutral-900 select-none align-middle relative whitespace-nowrap \${className}\`}
1889
2180
  {...props}
1890
2181
  >
1891
2182
  <div className="flex items-center gap-2">
1892
2183
  {icon && <span className="text-neutral-500 shrink-0">{icon}</span>}
1893
- <span>{children}</span>
2184
+ <span className="whitespace-nowrap">{children}</span>
1894
2185
  </div>
1895
2186
  </th>
1896
2187
  )
@@ -1936,7 +2227,7 @@ export const Tabs: React.FC<TabsProps> = ({ items, activeId, onChange, className
1936
2227
  type="button"
1937
2228
  disabled={tab.disabled}
1938
2229
  onClick={() => onChange(tab.id)}
1939
- className={\`inline-flex h-[28px] items-center justify-center gap-1.5 rounded-sm px-3 text-sm font-medium transition-all select-none \${
2230
+ className={\`inline-flex h-[28px] items-center justify-center gap-1.5 rounded-sm px-3 text-sm font-medium whitespace-nowrap shrink-0 transition-all select-none \${
1940
2231
  isActive
1941
2232
  ? "bg-white text-neutral-900 shadow-2xs"
1942
2233
  : "text-neutral-600 hover:text-neutral-900 hover:bg-neutral-200/50"
@@ -2028,8 +2319,8 @@ var COMPONENT_TEMPLATES = {
2028
2319
  };
2029
2320
  function scaffoldConfig(cwd = process.cwd(), preset = "berserk") {
2030
2321
  if (preset === "python") {
2031
- const jsonPath = import_node_path3.default.resolve(cwd, "dse.config.json");
2032
- if (import_node_fs3.default.existsSync(jsonPath)) return jsonPath;
2322
+ const jsonPath = import_node_path5.default.resolve(cwd, "dse.config.json");
2323
+ if (import_node_fs5.default.existsSync(jsonPath)) return jsonPath;
2033
2324
  const content2 = JSON.stringify(
2034
2325
  {
2035
2326
  preset: "python",
@@ -2046,11 +2337,11 @@ function scaffoldConfig(cwd = process.cwd(), preset = "berserk") {
2046
2337
  null,
2047
2338
  2
2048
2339
  ) + "\n";
2049
- import_node_fs3.default.writeFileSync(jsonPath, content2, "utf-8");
2340
+ import_node_fs5.default.writeFileSync(jsonPath, content2, "utf-8");
2050
2341
  return jsonPath;
2051
2342
  }
2052
- const configPath = import_node_path3.default.resolve(cwd, "dse.config.js");
2053
- if (import_node_fs3.default.existsSync(configPath)) {
2343
+ const configPath = import_node_path5.default.resolve(cwd, "dse.config.js");
2344
+ if (import_node_fs5.default.existsSync(configPath)) {
2054
2345
  return configPath;
2055
2346
  }
2056
2347
  const content = `import { defineConfig } from "design-system-enforcer";
@@ -2078,21 +2369,21 @@ export default defineConfig({
2078
2369
  ],
2079
2370
  });
2080
2371
  `;
2081
- import_node_fs3.default.writeFileSync(configPath, content, "utf-8");
2372
+ import_node_fs5.default.writeFileSync(configPath, content, "utf-8");
2082
2373
  return configPath;
2083
2374
  }
2084
2375
  function scaffoldTokens(cwd = process.cwd()) {
2085
- const libDir = import_node_path3.default.resolve(cwd, "src/lib");
2086
- const appDir = import_node_path3.default.resolve(cwd, "src/app");
2087
- if (!import_node_fs3.default.existsSync(libDir)) import_node_fs3.default.mkdirSync(libDir, { recursive: true });
2088
- if (!import_node_fs3.default.existsSync(appDir)) import_node_fs3.default.mkdirSync(appDir, { recursive: true });
2089
- const tokensPath = import_node_path3.default.join(libDir, "tokens.ts");
2090
- if (!import_node_fs3.default.existsSync(tokensPath)) {
2091
- import_node_fs3.default.writeFileSync(tokensPath, tokensTemplate, "utf-8");
2092
- }
2093
- const cssPath = import_node_path3.default.join(appDir, "globals.css");
2094
- if (!import_node_fs3.default.existsSync(cssPath)) {
2095
- import_node_fs3.default.writeFileSync(cssPath, cssTemplate, "utf-8");
2376
+ const libDir = import_node_path5.default.resolve(cwd, "src/lib");
2377
+ const appDir = import_node_path5.default.resolve(cwd, "src/app");
2378
+ if (!import_node_fs5.default.existsSync(libDir)) import_node_fs5.default.mkdirSync(libDir, { recursive: true });
2379
+ if (!import_node_fs5.default.existsSync(appDir)) import_node_fs5.default.mkdirSync(appDir, { recursive: true });
2380
+ const tokensPath = import_node_path5.default.join(libDir, "tokens.ts");
2381
+ if (!import_node_fs5.default.existsSync(tokensPath)) {
2382
+ import_node_fs5.default.writeFileSync(tokensPath, tokensTemplate, "utf-8");
2383
+ }
2384
+ const cssPath = import_node_path5.default.join(appDir, "globals.css");
2385
+ if (!import_node_fs5.default.existsSync(cssPath)) {
2386
+ import_node_fs5.default.writeFileSync(cssPath, cssTemplate, "utf-8");
2096
2387
  }
2097
2388
  return { tokensPath, cssPath };
2098
2389
  }
@@ -2102,13 +2393,13 @@ function scaffoldComponent(componentName, cwd = process.cwd()) {
2102
2393
  if (!template) {
2103
2394
  return null;
2104
2395
  }
2105
- const uiDir = import_node_path3.default.resolve(cwd, "src/components/ui");
2106
- if (!import_node_fs3.default.existsSync(uiDir)) {
2107
- import_node_fs3.default.mkdirSync(uiDir, { recursive: true });
2396
+ const uiDir = import_node_path5.default.resolve(cwd, "src/components/ui");
2397
+ if (!import_node_fs5.default.existsSync(uiDir)) {
2398
+ import_node_fs5.default.mkdirSync(uiDir, { recursive: true });
2108
2399
  }
2109
- const targetPath = import_node_path3.default.join(uiDir, template.filename);
2110
- if (!import_node_fs3.default.existsSync(targetPath)) {
2111
- import_node_fs3.default.writeFileSync(targetPath, template.content, "utf-8");
2400
+ const targetPath = import_node_path5.default.join(uiDir, template.filename);
2401
+ if (!import_node_fs5.default.existsSync(targetPath)) {
2402
+ import_node_fs5.default.writeFileSync(targetPath, template.content, "utf-8");
2112
2403
  }
2113
2404
  return targetPath;
2114
2405
  }