- Created automated script (scripts/align-8px-grid.py) to align all spacing to 8px grid - Replaced non-8px-aligned spacing: gap-3/p-3/m-3 (12px) → gap-4/p-4/m-4 (16px), gap-5/p-5/m-5 (20px) → gap-6/p-6/m-6 (24px), gap-10/p-10/m-10 (40px) → gap-12/p-12/m-12 (48px), gap-20/p-20/m-20 (80px) → gap-24/p-24/m-24 (96px) - Preserved: 4px values (gap-1, p-1, m-1) as they may be intentional fine-tuning, responsive breakpoints (sm:, md:, lg:), test files, documentation - Modified files across all components to ensure consistent 8px grid alignment - Action 11.2.1.3: Align all elements to 8px grid - COMPLETE
211 lines
6.7 KiB
Python
Executable file
211 lines
6.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""
|
|
Script to align all spacing to 8px grid for Action 11.2.1.3
|
|
|
|
This script:
|
|
1. Finds all non-8px-aligned spacing values (1, 3, 5, 10, 20)
|
|
2. Replaces them with nearest 8px-aligned values
|
|
3. Preserves necessary fine-tuning (4px values in specific contexts)
|
|
"""
|
|
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Tuple
|
|
|
|
# Non-8px-aligned spacing values and their replacements
|
|
# Note: We're conservative with 4px (spacing-1) values as they might be intentional fine-tuning
|
|
SPACING_REPLACEMENTS = {
|
|
# 12px (spacing-3) → 16px (better spacing)
|
|
'gap-3': 'gap-4', # 12px → 16px
|
|
'p-3': 'p-4', # 12px → 16px
|
|
'm-3': 'm-4', # 12px → 16px
|
|
'px-3': 'px-4',
|
|
'py-3': 'py-4',
|
|
'mx-3': 'mx-4',
|
|
'my-3': 'my-4',
|
|
'space-x-3': 'space-x-4',
|
|
'space-y-3': 'space-y-4',
|
|
|
|
# 20px (spacing-5) → 16px or 24px (prefer 24px for better spacing)
|
|
'gap-5': 'gap-6', # 20px → 24px
|
|
'p-5': 'p-6', # 20px → 24px
|
|
'm-5': 'm-6', # 20px → 24px
|
|
'px-5': 'px-6',
|
|
'py-5': 'py-6',
|
|
'mx-5': 'mx-6',
|
|
'my-5': 'my-6',
|
|
'space-x-5': 'space-x-6',
|
|
'space-y-5': 'space-y-6',
|
|
|
|
# 40px (spacing-10) → 32px or 48px (prefer 48px for better spacing)
|
|
'gap-10': 'gap-12', # 40px → 48px
|
|
'p-10': 'p-12', # 40px → 48px
|
|
'm-10': 'm-12', # 40px → 48px
|
|
'px-10': 'px-12',
|
|
'py-10': 'py-12',
|
|
'mx-10': 'mx-12',
|
|
'my-10': 'my-12',
|
|
'space-x-10': 'space-x-12',
|
|
'space-y-10': 'space-y-12',
|
|
|
|
# 80px (spacing-20) → 64px or 96px (prefer 96px for better spacing)
|
|
'gap-20': 'gap-24', # 80px → 96px
|
|
'p-20': 'p-24', # 80px → 96px
|
|
'm-20': 'm-24', # 80px → 96px
|
|
'px-20': 'px-24',
|
|
'py-20': 'py-24',
|
|
'mx-20': 'mx-24',
|
|
'my-20': 'my-24',
|
|
'space-x-20': 'space-x-24',
|
|
'space-y-20': 'space-y-24',
|
|
}
|
|
|
|
# Patterns to preserve (contexts where non-8px spacing might be intentional)
|
|
PRESERVE_PATTERNS = [
|
|
# Test files
|
|
r'\.test\.',
|
|
r'\.spec\.',
|
|
# Documentation
|
|
r'\.md',
|
|
# Responsive breakpoints (sm:, md:, lg:) - these are intentional
|
|
r'sm:gap-[135]|md:gap-[135]|lg:gap-[135]',
|
|
r'sm:p-[135]|md:p-[135]|lg:p-[135]',
|
|
r'sm:m-[135]|md:m-[135]|lg:m-[135]',
|
|
# Arbitrary values that might be intentional
|
|
r'gap-\[|p-\[|m-\[',
|
|
]
|
|
|
|
# Files/directories to skip
|
|
SKIP_PATTERNS = [
|
|
'node_modules',
|
|
'.git',
|
|
'.build',
|
|
'dist',
|
|
'coverage',
|
|
'test-reports',
|
|
'EXHAUSTIVE_TODO_LIST.md',
|
|
'GRID_SYSTEM.md',
|
|
'SPACING_AUDIT_REPORT.md',
|
|
'SPACING_GUIDE.md',
|
|
'design-tokens.css', # This file defines the spacing scale
|
|
]
|
|
|
|
def should_preserve(line: str, file_path: str) -> bool:
|
|
"""Check if this line should be preserved"""
|
|
# Skip test and documentation files
|
|
if any(pattern in file_path for pattern in SKIP_PATTERNS):
|
|
return True
|
|
|
|
# Check preserve patterns
|
|
for pattern in PRESERVE_PATTERNS:
|
|
if re.search(pattern, line, re.IGNORECASE):
|
|
return True
|
|
|
|
return False
|
|
|
|
def replace_spacing(file_path: Path, dry_run: bool = False) -> Tuple[int, int]:
|
|
"""Replace non-8px spacing in a file"""
|
|
replaced = 0
|
|
preserved = 0
|
|
|
|
try:
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
original_content = content
|
|
|
|
lines = content.split('\n')
|
|
new_lines = []
|
|
|
|
for line_num, line in enumerate(lines, 1):
|
|
original_line = line
|
|
|
|
# Check if should preserve
|
|
if should_preserve(line, str(file_path)):
|
|
# Count preserved instances
|
|
for old in SPACING_REPLACEMENTS.keys():
|
|
if old in line:
|
|
preserved += 1
|
|
break
|
|
new_lines.append(line)
|
|
continue
|
|
|
|
# Replace non-8px spacing
|
|
line_replaced = False
|
|
for old, new in SPACING_REPLACEMENTS.items():
|
|
# Use word boundaries to avoid partial matches
|
|
pattern = r'\b' + re.escape(old) + r'\b'
|
|
if re.search(pattern, line):
|
|
line = re.sub(pattern, new, line)
|
|
if not line_replaced:
|
|
replaced += 1
|
|
line_replaced = True
|
|
|
|
new_lines.append(line)
|
|
|
|
new_content = '\n'.join(new_lines)
|
|
|
|
if not dry_run and new_content != original_content:
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
|
|
return replaced, preserved
|
|
|
|
except Exception as e:
|
|
print(f"Error processing {file_path}: {e}", file=sys.stderr)
|
|
return 0, 0
|
|
|
|
def main():
|
|
"""Main function"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description='Align spacing to 8px grid')
|
|
parser.add_argument('--dry-run', action='store_true', help='Show what would be changed without making changes')
|
|
parser.add_argument('--path', default='apps/web/src', help='Path to search (default: apps/web/src)')
|
|
args = parser.parse_args()
|
|
|
|
base_path = Path(args.path)
|
|
if not base_path.exists():
|
|
print(f"Error: Path {base_path} does not exist", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Find all TypeScript/TSX files
|
|
tsx_files = []
|
|
for ext in ['*.tsx', '*.ts']:
|
|
tsx_files.extend(base_path.rglob(ext))
|
|
|
|
# Filter out skipped files
|
|
files_to_process = []
|
|
for file_path in tsx_files:
|
|
if not any(pattern in str(file_path) for pattern in SKIP_PATTERNS):
|
|
files_to_process.append(file_path)
|
|
|
|
print(f"Found {len(files_to_process)} files to process")
|
|
if args.dry_run:
|
|
print("DRY RUN MODE - No files will be modified\n")
|
|
|
|
total_replaced = 0
|
|
total_preserved = 0
|
|
files_modified = []
|
|
|
|
for file_path in sorted(files_to_process):
|
|
replaced, preserved = replace_spacing(file_path, dry_run=args.dry_run)
|
|
if replaced > 0:
|
|
total_replaced += replaced
|
|
total_preserved += preserved
|
|
files_modified.append((file_path, replaced, preserved))
|
|
status = "[DRY RUN] Would modify" if args.dry_run else "Modified"
|
|
print(f"{status}: {file_path} ({replaced} replaced, {preserved} preserved)")
|
|
|
|
print(f"\n{'='*60}")
|
|
print(f"Total files {'would be ' if args.dry_run else ''}modified: {len(files_modified)}")
|
|
print(f"Total instances replaced: {total_replaced}")
|
|
print(f"Total instances preserved: {total_preserved}")
|
|
print(f"{'='*60}")
|
|
|
|
if args.dry_run and len(files_modified) > 0:
|
|
print("\nRun without --dry-run to apply changes")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|