## Vendor-in-Token Architecture (Complete Migration) - Migrate all vendor API endpoints from require_vendor_context() to token_vendor_id - Update permission dependencies to extract vendor from JWT token - Add vendor exceptions: VendorAccessDeniedException, VendorOwnerOnlyException, InsufficientVendorPermissionsException - Shop endpoints retain require_vendor_context() for URL-based detection - Add AUTH-004 architecture rule enforcing vendor context patterns - Fix marketplace router missing /marketplace prefix ## Exception Pattern Fixes (API-003/API-004) - Services raise domain exceptions, endpoints let them bubble up - Add code_quality and content_page exception modules - Move business logic from endpoints to services (admin, auth, content_page) - Fix exception handling in admin, shop, and vendor endpoints ## Tailwind CSS Consolidation - Consolidate CSS to per-area files (admin, vendor, shop, platform) - Remove shared/cdn-fallback.html and shared/css/tailwind.min.css - Update all templates to use area-specific Tailwind output files - Remove Node.js config (package.json, postcss.config.js, tailwind.config.js) ## Documentation & Cleanup - Update vendor-in-token-architecture.md with completed migration status - Update architecture-rules.md with new rules - Move migration docs to docs/development/migration/ - Remove duplicate/obsolete documentation files - Merge pytest.ini settings into pyproject.toml 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
6.8 KiB
Tailwind CSS Build Guide
Version: 2.0 Last Updated: December 2024 Audience: Frontend Developers
Overview
The platform uses Tailwind CSS v4 with the Standalone CLI - no Node.js required. Each frontend (admin, vendor, shop, platform) has its own dedicated CSS configuration.
Architecture
How It Works
Tailwind Standalone CLI (single binary, no npm)
│
├── static/admin/css/tailwind.css → tailwind.output.css (Admin)
├── static/vendor/css/tailwind.css → tailwind.output.css (Vendor)
├── static/shop/css/tailwind.css → tailwind.output.css (Shop)
└── static/platform/css/tailwind.css → tailwind.output.css (Platform)
Key Files
| File | Purpose |
|---|---|
~/.local/bin/tailwindcss |
Standalone CLI binary (v4.x) |
static/*/css/tailwind.css |
CSS-first source configuration |
static/*/css/tailwind.output.css |
Compiled output (do not edit) |
Template Loading
Each frontend loads its own CSS:
<!-- Admin -->
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
<!-- Vendor -->
<link rel="stylesheet" href="{{ url_for('static', path='vendor/css/tailwind.output.css') }}" />
<!-- Shop -->
<link rel="stylesheet" href="{{ url_for('static', path='shop/css/tailwind.output.css') }}" />
<!-- Platform -->
<link rel="stylesheet" href="{{ url_for('static', path='platform/css/tailwind.output.css') }}" />
CSS-First Configuration (Tailwind v4)
Tailwind v4 uses CSS-first configuration instead of tailwind.config.js. All customization happens directly in CSS files.
Source File Structure
/* static/admin/css/tailwind.css */
/* Import Tailwind */
@import "tailwindcss";
/* Content sources for tree-shaking */
@source "../../../app/templates/admin/**/*.html";
@source "../../js/**/*.js";
/* Custom theme (colors, fonts, spacing) */
@theme {
--color-gray-50: #f9fafb;
--color-gray-900: #121317;
--font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif;
}
/* Dark mode variant */
@variant dark (&:where(.dark, .dark *));
/* Custom utilities */
@layer utilities {
.shadow-outline-purple {
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
}
}
/* Custom components */
@layer components {
.form-input { ... }
.btn-primary { ... }
}
Key Directives
| Directive | Purpose | Example |
|---|---|---|
@import "tailwindcss" |
Import Tailwind base | Required at top |
@source |
Content paths for purging | @source "../../../app/templates/**/*.html" |
@theme |
Custom design tokens | --color-purple-600: #7e3af2; |
@variant |
Custom variants | @variant dark (&:where(.dark, .dark *)) |
@layer utilities |
Custom utility classes | .shadow-outline-* |
@layer components |
Custom components | .form-input, .btn-* |
Custom Color Palette
All frontends share the same Windmill Dashboard color palette:
@theme {
/* Gray (custom darker palette) */
--color-gray-50: #f9fafb;
--color-gray-100: #f4f5f7;
--color-gray-200: #e5e7eb;
--color-gray-300: #d5d6d7;
--color-gray-400: #9e9e9e;
--color-gray-500: #707275;
--color-gray-600: #4c4f52;
--color-gray-700: #24262d;
--color-gray-800: #1a1c23;
--color-gray-900: #121317;
/* Purple (primary) */
--color-purple-600: #7e3af2;
/* Plus: red, orange, yellow, green, teal, blue, indigo, pink, cool-gray */
}
Building Tailwind CSS
Prerequisites
Install the standalone CLI (one-time setup):
make tailwind-install
Or manually:
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
chmod +x tailwindcss-linux-x64
mv tailwindcss-linux-x64 ~/.local/bin/tailwindcss
Development Build
Build all frontends (includes all classes, larger files):
make tailwind-dev
Production Build
Build with minification (smaller files):
make tailwind-build
Watch Mode
Watch for changes during development:
make tailwind-watch
Makefile Targets
| Target | Description |
|---|---|
make tailwind-install |
Install Tailwind standalone CLI |
make tailwind-dev |
Build all CSS (development) |
make tailwind-build |
Build all CSS (production, minified) |
make tailwind-watch |
Watch for changes |
Dark Mode
The platform uses class-based dark mode with the .dark class on the <html> element:
<html :class="{ 'dark': dark }">
<body class="bg-white dark:bg-gray-900">
Toggle dark mode with JavaScript:
document.documentElement.classList.toggle('dark');
localStorage.setItem('darkMode', dark);
Adding Custom Utilities
Add custom utilities in the source CSS file:
@layer utilities {
.shadow-outline-purple {
box-shadow: 0 0 0 3px hsla(262, 97%, 81%, 0.45);
}
.text-balance {
text-wrap: balance;
}
}
Adding Custom Components
Add reusable component classes:
@layer components {
.form-input {
@apply block w-full rounded-md border border-gray-300 bg-white px-3 py-2 text-sm;
@apply focus:border-purple-500 focus:ring-1 focus:ring-purple-500;
@apply dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200;
}
.btn-primary {
@apply px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg;
@apply hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-500;
}
}
Troubleshooting
Classes not appearing
- Check @source paths - Ensure your templates are in a scanned directory
- Check class exists - Tailwind v4 may have renamed some classes
- Rebuild CSS - Run
make tailwind-devafter config changes
Dynamic classes in Alpine.js
Tailwind v4 uses JIT compilation and scans for complete class names. Dynamic classes work automatically if the full class name appears in your templates:
<!-- This works - full class names visible -->
<div :class="isActive ? 'bg-green-600' : 'bg-red-600'">
Class name changes (v2 → v4)
| v2.x | v4.x |
|---|---|
overflow-ellipsis |
text-ellipsis |
flex-grow-0 |
grow-0 |
flex-shrink-0 |
shrink-0 |
Best Practices
-
Always rebuild after CSS changes
make tailwind-dev -
Use production builds for deployment
make tailwind-build -
Keep each frontend's CSS isolated - Don't cross-import between frontends
-
Use @apply sparingly - Prefer utility classes in templates when possible
-
Test in both light and dark modes