UI Styling Skill Comprehensive skill for creating beautiful, accessible user interfaces combining shadcn/ui components, Tailwind CSS utility styling, and canvas-based visual design systems. Reference - shadcn/ui: https://ui.shadcn.com/llms.txt - Tailwind CSS: https://tailwindcss.com/docs When to Use This Skill Use when: - Building UI with React-based frameworks (Next.js, Vite, Remix, Astro) - Implementing accessible components (dialogs, forms, tables, navigation) - Styling with utility-first CSS approach - Creating responsive, mobile-first layouts - Implementing dark mode and theme customizat…

)\n_TAILWIND_CONFIG_NAMES = (\n \"tailwind.config.js\",\n \"tailwind.config.cjs\",\n \"tailwind.config.mjs\",\n \"tailwind.config.ts\",\n)\n\n\nclass TailwindConfigGenerator:\n \"\"\"Generate Tailwind CSS configuration files.\"\"\"\n\n def __init__(\n self,\n typescript: bool = True,\n framework: str = \"react\",\n output_path: Optional[Path] = None,\n force: bool = False,\n ):\n \"\"\"\n Initialize generator.\n\n Args:\n typescript: If True, generate .ts config, else .js\n framework: Framework name (react, vue, svelte, nextjs)\n output_path: Output file path (default: auto-detect)\n force: If True, allow replacing an existing output file\n \"\"\"\n self.typescript = typescript\n self.framework = framework\n self.output_path = output_path or self._default_output_path()\n self.force = force\n self.config: Dict[str, Any] = self._base_config()\n\n def _default_output_path(self) -> Path:\n \"\"\"Determine default output path.\"\"\"\n ext = \"ts\" if self.typescript else \"js\"\n return Path.cwd() / f\"tailwind.config.{ext}\"\n\n def _base_config(self) -> Dict[str, Any]:\n \"\"\"Create base configuration structure.\"\"\"\n return {\n \"darkMode\": [\"class\"],\n \"content\": self._default_content_paths(),\n \"theme\": {\n \"extend\": {}\n },\n \"plugins\": []\n }\n\n def _default_content_paths(self) -> List[str]:\n \"\"\"Get default content paths for framework.\"\"\"\n paths = {\n \"react\": [\n \"./src/**/*.{js,jsx,ts,tsx}\",\n \"./index.html\",\n ],\n \"vue\": [\n \"./src/**/*.{vue,js,ts,jsx,tsx}\",\n \"./index.html\",\n ],\n \"svelte\": [\n \"./src/**/*.{svelte,js,ts}\",\n \"./src/app.html\",\n ],\n \"nextjs\": [\n \"./app/**/*.{js,ts,jsx,tsx}\",\n \"./pages/**/*.{js,ts,jsx,tsx}\",\n \"./components/**/*.{js,ts,jsx,tsx}\",\n ],\n }\n return paths.get(self.framework, paths[\"react\"])\n\n def add_colors(self, colors: Dict[str, str]) -> None:\n \"\"\"\n Add custom colors to theme.\n\n Args:\n colors: Dict of color_name: color_value\n Value can be hex (#3b82f6) or variable (hsl(var(--primary)))\n \"\"\"\n if \"colors\" not in self.config[\"theme\"][\"extend\"]:\n self.config[\"theme\"][\"extend\"][\"colors\"] = {}\n\n self.config[\"theme\"][\"extend\"][\"colors\"].update(colors)\n\n def add_color_palette(self, name: str, base_color: str) -> None:\n \"\"\"\n Add full color palette (50-950 shades) for a base color.\n\n Args:\n name: Color name (e.g., 'brand', 'primary')\n base_color: Base color in oklch format or hex\n \"\"\"\n # For simplicity, use CSS variable approach\n if \"colors\" not in self.config[\"theme\"][\"extend\"]:\n self.config[\"theme\"][\"extend\"][\"colors\"] = {}\n\n self.config[\"theme\"][\"extend\"][\"colors\"][name] = {\n \"50\": f\"var(--color-{name}-50)\",\n \"100\": f\"var(--color-{name}-100)\",\n \"200\": f\"var(--color-{name}-200)\",\n \"300\": f\"var(--color-{name}-300)\",\n \"400\": f\"var(--color-{name}-400)\",\n \"500\": f\"var(--color-{name}-500)\",\n \"600\": f\"var(--color-{name}-600)\",\n \"700\": f\"var(--color-{name}-700)\",\n \"800\": f\"var(--color-{name}-800)\",\n \"900\": f\"var(--color-{name}-900)\",\n \"950\": f\"var(--color-{name}-950)\",\n }\n\n def add_fonts(self, fonts: Dict[str, List[str]]) -> None:\n \"\"\"\n Add custom font families.\n\n Args:\n fonts: Dict of font_type: [font_names]\n e.g., {'sans': ['Inter', 'system-ui', 'sans-serif']}\n \"\"\"\n if \"fontFamily\" not in self.config[\"theme\"][\"extend\"]:\n self.config[\"theme\"][\"extend\"][\"fontFamily\"] = {}\n\n self.config[\"theme\"][\"extend\"][\"fontFamily\"].update(fonts)\n\n def add_spacing(self, spacing: Dict[str, str]) -> None:\n \"\"\"\n Add custom spacing values.\n\n Args:\n spacing: Dict of name: value\n e.g., {'18': '4.5rem', 'navbar': '4rem'}\n \"\"\"\n if \"spacing\" not in self.config[\"theme\"][\"extend\"]:\n self.config[\"theme\"][\"extend\"][\"spacing\"] = {}\n\n self.config[\"theme\"][\"extend\"][\"spacing\"].update(spacing)\n\n def add_breakpoints(self, breakpoints: Dict[str, str]) -> None:\n \"\"\"\n Add custom breakpoints.\n\n Args:\n breakpoints: Dict of name: width\n e.g., {'3xl': '1920px', 'tablet': '768px'}\n \"\"\"\n if \"screens\" not in self.config[\"theme\"][\"extend\"]:\n self.config[\"theme\"][\"extend\"][\"screens\"] = {}\n\n self.config[\"theme\"][\"extend\"][\"screens\"].update(breakpoints)\n\n def add_plugins(self, plugins: List[str]) -> None:\n \"\"\"\n Add plugin requirements.\n\n Args:\n plugins: List of plugin names\n e.g., ['@tailwindcss/typography', '@tailwindcss/forms']\n \"\"\"\n for plugin in plugins:\n if plugin not in self.config[\"plugins\"]:\n self.config[\"plugins\"].append(plugin)\n\n def recommend_plugins(self) -> List[str]:\n \"\"\"\n Get plugin recommendations based on configuration.\n\n Returns:\n List of recommended plugin package names\n \"\"\"\n recommendations = []\n\n # Always recommend animation plugin\n recommendations.append(\"tailwindcss-animate\")\n\n # Framework-specific recommendations\n if self.framework == \"nextjs\":\n recommendations.append(\"@tailwindcss/typography\")\n\n return recommendations\n\n def generate_config_string(self) -> str:\n \"\"\"\n Generate configuration file content.\n\n Returns:\n Configuration file as string\n \"\"\"\n if self.typescript:\n return self._generate_typescript()\n return self._generate_javascript()\n\n def _generate_typescript(self) -> str:\n \"\"\"Generate TypeScript configuration.\"\"\"\n plugins_str = self._format_plugins()\n\n config_json = json.dumps(self.config, indent=2)\n\n # Remove plugin array from JSON (we'll add it with require())\n config_obj = self.config.copy()\n config_obj.pop(\"plugins\", None)\n config_json = json.dumps(config_obj, indent=2)\n\n return f\"\"\"import type {{ Config }} from 'tailwindcss'\n\nconst config: Config = {{\n{self._indent_json(config_json, 1)},\n plugins: [{plugins_str}],\n}}\n\nexport default config\n\"\"\"\n\n def _generate_javascript(self) -> str:\n \"\"\"Generate JavaScript configuration.\"\"\"\n plugins_str = self._format_plugins()\n\n config_obj = self.config.copy()\n config_obj.pop(\"plugins\", None)\n config_json = json.dumps(config_obj, indent=2)\n\n return f\"\"\"/** @type {{import('tailwindcss').Config}} */\nmodule.exports = {{\n{self._indent_json(config_json, 1)},\n plugins: [{plugins_str}],\n}}\n\"\"\"\n\n def _format_plugins(self) -> str:\n \"\"\"Format plugins array for config.\n\n Validates each plugin name against a strict allowlist pattern\n to prevent code injection via crafted require() statements\n (see: CWE-94).\n \"\"\"\n if not self.config[\"plugins\"]:\n return \"\"\n\n plugin_requires = []\n for plugin in self.config[\"plugins\"]:\n if not _VALID_PLUGIN_NAME.match(plugin):\n raise ValueError(\n f\"Invalid plugin name: {plugin!r}. \"\n \"Plugin names must be valid npm package names \"\n \"(e.g. '@tailwindcss/typography').\"\n )\n plugin_requires.append(f\"require('{plugin}')\")\n return \", \".join(plugin_requires)\n\n def _indent_json(self, json_str: str, level: int) -> str:\n \"\"\"Add indentation to JSON string.\"\"\"\n indent = \" \" * level\n lines = json_str.split(\"\\n\")\n # Skip first and last lines (braces)\n indented = [indent + line for line in lines[1:-1]]\n return \"\\n\".join(indented)\n\n def write_config(self) -> tuple[bool, str]:\n \"\"\"\n Write configuration to file.\n\n Returns:\n Tuple of (success, message)\n \"\"\"\n try:\n existing_paths = []\n if self.output_path.name in _TAILWIND_CONFIG_NAMES:\n existing_paths = [\n self.output_path.parent / name\n for name in _TAILWIND_CONFIG_NAMES\n if (self.output_path.parent / name).exists()\n ]\n elif self.output_path.exists():\n existing_paths = [self.output_path]\n\n if existing_paths and not self.force:\n existing = \", \".join(str(path) for path in existing_paths)\n return (\n False,\n f\"Tailwind configuration already exists: {existing}. \"\n \"Refusing to create or overwrite a competing config; \"\n \"re-run with --force only if this is intentional.\",\n )\n\n config_content = self.generate_config_string()\n\n self.output_path.write_text(config_content)\n\n return True, f\"Configuration written to {self.output_path}\"\n\n except OSError as e:\n return False, f\"Failed to write config: {e}\"\n\n def validate_config(self) -> tuple[bool, str]:\n \"\"\"\n Validate configuration.\n\n Returns:\n Tuple of (valid, message)\n \"\"\"\n # Check content paths exist\n if not self.config[\"content\"]:\n return False, \"No content paths specified\"\n\n # Check if extending empty theme\n if not self.config[\"theme\"][\"extend\"]:\n return True, \"Warning: No theme extensions defined\"\n\n return True, \"Configuration valid\"\n\n\ndef main():\n \"\"\"CLI entry point.\"\"\"\n parser = argparse.ArgumentParser(\n description=\"Generate Tailwind CSS configuration\",\n formatter_class=argparse.RawDescriptionHelpFormatter,\n epilog=\"\"\"\nExamples:\n # Generate TypeScript config for Next.js\n python tailwind_config_gen.py --framework nextjs\n\n # Generate JavaScript config with custom colors\n python tailwind_config_gen.py --js --colors brand:#3b82f6 accent:#8b5cf6\n\n # Add custom fonts\n python tailwind_config_gen.py --fonts display:\"Playfair Display,serif\"\n\n # Add custom spacing and breakpoints\n python tailwind_config_gen.py --spacing navbar:4rem --breakpoints 3xl:1920px\n\n # Add recommended plugins\n python tailwind_config_gen.py --plugins\n \"\"\",\n )\n\n parser.add_argument(\n \"--framework\",\n choices=[\"react\", \"vue\", \"svelte\", \"nextjs\"],\n default=\"react\",\n help=\"Target framework (default: react)\",\n )\n\n parser.add_argument(\n \"--js\",\n action=\"store_true\",\n help=\"Generate JavaScript config instead of TypeScript\",\n )\n\n parser.add_argument(\n \"--output\",\n type=Path,\n help=\"Output file path\",\n )\n\n parser.add_argument(\n \"--colors\",\n nargs=\"*\",\n metavar=\"NAME:VALUE\",\n help=\"Custom colors (e.g., brand:#3b82f6)\",\n )\n\n parser.add_argument(\n \"--fonts\",\n nargs=\"*\",\n metavar=\"TYPE:FAMILY\",\n help=\"Custom fonts (e.g., sans:'Inter,system-ui')\",\n )\n\n parser.add_argument(\n \"--spacing\",\n nargs=\"*\",\n metavar=\"NAME:VALUE\",\n help=\"Custom spacing (e.g., navbar:4rem)\",\n )\n\n parser.add_argument(\n \"--breakpoints\",\n nargs=\"*\",\n metavar=\"NAME:WIDTH\",\n help=\"Custom breakpoints (e.g., 3xl:1920px)\",\n )\n\n parser.add_argument(\n \"--plugins\",\n action=\"store_true\",\n help=\"Add recommended plugins\",\n )\n\n parser.add_argument(\n \"--validate-only\",\n action=\"store_true\",\n help=\"Validate config without writing file\",\n )\n\n parser.add_argument(\n \"--force\",\n action=\"store_true\",\n help=\"Overwrite an existing output file\",\n )\n\n args = parser.parse_args()\n\n # Initialize generator\n generator = TailwindConfigGenerator(\n typescript=not args.js,\n framework=args.framework,\n output_path=args.output,\n force=args.force,\n )\n\n # Add custom colors\n if args.colors:\n colors = {}\n for color_spec in args.colors:\n try:\n name, value = color_spec.split(\":\", 1)\n colors[name] = value\n except ValueError:\n print(f\"Invalid color spec: {color_spec}\", file=sys.stderr)\n sys.exit(1)\n generator.add_colors(colors)\n\n # Add custom fonts\n if args.fonts:\n fonts = {}\n for font_spec in args.fonts:\n try:\n font_type, family = font_spec.split(\":\", 1)\n fonts[font_type] = [f.strip().strip(\"'\\\"\") for f in family.split(\",\")]\n except ValueError:\n print(f\"Invalid font spec: {font_spec}\", file=sys.stderr)\n sys.exit(1)\n generator.add_fonts(fonts)\n\n # Add custom spacing\n if args.spacing:\n spacing = {}\n for spacing_spec in args.spacing:\n try:\n name, value = spacing_spec.split(\":\", 1)\n spacing[name] = value\n except ValueError:\n print(f\"Invalid spacing spec: {spacing_spec}\", file=sys.stderr)\n sys.exit(1)\n generator.add_spacing(spacing)\n\n # Add custom breakpoints\n if args.breakpoints:\n breakpoints = {}\n for bp_spec in args.breakpoints:\n try:\n name, width = bp_spec.split(\":\", 1)\n breakpoints[name] = width\n except ValueError:\n print(f\"Invalid breakpoint spec: {bp_spec}\", file=sys.stderr)\n sys.exit(1)\n generator.add_breakpoints(breakpoints)\n\n # Add recommended plugins\n if args.plugins:\n recommended = generator.recommend_plugins()\n generator.add_plugins(recommended)\n print(f\"Added recommended plugins: {', '.join(recommended)}\")\n print(\"\\nInstall with:\")\n print(f\" npm install -D {' '.join(recommended)}\")\n\n # Validate\n valid, message = generator.validate_config()\n if not valid:\n print(f\"Validation failed: {message}\", file=sys.stderr)\n sys.exit(1)\n\n if message.startswith(\"Warning\"):\n print(message)\n\n # Validate only mode\n if args.validate_only:\n print(\"Configuration valid\")\n print(\"\\nGenerated config:\")\n print(generator.generate_config_string())\n sys.exit(0)\n\n # Write config\n success, message = generator.write_config()\n print(message, file=sys.stdout if success else sys.stderr)\n sys.exit(0 if success else 1)\n\n\nif __name__ == \"__main__\":\n main()\n","content_type":"text/x-python; charset=utf-8","language":"python","size":15712,"content_sha256":"9a81a5e2780650ab613a87d50ffd5caa9be22533f7bac0f8807a7f7214f26471"},{"filename":"scripts/tests/coverage-ui.json","content":"{\"meta\": {\"format\": 3, \"version\": \"7.11.0\", \"timestamp\": \"2025-11-05T00:57:08.005243\", \"branch_coverage\": false, \"show_contexts\": false}, \"files\": {\"shadcn_add.py\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 14, 17, 18, 20, 28, 29, 30, 32, 39, 41, 48, 49, 51, 52, 53, 55, 58, 60, 63, 67, 80, 81, 83, 84, 90, 91, 93, 94, 101, 103, 104, 106, 107, 110, 111, 119, 120, 121, 123, 125, 126, 127, 128, 129, 131, 141, 142, 147, 149, 152, 153, 155, 156, 164, 165, 166, 168, 176, 183, 184, 186, 188, 189, 191, 194, 291], \"summary\": {\"covered_lines\": 70, \"num_statements\": 103, \"percent_covered\": 67.96116504854369, \"percent_covered_display\": \"68\", \"missing_lines\": 33, \"excluded_lines\": 0}, \"missing_lines\": [61, 64, 65, 150, 170, 171, 172, 173, 174, 196, 221, 227, 233, 239, 245, 251, 257, 260, 266, 267, 268, 269, 272, 273, 274, 275, 278, 279, 280, 282, 287, 288, 292], \"excluded_lines\": [], \"functions\": {\"ShadcnInstaller.__init__\": {\"executed_lines\": [28, 29, 30], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"ShadcnInstaller.check_shadcn_config\": {\"executed_lines\": [39], \"summary\": {\"covered_lines\": 1, \"num_statements\": 1, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"ShadcnInstaller.get_installed_components\": {\"executed_lines\": [48, 49, 51, 52, 53, 55, 58, 60, 63], \"summary\": {\"covered_lines\": 9, \"num_statements\": 12, \"percent_covered\": 75.0, \"percent_covered_display\": \"75\", \"missing_lines\": 3, \"excluded_lines\": 0}, \"missing_lines\": [61, 64, 65], \"excluded_lines\": []}, \"ShadcnInstaller.add_components\": {\"executed_lines\": [80, 81, 83, 84, 90, 91, 93, 94, 101, 103, 104, 106, 107, 110, 111, 119, 120, 121, 123, 125, 126, 127, 128, 129], \"summary\": {\"covered_lines\": 24, \"num_statements\": 24, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"ShadcnInstaller.add_all_components\": {\"executed_lines\": [141, 142, 147, 149, 152, 153, 155, 156, 164, 165, 166, 168], \"summary\": {\"covered_lines\": 12, \"num_statements\": 18, \"percent_covered\": 66.66666666666667, \"percent_covered_display\": \"67\", \"missing_lines\": 6, \"excluded_lines\": 0}, \"missing_lines\": [150, 170, 171, 172, 173, 174], \"excluded_lines\": []}, \"ShadcnInstaller.list_installed\": {\"executed_lines\": [183, 184, 186, 188, 189, 191], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"main\": {\"executed_lines\": [], \"summary\": {\"covered_lines\": 0, \"num_statements\": 23, \"percent_covered\": 0.0, \"percent_covered_display\": \"0\", \"missing_lines\": 23, \"excluded_lines\": 0}, \"missing_lines\": [196, 221, 227, 233, 239, 245, 251, 257, 260, 266, 267, 268, 269, 272, 273, 274, 275, 278, 279, 280, 282, 287, 288], \"excluded_lines\": []}, \"\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 14, 17, 18, 20, 32, 41, 67, 131, 176, 194, 291], \"summary\": {\"covered_lines\": 15, \"num_statements\": 16, \"percent_covered\": 93.75, \"percent_covered_display\": \"94\", \"missing_lines\": 1, \"excluded_lines\": 0}, \"missing_lines\": [292], \"excluded_lines\": []}}, \"classes\": {\"ShadcnInstaller\": {\"executed_lines\": [28, 29, 30, 39, 48, 49, 51, 52, 53, 55, 58, 60, 63, 80, 81, 83, 84, 90, 91, 93, 94, 101, 103, 104, 106, 107, 110, 111, 119, 120, 121, 123, 125, 126, 127, 128, 129, 141, 142, 147, 149, 152, 153, 155, 156, 164, 165, 166, 168, 183, 184, 186, 188, 189, 191], \"summary\": {\"covered_lines\": 55, \"num_statements\": 64, \"percent_covered\": 85.9375, \"percent_covered_display\": \"86\", \"missing_lines\": 9, \"excluded_lines\": 0}, \"missing_lines\": [61, 64, 65, 150, 170, 171, 172, 173, 174], \"excluded_lines\": []}, \"\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 14, 17, 18, 20, 32, 41, 67, 131, 176, 194, 291], \"summary\": {\"covered_lines\": 15, \"num_statements\": 39, \"percent_covered\": 38.46153846153846, \"percent_covered_display\": \"38\", \"missing_lines\": 24, \"excluded_lines\": 0}, \"missing_lines\": [196, 221, 227, 233, 239, 245, 251, 257, 260, 266, 267, 268, 269, 272, 273, 274, 275, 278, 279, 280, 282, 287, 288, 292], \"excluded_lines\": []}}}, \"tailwind_config_gen.py\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 16, 17, 19, 33, 34, 35, 36, 38, 40, 41, 43, 45, 54, 56, 75, 77, 85, 86, 88, 90, 99, 100, 102, 116, 124, 125, 127, 129, 137, 138, 140, 142, 150, 151, 153, 155, 163, 164, 165, 167, 174, 177, 180, 181, 183, 185, 192, 193, 194, 196, 198, 200, 203, 204, 205, 207, 217, 219, 221, 222, 223, 225, 232, 234, 235, 237, 240, 242, 244, 245, 247, 248, 250, 257, 258, 260, 262, 264, 265, 267, 275, 276, 279, 280, 285, 455], \"summary\": {\"covered_lines\": 90, \"num_statements\": 164, \"percent_covered\": 54.8780487804878, \"percent_covered_display\": \"55\", \"missing_lines\": 74, \"excluded_lines\": 0}, \"missing_lines\": [282, 287, 309, 316, 322, 328, 335, 342, 349, 356, 362, 368, 371, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 426, 427, 428, 429, 430, 431, 434, 435, 436, 437, 439, 440, 443, 444, 445, 446, 447, 450, 451, 452, 456], \"excluded_lines\": [], \"functions\": {\"TailwindConfigGenerator.__init__\": {\"executed_lines\": [33, 34, 35, 36], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._default_output_path\": {\"executed_lines\": [40, 41], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._base_config\": {\"executed_lines\": [45], \"summary\": {\"covered_lines\": 1, \"num_statements\": 1, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._default_content_paths\": {\"executed_lines\": [56, 75], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_colors\": {\"executed_lines\": [85, 86, 88], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_color_palette\": {\"executed_lines\": [99, 100, 102], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_fonts\": {\"executed_lines\": [124, 125, 127], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_spacing\": {\"executed_lines\": [137, 138, 140], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_breakpoints\": {\"executed_lines\": [150, 151, 153], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.add_plugins\": {\"executed_lines\": [163, 164, 165], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.recommend_plugins\": {\"executed_lines\": [174, 177, 180, 181, 183], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.generate_config_string\": {\"executed_lines\": [192, 193, 194], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._generate_typescript\": {\"executed_lines\": [198, 200, 203, 204, 205, 207], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._generate_javascript\": {\"executed_lines\": [219, 221, 222, 223, 225], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._format_plugins\": {\"executed_lines\": [234, 235, 237, 240], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator._indent_json\": {\"executed_lines\": [244, 245, 247, 248], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.write_config\": {\"executed_lines\": [257, 258, 260, 262, 264, 265], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TailwindConfigGenerator.validate_config\": {\"executed_lines\": [275, 276, 279, 280], \"summary\": {\"covered_lines\": 4, \"num_statements\": 5, \"percent_covered\": 80.0, \"percent_covered_display\": \"80\", \"missing_lines\": 1, \"excluded_lines\": 0}, \"missing_lines\": [282], \"excluded_lines\": []}, \"main\": {\"executed_lines\": [], \"summary\": {\"covered_lines\": 0, \"num_statements\": 72, \"percent_covered\": 0.0, \"percent_covered_display\": \"0\", \"missing_lines\": 72, \"excluded_lines\": 0}, \"missing_lines\": [287, 309, 316, 322, 328, 335, 342, 349, 356, 362, 368, 371, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 426, 427, 428, 429, 430, 431, 434, 435, 436, 437, 439, 440, 443, 444, 445, 446, 447, 450, 451, 452], \"excluded_lines\": []}, \"\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 16, 17, 19, 38, 43, 54, 77, 90, 116, 129, 142, 155, 167, 185, 196, 217, 232, 242, 250, 267, 285, 455], \"summary\": {\"covered_lines\": 26, \"num_statements\": 27, \"percent_covered\": 96.29629629629629, \"percent_covered_display\": \"96\", \"missing_lines\": 1, \"excluded_lines\": 0}, \"missing_lines\": [456], \"excluded_lines\": []}}, \"classes\": {\"TailwindConfigGenerator\": {\"executed_lines\": [33, 34, 35, 36, 40, 41, 45, 56, 75, 85, 86, 88, 99, 100, 102, 124, 125, 127, 137, 138, 140, 150, 151, 153, 163, 164, 165, 174, 177, 180, 181, 183, 192, 193, 194, 198, 200, 203, 204, 205, 207, 219, 221, 222, 223, 225, 234, 235, 237, 240, 244, 245, 247, 248, 257, 258, 260, 262, 264, 265, 275, 276, 279, 280], \"summary\": {\"covered_lines\": 64, \"num_statements\": 65, \"percent_covered\": 98.46153846153847, \"percent_covered_display\": \"98\", \"missing_lines\": 1, \"excluded_lines\": 0}, \"missing_lines\": [282], \"excluded_lines\": []}, \"\": {\"executed_lines\": [2, 9, 10, 11, 12, 13, 16, 17, 19, 38, 43, 54, 77, 90, 116, 129, 142, 155, 167, 185, 196, 217, 232, 242, 250, 267, 285, 455], \"summary\": {\"covered_lines\": 26, \"num_statements\": 99, \"percent_covered\": 26.262626262626263, \"percent_covered_display\": \"26\", \"missing_lines\": 73, \"excluded_lines\": 0}, \"missing_lines\": [287, 309, 316, 322, 328, 335, 342, 349, 356, 362, 368, 371, 378, 379, 380, 381, 382, 383, 384, 385, 386, 387, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 414, 415, 416, 417, 418, 419, 420, 421, 422, 423, 426, 427, 428, 429, 430, 431, 434, 435, 436, 437, 439, 440, 443, 444, 445, 446, 447, 450, 451, 452, 456], \"excluded_lines\": []}}}, \"tests/test_shadcn_add.py\": {\"executed_lines\": [1, 3, 4, 5, 6, 8, 11, 12, 14, 17, 18, 20, 21, 23, 24, 27, 28, 39, 40, 42, 44, 46, 47, 48, 50, 52, 53, 55, 57, 58, 60, 62, 63, 65, 67, 68, 70, 72, 73, 74, 76, 78, 81, 82, 84, 85, 87, 89, 91, 92, 93, 95, 97, 98, 100, 101, 103, 105, 106, 108, 109, 111, 113, 114, 116, 117, 119, 120, 121, 123, 125, 126, 128, 130, 131, 136, 138, 139, 140, 143, 144, 146, 148, 149, 151, 152, 153, 154, 156, 157, 159, 165, 166, 168, 169, 170, 171, 174, 175, 176, 177, 178, 180, 181, 183, 187, 188, 190, 191, 193, 194, 196, 198, 199, 201, 202, 204, 206, 207, 209, 210, 212, 214, 215, 217, 218, 219, 221, 222, 224, 229, 230, 232, 233, 236, 237, 239, 241, 242, 244, 245, 247, 249, 250, 252, 253, 255, 257, 258, 259, 261, 262, 264, 265, 266], \"summary\": {\"covered_lines\": 153, \"num_statements\": 153, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": [], \"functions\": {\"TestShadcnInstaller.temp_project\": {\"executed_lines\": [23, 24, 27, 28, 39, 40, 42], \"summary\": {\"covered_lines\": 7, \"num_statements\": 7, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_init_default_project_root\": {\"executed_lines\": [46, 47, 48], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_init_custom_project_root\": {\"executed_lines\": [52, 53], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_init_dry_run\": {\"executed_lines\": [57, 58], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_check_shadcn_config_exists\": {\"executed_lines\": [62, 63], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_check_shadcn_config_not_exists\": {\"executed_lines\": [67, 68], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_get_installed_components_empty\": {\"executed_lines\": [72, 73, 74], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_get_installed_components_with_files\": {\"executed_lines\": [78, 81, 82, 84, 85, 87], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_get_installed_components_no_config\": {\"executed_lines\": [91, 92, 93], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_no_components\": {\"executed_lines\": [97, 98, 100, 101], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_no_config\": {\"executed_lines\": [105, 106, 108, 109], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_already_installed\": {\"executed_lines\": [113, 114, 116, 117, 119, 120, 121], \"summary\": {\"covered_lines\": 7, \"num_statements\": 7, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_with_overwrite\": {\"executed_lines\": [125, 126, 128, 130, 131, 136, 138, 139, 140, 143, 144], \"summary\": {\"covered_lines\": 11, \"num_statements\": 11, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_dry_run\": {\"executed_lines\": [148, 149, 151, 152, 153, 154], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_success\": {\"executed_lines\": [159, 165, 166, 168, 169, 170, 171, 174, 175, 176, 177, 178], \"summary\": {\"covered_lines\": 12, \"num_statements\": 12, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_subprocess_error\": {\"executed_lines\": [183, 187, 188, 190, 191], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_components_npx_not_found\": {\"executed_lines\": [196, 198, 199, 201, 202], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_all_components_no_config\": {\"executed_lines\": [206, 207, 209, 210], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_all_components_dry_run\": {\"executed_lines\": [214, 215, 217, 218, 219], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_add_all_components_success\": {\"executed_lines\": [224, 229, 230, 232, 233, 236, 237], \"summary\": {\"covered_lines\": 7, \"num_statements\": 7, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_list_installed_no_config\": {\"executed_lines\": [241, 242, 244, 245], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_list_installed_empty\": {\"executed_lines\": [249, 250, 252, 253], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestShadcnInstaller.test_list_installed_with_components\": {\"executed_lines\": [257, 258, 259, 261, 262, 264, 265, 266], \"summary\": {\"covered_lines\": 8, \"num_statements\": 8, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"\": {\"executed_lines\": [1, 3, 4, 5, 6, 8, 11, 12, 14, 17, 18, 20, 21, 44, 50, 55, 60, 65, 70, 76, 89, 95, 103, 111, 123, 146, 156, 157, 180, 181, 193, 194, 204, 212, 221, 222, 239, 247, 255], \"summary\": {\"covered_lines\": 37, \"num_statements\": 37, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}}, \"classes\": {\"TestShadcnInstaller\": {\"executed_lines\": [23, 24, 27, 28, 39, 40, 42, 46, 47, 48, 52, 53, 57, 58, 62, 63, 67, 68, 72, 73, 74, 78, 81, 82, 84, 85, 87, 91, 92, 93, 97, 98, 100, 101, 105, 106, 108, 109, 113, 114, 116, 117, 119, 120, 121, 125, 126, 128, 130, 131, 136, 138, 139, 140, 143, 144, 148, 149, 151, 152, 153, 154, 159, 165, 166, 168, 169, 170, 171, 174, 175, 176, 177, 178, 183, 187, 188, 190, 191, 196, 198, 199, 201, 202, 206, 207, 209, 210, 214, 215, 217, 218, 219, 224, 229, 230, 232, 233, 236, 237, 241, 242, 244, 245, 249, 250, 252, 253, 257, 258, 259, 261, 262, 264, 265, 266], \"summary\": {\"covered_lines\": 116, \"num_statements\": 116, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"\": {\"executed_lines\": [1, 3, 4, 5, 6, 8, 11, 12, 14, 17, 18, 20, 21, 44, 50, 55, 60, 65, 70, 76, 89, 95, 103, 111, 123, 146, 156, 157, 180, 181, 193, 194, 204, 212, 221, 222, 239, 247, 255], \"summary\": {\"covered_lines\": 37, \"num_statements\": 37, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}}}, \"tests/test_tailwind_config_gen.py\": {\"executed_lines\": [1, 3, 5, 8, 9, 11, 14, 15, 17, 19, 20, 21, 23, 25, 26, 28, 30, 31, 32, 34, 36, 37, 39, 41, 42, 44, 46, 47, 48, 50, 52, 53, 55, 56, 57, 58, 59, 61, 63, 64, 66, 67, 69, 71, 72, 74, 75, 76, 78, 80, 81, 83, 85, 87, 88, 92, 94, 95, 96, 98, 100, 102, 103, 105, 106, 107, 109, 111, 112, 114, 116, 117, 118, 119, 120, 122, 124, 125, 129, 131, 132, 133, 135, 137, 138, 142, 144, 145, 146, 148, 150, 151, 155, 157, 158, 159, 161, 163, 164, 165, 167, 168, 170, 172, 173, 174, 176, 177, 179, 181, 182, 184, 185, 187, 189, 190, 192, 194, 196, 197, 199, 200, 201, 203, 205, 206, 208, 209, 211, 213, 214, 215, 217, 218, 220, 222, 223, 224, 226, 227, 229, 231, 232, 234, 236, 238, 239, 241, 243, 244, 246, 248, 251, 253, 254, 256, 258, 259, 261, 263, 264, 265, 267, 269, 270, 271, 273, 275, 276, 277, 279, 281, 283, 285, 286, 288, 290, 291, 298, 299, 300, 301, 302, 304, 305, 307, 310, 311, 312, 313, 314, 315, 317, 319, 320, 326, 327, 329, 330, 332, 334, 335, 336], \"summary\": {\"covered_lines\": 201, \"num_statements\": 201, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": [], \"functions\": {\"TestTailwindConfigGenerator.test_init_default_typescript\": {\"executed_lines\": [19, 20, 21], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_init_javascript\": {\"executed_lines\": [25, 26], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_init_framework\": {\"executed_lines\": [30, 31, 32], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_default_output_path_typescript\": {\"executed_lines\": [36, 37], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_default_output_path_javascript\": {\"executed_lines\": [41, 42], \"summary\": {\"covered_lines\": 2, \"num_statements\": 2, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_custom_output_path\": {\"executed_lines\": [46, 47, 48], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_base_config_structure\": {\"executed_lines\": [52, 53, 55, 56, 57, 58, 59], \"summary\": {\"covered_lines\": 7, \"num_statements\": 7, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_default_content_paths_react\": {\"executed_lines\": [63, 64, 66, 67], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_default_content_paths_nextjs\": {\"executed_lines\": [71, 72, 74, 75, 76], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_default_content_paths_vue\": {\"executed_lines\": [80, 81, 83], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_colors\": {\"executed_lines\": [87, 88, 92, 94, 95, 96], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_colors_multiple_times\": {\"executed_lines\": [100, 102, 103, 105, 106, 107], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_color_palette\": {\"executed_lines\": [111, 112, 114, 116, 117, 118, 119, 120], \"summary\": {\"covered_lines\": 8, \"num_statements\": 8, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_fonts\": {\"executed_lines\": [124, 125, 129, 131, 132, 133], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_spacing\": {\"executed_lines\": [137, 138, 142, 144, 145, 146], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_breakpoints\": {\"executed_lines\": [150, 151, 155, 157, 158, 159], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_plugins\": {\"executed_lines\": [163, 164, 165, 167, 168], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_add_plugins_no_duplicates\": {\"executed_lines\": [172, 173, 174, 176, 177], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_recommend_plugins\": {\"executed_lines\": [181, 182, 184, 185], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_recommend_plugins_nextjs\": {\"executed_lines\": [189, 190, 192], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_generate_typescript_config\": {\"executed_lines\": [196, 197, 199, 200, 201], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_generate_javascript_config\": {\"executed_lines\": [205, 206, 208, 209], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_generate_config_with_colors\": {\"executed_lines\": [213, 214, 215, 217, 218], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_generate_config_with_plugins\": {\"executed_lines\": [222, 223, 224, 226, 227], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_validate_config_valid\": {\"executed_lines\": [231, 232, 234], \"summary\": {\"covered_lines\": 3, \"num_statements\": 3, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_validate_config_no_content\": {\"executed_lines\": [238, 239, 241, 243, 244], \"summary\": {\"covered_lines\": 5, \"num_statements\": 5, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_validate_config_empty_theme\": {\"executed_lines\": [248, 251, 253, 254], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_write_config\": {\"executed_lines\": [258, 259, 261, 263, 264, 265], \"summary\": {\"covered_lines\": 6, \"num_statements\": 6, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_write_config_creates_content\": {\"executed_lines\": [269, 270, 271, 273, 275, 276, 277], \"summary\": {\"covered_lines\": 7, \"num_statements\": 7, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_write_config_invalid_path\": {\"executed_lines\": [281, 283, 285, 286], \"summary\": {\"covered_lines\": 4, \"num_statements\": 4, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_full_configuration_typescript\": {\"executed_lines\": [290, 291, 298, 299, 300, 301, 302, 304, 305, 307, 310, 311, 312, 313, 314, 315], \"summary\": {\"covered_lines\": 16, \"num_statements\": 16, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"TestTailwindConfigGenerator.test_full_configuration_javascript\": {\"executed_lines\": [319, 320, 326, 327, 329, 330, 332, 334, 335, 336], \"summary\": {\"covered_lines\": 10, \"num_statements\": 10, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"\": {\"executed_lines\": [1, 3, 5, 8, 9, 11, 14, 15, 17, 23, 28, 34, 39, 44, 50, 61, 69, 78, 85, 98, 109, 122, 135, 148, 161, 170, 179, 187, 194, 203, 211, 220, 229, 236, 246, 256, 267, 279, 288, 317], \"summary\": {\"covered_lines\": 38, \"num_statements\": 38, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}}, \"classes\": {\"TestTailwindConfigGenerator\": {\"executed_lines\": [19, 20, 21, 25, 26, 30, 31, 32, 36, 37, 41, 42, 46, 47, 48, 52, 53, 55, 56, 57, 58, 59, 63, 64, 66, 67, 71, 72, 74, 75, 76, 80, 81, 83, 87, 88, 92, 94, 95, 96, 100, 102, 103, 105, 106, 107, 111, 112, 114, 116, 117, 118, 119, 120, 124, 125, 129, 131, 132, 133, 137, 138, 142, 144, 145, 146, 150, 151, 155, 157, 158, 159, 163, 164, 165, 167, 168, 172, 173, 174, 176, 177, 181, 182, 184, 185, 189, 190, 192, 196, 197, 199, 200, 201, 205, 206, 208, 209, 213, 214, 215, 217, 218, 222, 223, 224, 226, 227, 231, 232, 234, 238, 239, 241, 243, 244, 248, 251, 253, 254, 258, 259, 261, 263, 264, 265, 269, 270, 271, 273, 275, 276, 277, 281, 283, 285, 286, 290, 291, 298, 299, 300, 301, 302, 304, 305, 307, 310, 311, 312, 313, 314, 315, 319, 320, 326, 327, 329, 330, 332, 334, 335, 336], \"summary\": {\"covered_lines\": 163, \"num_statements\": 163, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}, \"\": {\"executed_lines\": [1, 3, 5, 8, 9, 11, 14, 15, 17, 23, 28, 34, 39, 44, 50, 61, 69, 78, 85, 98, 109, 122, 135, 148, 161, 170, 179, 187, 194, 203, 211, 220, 229, 236, 246, 256, 267, 279, 288, 317], \"summary\": {\"covered_lines\": 38, \"num_statements\": 38, \"percent_covered\": 100.0, \"percent_covered_display\": \"100\", \"missing_lines\": 0, \"excluded_lines\": 0}, \"missing_lines\": [], \"excluded_lines\": []}}}}, \"totals\": {\"covered_lines\": 514, \"num_statements\": 621, \"percent_covered\": 82.76972624798712, \"percent_covered_display\": \"83\", \"missing_lines\": 107, \"excluded_lines\": 0}}","content_type":"application/json; charset=utf-8","language":"json","size":35121,"content_sha256":"33bdc1f5998db9a38cac8e6128a82711f8f8b65fbb3dcb0c1680ae260441cbce"},{"filename":"scripts/tests/requirements.txt","content":"pytest>=7.4.0\npytest-cov>=4.1.0\npytest-mock>=3.11.1\n","content_type":"text/plain; charset=utf-8","language":null,"size":52,"content_sha256":"80846c98ee02a7e9651ec4eee6709f840b785646b469aad5284ab7ab9b344f17"},{"filename":"scripts/tests/test_shadcn_add.py","content":"\"\"\"Tests for shadcn_add.py\"\"\"\n\nimport json\nimport subprocess\nfrom pathlib import Path\nfrom unittest.mock import MagicMock, mock_open, patch\n\nimport pytest\n\n# Add parent directory to path for imports\nimport sys\nsys.path.insert(0, str(Path(__file__).parent.parent))\n\nfrom shadcn_add import ShadcnInstaller\n\n\nclass TestShadcnInstaller:\n \"\"\"Test ShadcnInstaller class.\"\"\"\n\n @pytest.fixture\n def temp_project(self, tmp_path):\n \"\"\"Create temporary project structure.\"\"\"\n project_root = tmp_path / \"test-project\"\n project_root.mkdir()\n\n # Create components.json\n components_json = project_root / \"components.json\"\n components_json.write_text(\n json.dumps({\n \"style\": \"new-york\",\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\"\n }\n })\n )\n\n # Create components directory\n ui_dir = project_root / \"components\" / \"ui\"\n ui_dir.mkdir(parents=True)\n\n return project_root\n\n def test_init_default_project_root(self):\n \"\"\"Test initialization with default project root.\"\"\"\n installer = ShadcnInstaller()\n assert installer.project_root == Path.cwd()\n assert installer.dry_run is False\n\n def test_init_custom_project_root(self, tmp_path):\n \"\"\"Test initialization with custom project root.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n assert installer.project_root == tmp_path\n\n def test_init_dry_run(self):\n \"\"\"Test initialization with dry run mode.\"\"\"\n installer = ShadcnInstaller(dry_run=True)\n assert installer.dry_run is True\n\n def test_check_shadcn_config_exists(self, temp_project):\n \"\"\"Test checking for existing shadcn config.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project)\n assert installer.check_shadcn_config() is True\n\n def test_check_shadcn_config_not_exists(self, tmp_path):\n \"\"\"Test checking for non-existent shadcn config.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n assert installer.check_shadcn_config() is False\n\n def test_get_installed_components_empty(self, temp_project):\n \"\"\"Test getting installed components when none exist.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project)\n installed = installer.get_installed_components()\n assert installed == []\n\n def test_get_installed_components_with_files(self, temp_project):\n \"\"\"Test getting installed components when files exist.\"\"\"\n ui_dir = temp_project / \"components\" / \"ui\"\n\n # Create component files\n (ui_dir / \"button.tsx\").write_text(\"export const Button = () => {}\")\n (ui_dir / \"card.tsx\").write_text(\"export const Card = () => {}\")\n\n installer = ShadcnInstaller(project_root=temp_project)\n installed = installer.get_installed_components()\n\n assert sorted(installed) == [\"button\", \"card\"]\n\n def test_get_installed_components_no_config(self, tmp_path):\n \"\"\"Test getting installed components without config.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n installed = installer.get_installed_components()\n assert installed == []\n\n def test_add_components_no_components(self, temp_project):\n \"\"\"Test adding components with empty list.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_components([])\n\n assert success is False\n assert \"No components specified\" in message\n\n def test_add_components_no_config(self, tmp_path):\n \"\"\"Test adding components without shadcn config.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n success, message = installer.add_components([\"button\"])\n\n assert success is False\n assert \"not initialized\" in message\n\n def test_add_components_already_installed(self, temp_project):\n \"\"\"Test adding components that are already installed.\"\"\"\n ui_dir = temp_project / \"components\" / \"ui\"\n (ui_dir / \"button.tsx\").write_text(\"export const Button = () => {}\")\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_components([\"button\"])\n\n assert success is False\n assert \"already installed\" in message\n assert \"button\" in message\n\n def test_add_components_with_overwrite(self, temp_project):\n \"\"\"Test adding components with overwrite flag.\"\"\"\n ui_dir = temp_project / \"components\" / \"ui\"\n (ui_dir / \"button.tsx\").write_text(\"export const Button = () => {}\")\n\n installer = ShadcnInstaller(project_root=temp_project)\n\n with patch(\"subprocess.run\") as mock_run:\n mock_run.return_value = MagicMock(\n stdout=\"Component added successfully\",\n returncode=0\n )\n\n success, message = installer.add_components([\"button\"], overwrite=True)\n\n assert success is True\n assert \"Successfully added\" in message\n mock_run.assert_called_once()\n\n # Verify --overwrite flag was passed\n call_args = mock_run.call_args[0][0]\n assert \"--overwrite\" in call_args\n\n def test_add_components_dry_run(self, temp_project):\n \"\"\"Test adding components in dry run mode.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project, dry_run=True)\n success, message = installer.add_components([\"button\", \"card\"])\n\n assert success is True\n assert \"Would run:\" in message\n assert \"button\" in message\n assert \"card\" in message\n\n @patch(\"subprocess.run\")\n def test_add_components_success(self, mock_run, temp_project):\n \"\"\"Test successful component addition.\"\"\"\n mock_run.return_value = MagicMock(\n stdout=\"Components added successfully\",\n stderr=\"\",\n returncode=0\n )\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_components([\"button\", \"card\"])\n\n assert success is True\n assert \"Successfully added\" in message\n assert \"button\" in message\n assert \"card\" in message\n\n # Verify correct command was called\n mock_run.assert_called_once()\n call_args = mock_run.call_args[0][0]\n assert call_args[:3] == [\"npx\", \"[email protected]\", \"add\"]\n assert \"button\" in call_args\n assert \"card\" in call_args\n\n @patch(\"subprocess.run\")\n def test_add_components_subprocess_error(self, mock_run, temp_project):\n \"\"\"Test component addition with subprocess error.\"\"\"\n mock_run.side_effect = subprocess.CalledProcessError(\n 1, \"cmd\", stderr=\"Error occurred\"\n )\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_components([\"button\"])\n\n assert success is False\n assert \"Failed to add\" in message\n\n @patch(\"subprocess.run\")\n def test_add_components_npx_not_found(self, mock_run, temp_project):\n \"\"\"Test component addition when npx is not found.\"\"\"\n mock_run.side_effect = FileNotFoundError()\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_components([\"button\"])\n\n assert success is False\n assert \"npx not found\" in message\n\n def test_add_all_components_no_config(self, tmp_path):\n \"\"\"Test adding all components without config.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n success, message = installer.add_all_components()\n\n assert success is False\n assert \"not initialized\" in message\n\n def test_add_all_components_dry_run(self, temp_project):\n \"\"\"Test adding all components in dry run mode.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project, dry_run=True)\n success, message = installer.add_all_components()\n\n assert success is True\n assert \"Would run:\" in message\n assert \"--all\" in message\n\n @patch(\"subprocess.run\")\n def test_add_all_components_success(self, mock_run, temp_project):\n \"\"\"Test successful addition of all components.\"\"\"\n mock_run.return_value = MagicMock(\n stdout=\"All components added\",\n returncode=0\n )\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.add_all_components()\n\n assert success is True\n assert \"Successfully added all\" in message\n\n # Verify --all flag was passed\n call_args = mock_run.call_args[0][0]\n assert \"--all\" in call_args\n\n def test_list_installed_no_config(self, tmp_path):\n \"\"\"Test listing installed components without config.\"\"\"\n installer = ShadcnInstaller(project_root=tmp_path)\n success, message = installer.list_installed()\n\n assert success is False\n assert \"not initialized\" in message\n\n def test_list_installed_empty(self, temp_project):\n \"\"\"Test listing installed components when none exist.\"\"\"\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.list_installed()\n\n assert success is True\n assert \"No components installed\" in message\n\n def test_list_installed_with_components(self, temp_project):\n \"\"\"Test listing installed components when they exist.\"\"\"\n ui_dir = temp_project / \"components\" / \"ui\"\n (ui_dir / \"button.tsx\").write_text(\"export const Button = () => {}\")\n (ui_dir / \"card.tsx\").write_text(\"export const Card = () => {}\")\n\n installer = ShadcnInstaller(project_root=temp_project)\n success, message = installer.list_installed()\n\n assert success is True\n assert \"button\" in message\n assert \"card\" in message\n","content_type":"text/x-python; charset=utf-8","language":"python","size":9919,"content_sha256":"35f60ac5d74c4a7f950854a0337883c3e8dd812c58fcf6c5153876e43a6effa2"},{"filename":"scripts/tests/test_tailwind_config_gen.py","content":"\"\"\"Tests for tailwind_config_gen.py\"\"\"\n\nimport shutil\nimport subprocess\nfrom pathlib import Path\n\nimport pytest\n\n# Add parent directory to path for imports\nimport sys\nsys.path.insert(0, str(Path(__file__).parent.parent))\n\nfrom tailwind_config_gen import TailwindConfigGenerator\n\n\nclass TestTailwindConfigGenerator:\n \"\"\"Test TailwindConfigGenerator class.\"\"\"\n\n def test_init_default_typescript(self):\n \"\"\"Test initialization with default settings.\"\"\"\n generator = TailwindConfigGenerator()\n assert generator.typescript is True\n assert generator.framework == \"react\"\n\n def test_init_javascript(self):\n \"\"\"Test initialization for JavaScript config.\"\"\"\n generator = TailwindConfigGenerator(typescript=False)\n assert generator.typescript is False\n\n def test_init_framework(self):\n \"\"\"Test initialization with different frameworks.\"\"\"\n for framework in [\"react\", \"vue\", \"svelte\", \"nextjs\"]:\n generator = TailwindConfigGenerator(framework=framework)\n assert generator.framework == framework\n\n def test_default_output_path_typescript(self):\n \"\"\"Test default output path for TypeScript.\"\"\"\n generator = TailwindConfigGenerator(typescript=True)\n assert generator.output_path.name == \"tailwind.config.ts\"\n\n def test_default_output_path_javascript(self):\n \"\"\"Test default output path for JavaScript.\"\"\"\n generator = TailwindConfigGenerator(typescript=False)\n assert generator.output_path.name == \"tailwind.config.js\"\n\n def test_custom_output_path(self, tmp_path):\n \"\"\"Test custom output path.\"\"\"\n custom_path = tmp_path / \"custom-config.ts\"\n generator = TailwindConfigGenerator(output_path=custom_path)\n assert generator.output_path == custom_path\n\n def test_base_config_structure(self):\n \"\"\"Test base configuration structure.\"\"\"\n generator = TailwindConfigGenerator()\n config = generator.config\n\n assert \"darkMode\" in config\n assert \"content\" in config\n assert \"theme\" in config\n assert \"plugins\" in config\n assert \"extend\" in config[\"theme\"]\n\n def test_default_content_paths_react(self):\n \"\"\"Test default content paths for React.\"\"\"\n generator = TailwindConfigGenerator(framework=\"react\")\n paths = generator.config[\"content\"]\n\n assert any(\"src/**/*.{js,jsx,ts,tsx}\" in p for p in paths)\n assert any(\"index.html\" in p for p in paths)\n\n def test_default_content_paths_nextjs(self):\n \"\"\"Test default content paths for Next.js.\"\"\"\n generator = TailwindConfigGenerator(framework=\"nextjs\")\n paths = generator.config[\"content\"]\n\n assert any(\"app/**\" in p for p in paths)\n assert any(\"pages/**\" in p for p in paths)\n assert any(\"components/**\" in p for p in paths)\n\n def test_default_content_paths_vue(self):\n \"\"\"Test default content paths for Vue.\"\"\"\n generator = TailwindConfigGenerator(framework=\"vue\")\n paths = generator.config[\"content\"]\n\n assert any(\"vue\" in p for p in paths)\n\n def test_add_colors(self):\n \"\"\"Test adding custom colors.\"\"\"\n generator = TailwindConfigGenerator()\n colors = {\n \"brand\": \"#3b82f6\",\n \"accent\": \"#8b5cf6\"\n }\n generator.add_colors(colors)\n\n assert \"colors\" in generator.config[\"theme\"][\"extend\"]\n assert generator.config[\"theme\"][\"extend\"][\"colors\"][\"brand\"] == \"#3b82f6\"\n assert generator.config[\"theme\"][\"extend\"][\"colors\"][\"accent\"] == \"#8b5cf6\"\n\n def test_add_colors_multiple_times(self):\n \"\"\"Test adding colors multiple times.\"\"\"\n generator = TailwindConfigGenerator()\n\n generator.add_colors({\"brand\": \"#3b82f6\"})\n generator.add_colors({\"accent\": \"#8b5cf6\"})\n\n colors = generator.config[\"theme\"][\"extend\"][\"colors\"]\n assert \"brand\" in colors\n assert \"accent\" in colors\n\n def test_add_color_palette(self):\n \"\"\"Test adding full color palette.\"\"\"\n generator = TailwindConfigGenerator()\n generator.add_color_palette(\"brand\", \"#3b82f6\")\n\n brand = generator.config[\"theme\"][\"extend\"][\"colors\"][\"brand\"]\n\n assert isinstance(brand, dict)\n assert \"50\" in brand\n assert \"500\" in brand\n assert \"950\" in brand\n assert \"var(--color-brand\" in brand[\"500\"]\n\n def test_add_fonts(self):\n \"\"\"Test adding custom fonts.\"\"\"\n generator = TailwindConfigGenerator()\n fonts = {\n \"sans\": [\"Inter\", \"system-ui\", \"sans-serif\"],\n \"display\": [\"Playfair Display\", \"serif\"]\n }\n generator.add_fonts(fonts)\n\n font_family = generator.config[\"theme\"][\"extend\"][\"fontFamily\"]\n assert font_family[\"sans\"] == [\"Inter\", \"system-ui\", \"sans-serif\"]\n assert font_family[\"display\"] == [\"Playfair Display\", \"serif\"]\n\n def test_add_spacing(self):\n \"\"\"Test adding custom spacing.\"\"\"\n generator = TailwindConfigGenerator()\n spacing = {\n \"18\": \"4.5rem\",\n \"navbar\": \"4rem\"\n }\n generator.add_spacing(spacing)\n\n spacing_config = generator.config[\"theme\"][\"extend\"][\"spacing\"]\n assert spacing_config[\"18\"] == \"4.5rem\"\n assert spacing_config[\"navbar\"] == \"4rem\"\n\n def test_add_breakpoints(self):\n \"\"\"Test adding custom breakpoints.\"\"\"\n generator = TailwindConfigGenerator()\n breakpoints = {\n \"3xl\": \"1920px\",\n \"tablet\": \"768px\"\n }\n generator.add_breakpoints(breakpoints)\n\n screens = generator.config[\"theme\"][\"extend\"][\"screens\"]\n assert screens[\"3xl\"] == \"1920px\"\n assert screens[\"tablet\"] == \"768px\"\n\n def test_add_plugins(self):\n \"\"\"Test adding plugins.\"\"\"\n generator = TailwindConfigGenerator()\n plugins = [\"@tailwindcss/typography\", \"@tailwindcss/forms\"]\n generator.add_plugins(plugins)\n\n assert \"@tailwindcss/typography\" in generator.config[\"plugins\"]\n assert \"@tailwindcss/forms\" in generator.config[\"plugins\"]\n\n def test_add_plugins_no_duplicates(self):\n \"\"\"Test that adding same plugin twice doesn't duplicate.\"\"\"\n generator = TailwindConfigGenerator()\n generator.add_plugins([\"@tailwindcss/typography\"])\n generator.add_plugins([\"@tailwindcss/typography\"])\n\n count = generator.config[\"plugins\"].count(\"@tailwindcss/typography\")\n assert count == 1\n\n def test_recommend_plugins(self):\n \"\"\"Test plugin recommendations.\"\"\"\n generator = TailwindConfigGenerator()\n recommendations = generator.recommend_plugins()\n\n assert isinstance(recommendations, list)\n assert \"tailwindcss-animate\" in recommendations\n\n def test_recommend_plugins_nextjs(self):\n \"\"\"Test plugin recommendations for Next.js.\"\"\"\n generator = TailwindConfigGenerator(framework=\"nextjs\")\n recommendations = generator.recommend_plugins()\n\n assert \"@tailwindcss/typography\" in recommendations\n\n def test_generate_typescript_config(self):\n \"\"\"Test generating TypeScript configuration.\"\"\"\n generator = TailwindConfigGenerator(typescript=True)\n config = generator.generate_config_string()\n\n assert \"import type { Config } from 'tailwindcss'\" in config\n assert \"const config: Config\" in config\n assert \"export default config\" in config\n\n def test_generate_javascript_config(self):\n \"\"\"Test generating JavaScript configuration.\"\"\"\n generator = TailwindConfigGenerator(typescript=False)\n config = generator.generate_config_string()\n\n assert \"module.exports\" in config\n assert \"@type\" in config\n\n def test_generate_config_with_colors(self):\n \"\"\"Test generating config with custom colors.\"\"\"\n generator = TailwindConfigGenerator()\n generator.add_colors({\"brand\": \"#3b82f6\"})\n config = generator.generate_config_string()\n\n assert \"colors\" in config\n assert \"brand\" in config\n\n def test_generate_config_with_plugins(self):\n \"\"\"Test generating config with plugins.\"\"\"\n generator = TailwindConfigGenerator()\n generator.add_plugins([\"tailwindcss-animate\"])\n config = generator.generate_config_string()\n\n assert \"plugins:\" in config\n assert \"require('tailwindcss-animate')\" in config\n\n def test_validate_config_valid(self):\n \"\"\"Test validating valid configuration.\"\"\"\n generator = TailwindConfigGenerator()\n valid, message = generator.validate_config()\n\n assert valid is True\n\n def test_validate_config_no_content(self):\n \"\"\"Test validating config with no content paths.\"\"\"\n generator = TailwindConfigGenerator()\n generator.config[\"content\"] = []\n\n valid, message = generator.validate_config()\n\n assert valid is False\n assert \"No content paths\" in message\n\n def test_validate_config_empty_theme(self):\n \"\"\"Test validating config with empty theme extensions.\"\"\"\n generator = TailwindConfigGenerator()\n # Default has empty theme.extend\n\n valid, message = generator.validate_config()\n\n assert valid is True\n assert \"Warning\" in message\n\n def test_write_config(self, tmp_path):\n \"\"\"Test writing configuration to file.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n generator = TailwindConfigGenerator(output_path=output_path)\n\n success, message = generator.write_config()\n\n assert success is True\n assert output_path.exists()\n assert \"written to\" in message\n\n def test_write_config_creates_content(self, tmp_path):\n \"\"\"Test that written config contains expected content.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n generator = TailwindConfigGenerator(output_path=output_path)\n generator.add_colors({\"brand\": \"#3b82f6\"})\n\n generator.write_config()\n\n content = output_path.read_text()\n assert \"import type { Config }\" in content\n assert \"brand\" in content\n\n def test_write_config_refuses_to_overwrite_existing_file(self, tmp_path):\n \"\"\"Existing project configuration must be preserved by default.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n existing = \"// existing project config\\nexport default { theme: {} }\\n\"\n output_path.write_text(existing)\n generator = TailwindConfigGenerator(output_path=output_path)\n\n success, message = generator.write_config()\n\n assert success is False\n assert \"already exists\" in message\n assert \"--force\" in message\n assert output_path.read_text() == existing\n\n def test_write_config_force_overwrites_existing_file(self, tmp_path):\n \"\"\"An explicit force opt-in permits replacing an existing config.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n output_path.write_text(\"// existing project config\\n\")\n generator = TailwindConfigGenerator(output_path=output_path, force=True)\n generator.add_colors({\"brand\": \"#3b82f6\"})\n\n success, message = generator.write_config()\n\n assert success is True\n assert \"written to\" in message\n assert \"brand\" in output_path.read_text()\n\n def test_cli_refuses_existing_config_without_force(self, tmp_path):\n \"\"\"The CLI must return non-zero and preserve an existing default target.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n existing = \"// existing project config\\n\"\n output_path.write_text(existing)\n script = Path(__file__).parent.parent / \"tailwind_config_gen.py\"\n\n result = subprocess.run(\n [sys.executable, str(script), \"--colors\", \"brand:#3b82f6\"],\n cwd=tmp_path,\n capture_output=True,\n text=True,\n )\n\n assert result.returncode == 1\n assert \"already exists\" in result.stderr\n assert \"--force\" in result.stderr\n assert output_path.read_text() == existing\n\n def test_cli_refuses_sibling_config_extension_without_force(self, tmp_path):\n \"\"\"A default .ts write must not create a second config beside .js.\"\"\"\n existing_path = tmp_path / \"tailwind.config.js\"\n existing = \"// existing JavaScript project config\\nmodule.exports = {}\\n\"\n existing_path.write_text(existing)\n output_path = tmp_path / \"tailwind.config.ts\"\n script = Path(__file__).parent.parent / \"tailwind_config_gen.py\"\n\n result = subprocess.run(\n [sys.executable, str(script), \"--colors\", \"brand:#3b82f6\"],\n cwd=tmp_path,\n capture_output=True,\n text=True,\n )\n\n assert result.returncode == 1\n assert \"tailwind.config.js\" in result.stderr\n assert \"--force\" in result.stderr\n assert existing_path.read_text() == existing\n assert not output_path.exists()\n\n def test_cli_force_allows_target_beside_sibling_config(self, tmp_path):\n \"\"\"The explicit force opt-in also overrides cross-extension detection.\"\"\"\n existing_path = tmp_path / \"tailwind.config.js\"\n existing = \"// existing JavaScript project config\\nmodule.exports = {}\\n\"\n existing_path.write_text(existing)\n output_path = tmp_path / \"tailwind.config.ts\"\n script = Path(__file__).parent.parent / \"tailwind_config_gen.py\"\n\n result = subprocess.run(\n [\n sys.executable,\n str(script),\n \"--colors\",\n \"brand:#3b82f6\",\n \"--force\",\n ],\n cwd=tmp_path,\n capture_output=True,\n text=True,\n )\n\n assert result.returncode == 0, result.stderr\n assert existing_path.read_text() == existing\n assert \"brand\" in output_path.read_text()\n\n def test_cli_force_overwrites_existing_config(self, tmp_path):\n \"\"\"The CLI must wire --force through to the generator.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n output_path.write_text(\"// existing project config\\n\")\n script = Path(__file__).parent.parent / \"tailwind_config_gen.py\"\n\n result = subprocess.run(\n [\n sys.executable,\n str(script),\n \"--colors\",\n \"brand:#3b82f6\",\n \"--force\",\n ],\n cwd=tmp_path,\n capture_output=True,\n text=True,\n )\n\n assert result.returncode == 0, result.stderr\n assert \"brand\" in output_path.read_text()\n\n def test_write_config_invalid_path(self):\n \"\"\"Test writing config to invalid path.\"\"\"\n generator = TailwindConfigGenerator(output_path=Path(\"/invalid/path/config.ts\"))\n\n success, message = generator.write_config()\n\n assert success is False\n assert \"Failed to write\" in message\n\n def test_full_configuration_typescript(self, tmp_path):\n \"\"\"Test generating complete TypeScript configuration.\"\"\"\n output_path = tmp_path / \"tailwind.config.ts\"\n generator = TailwindConfigGenerator(\n typescript=True,\n framework=\"nextjs\",\n output_path=output_path\n )\n\n # Add various customizations\n generator.add_colors({\"brand\": \"#3b82f6\", \"accent\": \"#8b5cf6\"})\n generator.add_fonts({\"sans\": [\"Inter\", \"sans-serif\"]})\n generator.add_spacing({\"navbar\": \"4rem\"})\n generator.add_breakpoints({\"3xl\": \"1920px\"})\n generator.add_plugins([\"tailwindcss-animate\"])\n\n success, _ = generator.write_config()\n assert success is True\n\n content = output_path.read_text()\n\n # Verify all customizations are present\n assert \"brand\" in content\n assert \"accent\" in content\n assert \"Inter\" in content\n assert \"navbar\" in content\n assert \"3xl\" in content\n assert \"tailwindcss-animate\" in content\n\n def test_full_configuration_javascript(self, tmp_path):\n \"\"\"Test generating complete JavaScript configuration.\"\"\"\n output_path = tmp_path / \"tailwind.config.js\"\n generator = TailwindConfigGenerator(\n typescript=False,\n framework=\"react\",\n output_path=output_path\n )\n\n generator.add_colors({\"primary\": \"#3b82f6\"})\n generator.add_plugins([\"@tailwindcss/forms\"])\n\n success, _ = generator.write_config()\n assert success is True\n\n content = output_path.read_text()\n\n assert \"module.exports\" in content\n assert \"primary\" in content\n assert \"@tailwindcss/forms\" in content\n\n\ndef _strip_to_object(config_str: str) -> str:\n \"\"\"Reduce a generated TS/JS config to a bare assignable object so it can be\n handed to `node --check` without a TypeScript loader.\"\"\"\n lines = []\n for line in config_str.splitlines():\n if line.startswith(\"import type\"):\n continue\n if line.strip() == \"export default config\":\n continue\n line = line.replace(\"const config: Config =\", \"const config =\")\n line = line.replace(\"module.exports =\", \"const config =\")\n lines.append(line)\n return \"\\n\".join(lines)\n\n\nclass TestGeneratedConfigIsValidJs:\n \"\"\"Regression guard for the missing-comma bug between the ``theme`` block and\n ``plugins`` that produced syntactically invalid config files. The data-shape\n tests above all passed while the emitted string was unparseable, so these\n tests validate the serialized output itself.\"\"\"\n\n @pytest.mark.parametrize(\"typescript\", [True, False])\n def test_property_before_plugins_is_comma_terminated(self, typescript):\n \"\"\"The property preceding ``plugins`` must end with a comma (pure-Python\n check, so the regression is caught even where node is unavailable).\"\"\"\n generator = TailwindConfigGenerator(typescript=typescript)\n generator.add_colors({\"brand\": \"#6366F1\"})\n generator.add_breakpoints({\"3xl\": \"1920px\"})\n config = generator.generate_config_string()\n\n assert \"}\\n plugins:\" not in config, \"missing comma before plugins\"\n assert \"},\\n plugins:\" in config\n\n @pytest.mark.parametrize(\"typescript\", [True, False])\n def test_node_check_parses_generated_config(self, typescript, tmp_path):\n \"\"\"The emitted config parses as valid JS via ``node --check``.\"\"\"\n node = shutil.which(\"node\")\n if not node:\n pytest.skip(\"node not available\")\n\n generator = TailwindConfigGenerator(typescript=typescript)\n generator.add_colors({\"brand\": \"#6366F1\", \"accent\": \"#10B981\"})\n generator.add_fonts({\"sans\": [\"Inter\"]})\n generator.add_breakpoints({\"3xl\": \"1920px\"})\n generator.add_plugins([\"tailwindcss-animate\"])\n\n snippet = _strip_to_object(generator.generate_config_string())\n path = tmp_path / \"config.cjs\"\n path.write_text(snippet)\n\n result = subprocess.run(\n [node, \"--check\", str(path)], capture_output=True, text=True\n )\n assert result.returncode == 0, result.stderr\n","content_type":"text/x-python; charset=utf-8","language":"python","size":19154,"content_sha256":"2d044e1a9e67d358e8f7f664eeaa588dbd0ef150daa91cdea71519d8d686e470"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"UI Styling Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Comprehensive skill for creating beautiful, accessible user interfaces combining shadcn/ui components, Tailwind CSS utility styling, and canvas-based visual design systems.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"shadcn/ui: https://ui.shadcn.com/llms.txt","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tailwind CSS: https://tailwindcss.com/docs","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use when:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building UI with React-based frameworks (Next.js, Vite, Remix, Astro)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implementing accessible components (dialogs, forms, tables, navigation)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Styling with utility-first CSS approach","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Creating responsive, mobile-first layouts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Implementing dark mode and theme customization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Building design systems with consistent tokens","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Generating visual designs, posters, or brand materials","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rapid prototyping with immediate visual feedback","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Adding complex UI patterns (data tables, charts, command palettes)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Stack","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Component Layer: shadcn/ui","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Pre-built accessible components via Radix UI primitives","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Copy-paste distribution model (components live in your codebase)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TypeScript-first with full type safety","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Composable primitives for complex UIs","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CLI-based installation and management","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Styling Layer: Tailwind CSS","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Utility-first CSS framework","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Build-time processing with zero runtime overhead","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mobile-first responsive design","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Consistent design tokens (colors, spacing, typography)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Automatic dead code elimination","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Visual Design Layer: Canvas","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Museum-quality visual compositions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Philosophy-driven design approach","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sophisticated visual communication","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Minimal text, maximum visual impact","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Systematic patterns and refined aesthetics","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Script Paths","type":"text"}]},{"type":"paragraph","content":[{"text":"Script paths in this skill and its ","type":"text"},{"text":"references/","type":"text","marks":[{"type":"code_inline"}]},{"text":" are relative to the directory that contains this SKILL.md, not to the project: ","type":"text"},{"text":"scripts/\u003cfile>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is this skill's own ","type":"text"},{"text":"scripts/","type":"text","marks":[{"type":"code_inline"}]},{"text":" folder, and ","type":"text"},{"text":"../\u003cskill>/scripts/\u003cfile>","type":"text","marks":[{"type":"code_inline"}]},{"text":" is a sibling sub-skill installed alongside it. Build the full path from that directory (Claude Code reports it as the skill's base directory when the skill loads) and keep the working directory at the project root — the scripts read and write project files such as ","type":"text"},{"text":"docs/brand-guidelines.md","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"assets/design-tokens.json","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"src/","type":"text","marks":[{"type":"code_inline"}]},{"text":" relative to it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Component + Styling Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"Install shadcn/ui with Tailwind:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npx shadcn@latest init","type":"text"}]},{"type":"paragraph","content":[{"text":"CLI prompts for framework, TypeScript, paths, and theme preferences. This configures both shadcn/ui and Tailwind CSS.","type":"text"}]},{"type":"paragraph","content":[{"text":"Add components:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npx shadcn@latest add button card dialog form","type":"text"}]},{"type":"paragraph","content":[{"text":"Use components with utility styling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"import { Button } from \"@/components/ui/button\"\nimport { Card, CardHeader, CardTitle, CardContent } from \"@/components/ui/card\"\n\nexport function Dashboard() {\n return (\n \u003cdiv className=\"container mx-auto p-6 grid gap-6 md:grid-cols-2 lg:grid-cols-3\">\n \u003cCard className=\"hover:shadow-lg transition-shadow\">\n \u003cCardHeader>\n \u003cCardTitle className=\"text-2xl font-bold\">Analytics\u003c/CardTitle>\n \u003c/CardHeader>\n \u003cCardContent className=\"space-y-4\">\n \u003cp className=\"text-muted-foreground\">View your metrics\u003c/p>\n \u003cButton variant=\"default\" className=\"w-full\">\n View Details\n \u003c/Button>\n \u003c/CardContent>\n \u003c/Card>\n \u003c/div>\n )\n}","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Alternative: Tailwind-Only Setup","type":"text"}]},{"type":"paragraph","content":[{"text":"Vite projects:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"npm install -D tailwindcss @tailwindcss/vite","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// vite.config.ts\nimport tailwindcss from '@tailwindcss/vite'\nexport default { plugins: [tailwindcss()] }","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"css"},"content":[{"text":"/* src/index.css */\n@import \"tailwindcss\";","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Component Library Guide","type":"text"}]},{"type":"paragraph","content":[{"text":"Comprehensive component catalog with usage patterns, installation, and composition examples.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/shadcn-components.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form & input components (Button, Input, Select, Checkbox, Date Picker, Form validation)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Layout & navigation (Card, Tabs, Accordion, Navigation Menu)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Overlays & dialogs (Dialog, Drawer, Popover, Toast, Command)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Feedback & status (Alert, Progress, Skeleton)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Display components (Table, Data Table, Avatar, Badge)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Theme & Customization","type":"text"}]},{"type":"paragraph","content":[{"text":"Theme configuration, CSS variables, dark mode implementation, and component customization.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/shadcn-theming.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dark mode setup with next-themes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"CSS variable system","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Color customization and palettes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Component variant customization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Theme toggle implementation","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Accessibility Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"ARIA patterns, keyboard navigation, screen reader support, and accessible component usage.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/shadcn-accessibility.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Radix UI accessibility features","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Keyboard navigation patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Focus management","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Screen reader announcements","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Form validation accessibility","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tailwind Utilities","type":"text"}]},{"type":"paragraph","content":[{"text":"Core utility classes for layout, spacing, typography, colors, borders, and shadows.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/tailwind-utilities.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Layout utilities (Flexbox, Grid, positioning)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spacing system (padding, margin, gap)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Typography (font sizes, weights, alignment, line height)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Colors and backgrounds","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Borders and shadows","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Arbitrary values for custom styling","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Responsive Design","type":"text"}]},{"type":"paragraph","content":[{"text":"Mobile-first breakpoints, responsive utilities, and adaptive layouts.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/tailwind-responsive.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mobile-first approach","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Breakpoint system (sm, md, lg, xl, 2xl)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Responsive utility patterns","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Container queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Max-width queries","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom breakpoints","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Tailwind Customization","type":"text"}]},{"type":"paragraph","content":[{"text":"Config file structure, custom utilities, plugins, and theme extensions.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/tailwind-customization.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@theme directive for custom tokens","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom colors and fonts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Spacing and breakpoint extensions","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom utility creation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Custom variants","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Layer organization (@layer base, components, utilities)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Apply directive for component extraction","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Visual Design System","type":"text"}]},{"type":"paragraph","content":[{"text":"Canvas-based design philosophy, visual communication principles, and sophisticated compositions.","type":"text","marks":[{"type":"strong"}]}]},{"type":"paragraph","content":[{"text":"See: ","type":"text"},{"text":"references/canvas-design-system.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Covers:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Design philosophy approach","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Visual communication over text","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Systematic patterns and composition","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Color, form, and spatial design","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Minimal text integration","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Museum-quality execution","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multi-page design systems","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Utility Scripts","type":"text"}]},{"type":"paragraph","content":[{"text":"Python automation for component installation and configuration generation.","type":"text","marks":[{"type":"strong"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"shadcn_add.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Add shadcn/ui components with dependency handling:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/shadcn_add.py button card dialog","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"tailwind_config_gen.py","type":"text"}]},{"type":"paragraph","content":[{"text":"Generate tailwind.config.js with custom theme:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/tailwind_config_gen.py --colors brand:blue --fonts display:Inter","type":"text"}]},{"type":"paragraph","content":[{"text":"The generator refuses to create or replace a config when any sibling ","type":"text"},{"text":"tailwind.config.js","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".cjs","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":".mjs","type":"text","marks":[{"type":"code_inline"}]},{"text":", or ","type":"text"},{"text":".ts","type":"text","marks":[{"type":"code_inline"}]},{"text":" file already exists. Review the reported config first, then pass ","type":"text"},{"text":"--force","type":"text","marks":[{"type":"code_inline"}]},{"text":" only when the competing output is intentional:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python scripts/tailwind_config_gen.py --colors brand:blue --force","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Component Composition","type":"text","marks":[{"type":"strong"}]},{"text":": Build complex UIs from simple, composable primitives","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Utility-First Styling","type":"text","marks":[{"type":"strong"}]},{"text":": Use Tailwind classes directly; extract components only for true repetition","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mobile-First Responsive","type":"text","marks":[{"type":"strong"}]},{"text":": Start with mobile styles, layer responsive variants","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Accessibility-First","type":"text","marks":[{"type":"strong"}]},{"text":": Leverage Radix UI primitives, add focus states, use semantic HTML","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Design Tokens","type":"text","marks":[{"type":"strong"}]},{"text":": Use consistent spacing scale, color palettes, typography system","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Dark Mode Consistency","type":"text","marks":[{"type":"strong"}]},{"text":": Apply dark variants to all themed elements","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Performance","type":"text","marks":[{"type":"strong"}]},{"text":": Leverage automatic CSS purging, avoid dynamic class names","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"TypeScript","type":"text","marks":[{"type":"strong"}]},{"text":": Use full type safety for better DX","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Visual Hierarchy","type":"text","marks":[{"type":"strong"}]},{"text":": Let composition guide attention, use spacing and color intentionally","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Expert Craftsmanship","type":"text","marks":[{"type":"strong"}]},{"text":": Every detail matters - treat UI as a craft","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Reference Navigation","type":"text"}]},{"type":"paragraph","content":[{"text":"Component Library","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/shadcn-components.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Complete component catalog","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/shadcn-theming.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Theming and customization","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/shadcn-accessibility.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Accessibility patterns","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Styling System","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/tailwind-utilities.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Core utility classes","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/tailwind-responsive.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Responsive design","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/tailwind-customization.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Configuration and extensions","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Visual Design","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/canvas-design-system.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Design philosophy and canvas workflows","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Automation","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/shadcn_add.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Component installation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/tailwind_config_gen.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Config generation","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Patterns","type":"text"}]},{"type":"paragraph","content":[{"text":"Form with validation:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"import { useForm } from \"react-hook-form\"\nimport { zodResolver } from \"@hookform/resolvers/zod\"\nimport * as z from \"zod\"\nimport { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from \"@/components/ui/form\"\nimport { Input } from \"@/components/ui/input\"\nimport { Button } from \"@/components/ui/button\"\n\nconst schema = z.object({\n email: z.string().email(),\n password: z.string().min(8)\n})\n\nexport function LoginForm() {\n const form = useForm({\n resolver: zodResolver(schema),\n defaultValues: { email: \"\", password: \"\" }\n })\n\n return (\n \u003cForm {...form}>\n \u003cform onSubmit={form.handleSubmit(console.log)} className=\"space-y-6\">\n \u003cFormField control={form.control} name=\"email\" render={({ field }) => (\n \u003cFormItem>\n \u003cFormLabel>Email\u003c/FormLabel>\n \u003cFormControl>\n \u003cInput type=\"email\" {...field} />\n \u003c/FormControl>\n \u003cFormMessage />\n \u003c/FormItem>\n )} />\n \u003cButton type=\"submit\" className=\"w-full\">Sign In\u003c/Button>\n \u003c/form>\n \u003c/Form>\n )\n}","type":"text"}]},{"type":"paragraph","content":[{"text":"Responsive layout with dark mode:","type":"text","marks":[{"type":"strong"}]}]},{"type":"code_block","attrs":{"wrap":false,"language":"tsx"},"content":[{"text":"\u003cdiv className=\"min-h-screen bg-white dark:bg-gray-900\">\n \u003cdiv className=\"container mx-auto px-4 py-8\">\n \u003cdiv className=\"grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6\">\n \u003cCard className=\"bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700\">\n \u003cCardContent className=\"p-6\">\n \u003ch3 className=\"text-xl font-semibold text-gray-900 dark:text-white\">\n Content\n \u003c/h3>\n \u003c/CardContent>\n \u003c/Card>\n \u003c/div>\n \u003c/div>\n\u003c/div>","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"shadcn/ui Docs: https://ui.shadcn.com","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tailwind CSS Docs: https://tailwindcss.com","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Radix UI: https://radix-ui.com","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Tailwind UI: https://tailwindui.com","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Headless UI: https://headlessui.com","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"v0 (AI UI Generator): https://v0.dev","type":"text"}]}]}]}]},"metadata":{"date":"2026-11-02","name":"ui-styling","author":"@skillopedia","source":{"stars":144206,"repo_name":"ui-ux-pro-max-skill","origin_url":"https://github.com/nextlevelbuilder/ui-ux-pro-max-skill/blob/HEAD/.claude/skills/ui-styling/SKILL.md","repo_owner":"nextlevelbuilder","body_sha256":"625e302c60ee8f2e22d56cbe638c4a739e2657f566dae42689503ad517c30f1e","cluster_key":"d6dd1d4d6d5270a3083912ddfa33430fa80dc64861b999de133e68324fb9f736","clean_bundle":{"format":"clean-skill-bundle-v1","source":"nextlevelbuilder/ui-ux-pro-max-skill/.claude/skills/ui-styling/SKILL.md","text_bytes":273928,"source_commit":"713eef51b04efc98d6fa565323b495f7c9c89fd4","skills_sh_rank":854,"attachment_count":41,"text_attachments":41,"attachment_storage":"skill_attachments","binary_attachments":0},"cluster_size":1,"skill_md_path":".claude/skills/ui-styling/SKILL.md","import_metadata":{"date":"2026-11-02","author":"@skillopedia","version":"v1","category":"design-ux","category_label":"Design"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"design-ux","metadata":{"author":"claudekit","version":"1.0.0"},"import_tag":"skills-sh-top20k-2026-11","description":"Create beautiful, accessible user interfaces with shadcn/ui components (built on Radix UI + Tailwind), Tailwind CSS utility-first styling, and canvas-based visual designs. Use when building user interfaces, implementing design systems, creating responsive layouts, adding accessible components (dialogs, dropdowns, forms, tables), customizing themes and colors, implementing dark mode, generating visual designs and posters, or establishing consistent styling patterns across applications.","argument-hint":"[component or layout]"}},"renderedAt":1793661432984}

UI Styling Skill Comprehensive skill for creating beautiful, accessible user interfaces combining shadcn/ui components, Tailwind CSS utility styling, and canvas-based visual design systems. Reference - shadcn/ui: https://ui.shadcn.com/llms.txt - Tailwind CSS: https://tailwindcss.com/docs When to Use This Skill Use when: - Building UI with React-based frameworks (Next.js, Vite, Remix, Astro) - Implementing accessible components (dialogs, forms, tables, navigation) - Styling with utility-first CSS approach - Creating responsive, mobile-first layouts - Implementing dark mode and theme customizat…