refactor: migrate vendor APIs to token-based context and consolidate architecture
## 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>
This commit is contained in:
@@ -136,32 +136,36 @@ async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
|
||||
- `db.commit()`
|
||||
- `db.query(`
|
||||
|
||||
#### API-003: Catch Service Exceptions
|
||||
#### API-003: No HTTPException in Endpoints
|
||||
**Severity:** Error
|
||||
|
||||
API endpoints must catch domain exceptions from services and convert them to appropriate HTTPException with proper status codes.
|
||||
API endpoints must NOT raise HTTPException directly. Instead, let domain exceptions bubble up to the global exception handler which converts them to appropriate HTTP responses.
|
||||
|
||||
```python
|
||||
# ✅ Good
|
||||
# ✅ Good - Let domain exceptions bubble up
|
||||
@router.post("/vendors")
|
||||
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
|
||||
try:
|
||||
result = vendor_service.create_vendor(db, vendor)
|
||||
return result
|
||||
except VendorAlreadyExistsError as e:
|
||||
raise HTTPException(status_code=409, detail=str(e))
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
# Service raises VendorAlreadyExistsException if duplicate
|
||||
# Global handler converts to 409 Conflict
|
||||
return vendor_service.create_vendor(db, vendor)
|
||||
|
||||
# ❌ Bad - Don't raise HTTPException directly
|
||||
@router.post("/vendors")
|
||||
async def create_vendor(vendor: VendorCreate, db: Session = Depends(get_db)):
|
||||
if vendor_service.exists(db, vendor.subdomain):
|
||||
raise HTTPException(status_code=409, detail="Vendor exists") # BAD!
|
||||
return vendor_service.create_vendor(db, vendor)
|
||||
```
|
||||
|
||||
**Pattern:** Services raise domain exceptions → Global handler converts to HTTP responses
|
||||
|
||||
#### API-004: Proper Authentication
|
||||
**Severity:** Warning
|
||||
|
||||
Protected endpoints must use Depends() for authentication.
|
||||
|
||||
```python
|
||||
# ✅ Good
|
||||
# ✅ Good - Protected endpoint with authentication
|
||||
@router.post("/vendors")
|
||||
async def create_vendor(
|
||||
vendor: VendorCreate,
|
||||
@@ -171,6 +175,31 @@ async def create_vendor(
|
||||
pass
|
||||
```
|
||||
|
||||
**Auto-Excluded Files:**
|
||||
|
||||
The validator automatically skips API-004 checks for authentication endpoint files (`*/auth.py`) since login, logout, and registration endpoints are intentionally public.
|
||||
|
||||
**Marking Public Endpoints:**
|
||||
|
||||
For other intentionally public endpoints (webhooks, health checks, etc.), use a comment marker:
|
||||
|
||||
```python
|
||||
# ✅ Good - Webhook endpoint marked as public
|
||||
# public - Stripe webhook receives external callbacks
|
||||
@router.post("/webhook/stripe")
|
||||
def stripe_webhook(request: Request):
|
||||
...
|
||||
|
||||
# ✅ Good - Using noqa style
|
||||
@router.post("/health") # noqa: API-004
|
||||
def health_check():
|
||||
...
|
||||
```
|
||||
|
||||
**Recognized markers:**
|
||||
- `# public` - descriptive marker for intentionally unauthenticated endpoints
|
||||
- `# noqa: API-004` - standard noqa style to suppress the warning
|
||||
|
||||
#### API-005: Multi-Tenant Scoping
|
||||
**Severity:** Error
|
||||
|
||||
@@ -542,12 +571,41 @@ async loadData() {
|
||||
#### TPL-001: Admin Templates Extend admin/base.html
|
||||
**Severity:** Error
|
||||
|
||||
All admin templates must extend the base template for consistent layout (sidebar, navigation, etc.).
|
||||
|
||||
```jinja
|
||||
✅ {% extends "admin/base.html" %}
|
||||
❌ No extends directive
|
||||
{# ✅ Good - Extends base template #}
|
||||
{% extends "admin/base.html" %}
|
||||
{% block content %}
|
||||
...
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
Exceptions: `base.html` itself, files in `partials/`
|
||||
**Auto-Excluded Files:**
|
||||
|
||||
The validator automatically skips TPL-001 checks for:
|
||||
|
||||
- `login.html` - Standalone login page (no sidebar/navigation needed)
|
||||
- `errors/*.html` - Error pages extend `errors/base.html` instead
|
||||
- `test-*.html` - Test/development templates
|
||||
- `base.html` - The base template itself
|
||||
- `partials/*.html` - Partial templates included in other templates
|
||||
|
||||
**Marking Standalone Templates:**
|
||||
|
||||
For other templates that intentionally don't extend base.html, use a comment marker in the first 5 lines:
|
||||
|
||||
```jinja
|
||||
{# standalone - Minimal monitoring page without admin chrome #}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
...
|
||||
```
|
||||
|
||||
**Recognized markers:**
|
||||
- `{# standalone #}` - Jinja comment style
|
||||
- `{# noqa: TPL-001 #}` - Standard noqa style
|
||||
- `<!-- standalone -->` - HTML comment style
|
||||
|
||||
#### TPL-002: Vendor Templates Extend vendor/base.html
|
||||
**Severity:** Error
|
||||
@@ -789,12 +847,13 @@ Before committing code:
|
||||
| Violation | Quick Fix |
|
||||
|-----------|-----------|
|
||||
| HTTPException in service | Create custom exception in `app/exceptions/` |
|
||||
| HTTPException in endpoint | Let domain exceptions bubble up to global handler |
|
||||
| Business logic in endpoint | Move to service layer |
|
||||
| No exception handling | Add try/except, convert to HTTPException |
|
||||
| console.log in JS | Use `window.LogConfig.createLogger()` |
|
||||
| Missing ...data() | Add spread operator in component return |
|
||||
| Bare except clause | Specify exception type |
|
||||
| Raw dict return | Create Pydantic response model |
|
||||
| Template not extending base | Add `{% extends %}` or `{# standalone #}` marker |
|
||||
|
||||
---
|
||||
|
||||
@@ -834,5 +893,5 @@ All rules are defined in `.architecture-rules.yaml`. To modify rules:
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-28
|
||||
**Version:** 2.0
|
||||
**Last Updated:** 2025-12-04
|
||||
**Version:** 2.2
|
||||
|
||||
@@ -324,5 +324,5 @@ Migration failures will halt deployment to prevent data corruption.
|
||||
## Further Reading
|
||||
|
||||
- [Alembic Official Documentation](https://alembic.sqlalchemy.org/)
|
||||
- [Database Setup Guide](../getting-started/database-setup-guide.md)
|
||||
- [Deployment Guide](../deployment/production.md)
|
||||
- [Database Seeder Documentation](../database-seeder/database-seeder-documentation.md)
|
||||
- [Database Init Guide](../database-seeder/database-init-guide.md)
|
||||
|
||||
402
docs/development/migration/tailwind-migration-plan.md
Normal file
402
docs/development/migration/tailwind-migration-plan.md
Normal file
@@ -0,0 +1,402 @@
|
||||
# Tailwind CSS Migration Plan: v1.4/v2.2 → v4.0 (Standalone CLI)
|
||||
|
||||
**Created:** December 2024
|
||||
**Completed:** December 2024
|
||||
**Status:** COMPLETE
|
||||
**Goal:** Eliminate Node.js dependency, use Tailwind Standalone CLI
|
||||
|
||||
> **Migration Complete!** All frontends now use Tailwind CSS v4 with the standalone CLI.
|
||||
> See [Tailwind CSS Build Guide](../../frontend/tailwind-css.md) for current documentation.
|
||||
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Two Tailwind Setups (Before Migration)
|
||||
|
||||
| Component | Version | Source | Purpose |
|
||||
|-----------|---------|--------|---------|
|
||||
| Base styles | 2.2.19 | CDN + local fallback | Core Tailwind utilities for all frontends |
|
||||
| Custom overrides | 1.4.6 | npm build | Windmill Dashboard theme (admin/vendor) |
|
||||
|
||||
### Current Files (Before Migration)
|
||||
|
||||
```
|
||||
package.json # tailwindcss: 1.4.6 (TO BE REMOVED)
|
||||
package-lock.json # (TO BE REMOVED)
|
||||
node_modules/ # (TO BE REMOVED)
|
||||
tailwind.config.js # v1.4 format config (TO BE UPDATED)
|
||||
postcss.config.js # (TO BE REMOVED)
|
||||
static/shared/css/tailwind.min.css # CDN fallback v2.2.19 (TO BE REMOVED)
|
||||
static/admin/css/tailwind.output.css # Built overrides (TO BE REBUILT)
|
||||
static/vendor/css/tailwind.output.css # Built overrides (TO BE REBUILT)
|
||||
```
|
||||
|
||||
### Current Plugins (TO BE REPLACED)
|
||||
|
||||
```json
|
||||
{
|
||||
"@tailwindcss/custom-forms": "0.2.1", // DEPRECATED - replaced by @tailwindcss/forms
|
||||
"tailwindcss-multi-theme": "1.0.3" // DEPRECATED - native darkMode in v3+
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Goals
|
||||
|
||||
1. ✅ Eliminate Node.js dependency entirely
|
||||
2. ✅ Use Tailwind Standalone CLI (single binary, no npm)
|
||||
3. ✅ Upgrade to Tailwind v4.0 (latest)
|
||||
4. ✅ Remove CDN dependency (all CSS built locally)
|
||||
5. ✅ Update config to v4 format
|
||||
6. ✅ Replace deprecated plugins with native features
|
||||
7. ✅ Consolidate to single Tailwind version
|
||||
8. ✅ Verify all frontends work correctly
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Migration
|
||||
|
||||
### Phase 1: Install Tailwind Standalone CLI
|
||||
|
||||
The standalone CLI bundles Tailwind + Node.js runtime into a single binary.
|
||||
|
||||
```bash
|
||||
# Download latest standalone CLI for Linux x64
|
||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
|
||||
chmod +x tailwindcss-linux-x64
|
||||
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||
|
||||
# Verify installation
|
||||
tailwindcss --version
|
||||
```
|
||||
|
||||
**For other platforms:**
|
||||
- macOS (Intel): `tailwindcss-macos-x64`
|
||||
- macOS (Apple Silicon): `tailwindcss-macos-arm64`
|
||||
- Windows: `tailwindcss-windows-x64.exe`
|
||||
- Linux ARM: `tailwindcss-linux-arm64`
|
||||
|
||||
### Phase 2: Update tailwind.config.js
|
||||
|
||||
Replace the entire config file with v4-compatible format:
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js - Tailwind CSS v4.0 (Standalone CLI)
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
// v4: 'content' replaces 'purge'
|
||||
content: [
|
||||
'app/templates/**/*.html',
|
||||
'static/**/*.js',
|
||||
],
|
||||
|
||||
// v4: safelist for dynamic classes (Alpine.js)
|
||||
safelist: [
|
||||
'bg-orange-600',
|
||||
'bg-green-600',
|
||||
'bg-red-600',
|
||||
'bg-blue-600',
|
||||
'bg-purple-600',
|
||||
'hover:bg-orange-700',
|
||||
'hover:bg-green-700',
|
||||
'hover:bg-red-700',
|
||||
'hover:bg-blue-700',
|
||||
'hover:bg-purple-700',
|
||||
// Status colors
|
||||
'text-green-600',
|
||||
'text-red-600',
|
||||
'text-yellow-600',
|
||||
'text-blue-600',
|
||||
'bg-green-100',
|
||||
'bg-red-100',
|
||||
'bg-yellow-100',
|
||||
'bg-blue-100',
|
||||
],
|
||||
|
||||
// v4: darkMode replaces tailwindcss-multi-theme
|
||||
darkMode: 'class',
|
||||
|
||||
theme: {
|
||||
extend: {
|
||||
// Custom colors from Windmill Dashboard
|
||||
colors: {
|
||||
gray: {
|
||||
50: '#f9fafb',
|
||||
100: '#f4f5f7',
|
||||
200: '#e5e7eb',
|
||||
300: '#d5d6d7',
|
||||
400: '#9e9e9e',
|
||||
500: '#707275',
|
||||
600: '#4c4f52',
|
||||
700: '#24262d',
|
||||
800: '#1a1c23',
|
||||
900: '#121317',
|
||||
},
|
||||
purple: {
|
||||
50: '#f6f5ff',
|
||||
100: '#edebfe',
|
||||
200: '#dcd7fe',
|
||||
300: '#cabffd',
|
||||
400: '#ac94fa',
|
||||
500: '#9061f9',
|
||||
600: '#7e3af2',
|
||||
700: '#6c2bd9',
|
||||
800: '#5521b5',
|
||||
900: '#4a1d96',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ['Inter', 'system-ui', '-apple-system', 'sans-serif'],
|
||||
},
|
||||
maxHeight: {
|
||||
'xl': '36rem',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
plugins: [
|
||||
// Note: Standalone CLI includes @tailwindcss/forms by default in v4
|
||||
// No external plugins needed
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Update CSS Source Files
|
||||
|
||||
Update `static/admin/css/tailwind.css`:
|
||||
|
||||
```css
|
||||
/* Tailwind CSS v4 directives */
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
/* Custom component classes */
|
||||
@layer components {
|
||||
.form-input {
|
||||
@apply block w-full rounded-md border-gray-300 shadow-sm
|
||||
focus:border-purple-500 focus:ring-purple-500;
|
||||
}
|
||||
|
||||
.form-select {
|
||||
@apply block w-full rounded-md border-gray-300 shadow-sm
|
||||
focus:border-purple-500 focus:ring-purple-500;
|
||||
}
|
||||
|
||||
.form-checkbox {
|
||||
@apply rounded border-gray-300 text-purple-600
|
||||
focus:ring-purple-500;
|
||||
}
|
||||
|
||||
.form-textarea {
|
||||
@apply block w-full rounded-md border-gray-300 shadow-sm
|
||||
focus:border-purple-500 focus:ring-purple-500;
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode base overrides */
|
||||
@layer base {
|
||||
.dark body {
|
||||
@apply bg-gray-900 text-gray-200;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Add Makefile Targets
|
||||
|
||||
Add these targets to your Makefile (replaces npm scripts):
|
||||
|
||||
```makefile
|
||||
# =============================================================================
|
||||
# Tailwind CSS (Standalone CLI - No Node.js Required)
|
||||
# =============================================================================
|
||||
|
||||
.PHONY: tailwind-install tailwind-dev tailwind-build tailwind-watch
|
||||
|
||||
# Install Tailwind standalone CLI
|
||||
tailwind-install:
|
||||
@echo "Installing Tailwind CSS standalone CLI..."
|
||||
curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64
|
||||
chmod +x tailwindcss-linux-x64
|
||||
sudo mv tailwindcss-linux-x64 /usr/local/bin/tailwindcss
|
||||
@echo "Tailwind CLI installed: $$(tailwindcss --version)"
|
||||
|
||||
# Development build (includes all classes, faster)
|
||||
tailwind-dev:
|
||||
@echo "Building Tailwind CSS (development)..."
|
||||
tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css
|
||||
tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css
|
||||
@echo "CSS built successfully"
|
||||
|
||||
# Production build (purged and minified)
|
||||
tailwind-build:
|
||||
@echo "Building Tailwind CSS (production)..."
|
||||
tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --minify
|
||||
tailwindcss -i static/admin/css/tailwind.css -o static/vendor/css/tailwind.output.css --minify
|
||||
@echo "CSS built and minified successfully"
|
||||
|
||||
# Watch mode for development
|
||||
tailwind-watch:
|
||||
@echo "Watching for CSS changes..."
|
||||
tailwindcss -i static/admin/css/tailwind.css -o static/admin/css/tailwind.output.css --watch
|
||||
```
|
||||
|
||||
### Phase 5: Update Dark Mode Implementation
|
||||
|
||||
**Old approach (tailwindcss-multi-theme):**
|
||||
```html
|
||||
<body class="theme-dark">
|
||||
<div class="bg-white dark:bg-gray-800">
|
||||
```
|
||||
|
||||
**New approach (Tailwind v4 native):**
|
||||
```html
|
||||
<html class="dark">
|
||||
<body>
|
||||
<div class="bg-white dark:bg-gray-800">
|
||||
```
|
||||
|
||||
**Files to update:**
|
||||
1. `app/templates/admin/base.html` - Add `dark` class to `<html>` element
|
||||
2. `app/templates/vendor/base.html` - Add `dark` class to `<html>` element
|
||||
3. `static/admin/js/init-alpine.js` - Update dark mode toggle logic
|
||||
4. `static/vendor/js/init-alpine.js` - Update dark mode toggle logic
|
||||
|
||||
**JavaScript update:**
|
||||
```javascript
|
||||
// Old
|
||||
document.body.classList.toggle('theme-dark');
|
||||
|
||||
// New
|
||||
document.documentElement.classList.toggle('dark');
|
||||
```
|
||||
|
||||
### Phase 6: Update Base Templates
|
||||
|
||||
**Remove CDN loading, use only local built CSS:**
|
||||
|
||||
```html
|
||||
<!-- OLD: CDN with fallback (REMOVE THIS) -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css"
|
||||
onerror="this.onerror=null; this.href='{{ url_for('static', path='shared/css/tailwind.min.css') }}';">
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
|
||||
<!-- NEW: Local only (v4) -->
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='admin/css/tailwind.output.css') }}" />
|
||||
```
|
||||
|
||||
### Phase 7: Build and Test
|
||||
|
||||
```bash
|
||||
# Build CSS
|
||||
make tailwind-build
|
||||
|
||||
# Start development server
|
||||
make dev
|
||||
|
||||
# Test all frontends:
|
||||
# - http://localhost:8000/admin/dashboard
|
||||
# - http://localhost:8000/vendor/{code}/dashboard
|
||||
# - http://localhost:8000/ (shop)
|
||||
```
|
||||
|
||||
### Phase 8: Remove Node.js Artifacts
|
||||
|
||||
After successful testing, remove all Node.js related files:
|
||||
|
||||
```bash
|
||||
# Remove Node.js files
|
||||
rm -rf node_modules/
|
||||
rm package.json
|
||||
rm package-lock.json
|
||||
rm postcss.config.js
|
||||
|
||||
# Remove unused CDN fallback
|
||||
rm static/shared/css/tailwind.min.css
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
- [x] Tailwind CLI installed and working (`tailwindcss --version`)
|
||||
- [x] Admin dashboard loads correctly
|
||||
- [x] Vendor dashboard loads correctly
|
||||
- [x] Shop frontend loads correctly
|
||||
- [x] Platform pages load correctly
|
||||
- [x] Dark mode toggle works
|
||||
- [x] All buttons have correct colors
|
||||
- [x] Forms display correctly (inputs, selects, checkboxes)
|
||||
- [x] Responsive design works (mobile/tablet/desktop)
|
||||
- [x] No console errors
|
||||
- [x] Production build works (`make tailwind-build`)
|
||||
- [x] node_modules removed
|
||||
- [x] package.json removed
|
||||
- [x] Each frontend has its own CSS source file
|
||||
- [x] Vendor theming (CSS variables) still works
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes Reference
|
||||
|
||||
### Class Name Changes (v2 → v4)
|
||||
|
||||
| v2.x | v4.x | Notes |
|
||||
|------|------|-------|
|
||||
| `overflow-ellipsis` | `text-ellipsis` | Renamed |
|
||||
| `flex-grow-0` | `grow-0` | Shortened |
|
||||
| `flex-shrink-0` | `shrink-0` | Shortened |
|
||||
| `decoration-clone` | `box-decoration-clone` | Renamed |
|
||||
|
||||
### Plugin Changes
|
||||
|
||||
| Old Plugin | New Approach | Notes |
|
||||
|------------|--------------|-------|
|
||||
| `@tailwindcss/custom-forms` | Built-in form styles | Native in v4 |
|
||||
| `tailwindcss-multi-theme` | `darkMode: 'class'` | Native support |
|
||||
|
||||
### Config Changes
|
||||
|
||||
| v1.x/v2.x | v4.x |
|
||||
|-----------|------|
|
||||
| `purge: [...]` | `content: [...]` |
|
||||
| `variants: {...}` | Removed (JIT generates all) |
|
||||
| `mode: 'jit'` | Default (not needed) |
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If issues arise, the old setup can be restored:
|
||||
|
||||
```bash
|
||||
# Reinstall Node dependencies
|
||||
npm install
|
||||
|
||||
# Rebuild with old config
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Benefits of This Migration
|
||||
|
||||
| Aspect | Before | After |
|
||||
|--------|--------|-------|
|
||||
| **Node.js required** | Yes | No |
|
||||
| **Dependencies** | ~200MB node_modules | ~50MB single binary |
|
||||
| **Build speed** | Slower (npm + PostCSS) | Faster (standalone) |
|
||||
| **Tailwind versions** | 2 (v1.4 + v2.2) | 1 (v4.0) |
|
||||
| **CSS loading** | CDN + local | Local only |
|
||||
| **Maintenance** | npm updates, security patches | Single binary updates |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Tailwind CSS Standalone CLI](https://tailwindcss.com/blog/standalone-cli)
|
||||
- [Tailwind CSS v4 Documentation](https://tailwindcss.com/docs)
|
||||
- [Dark Mode in Tailwind](https://tailwindcss.com/docs/dark-mode)
|
||||
- [Standalone CLI Releases](https://github.com/tailwindlabs/tailwindcss/releases)
|
||||
Reference in New Issue
Block a user