Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github: Nadav011
custom: ["https://mcpize.com/mcp/rtl-fixer"]
28 changes: 28 additions & 0 deletions .github/workflows/security-scan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Security Scan

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: security-${{ github.ref }}
cancel-in-progress: true

jobs:
semgrep:
runs-on: [self-hosted, linux, x64, pop-os]
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
run: |
pip3 install --user semgrep 2>/dev/null || true
semgrep scan . --config=auto --error --severity ERROR

trivy:
runs-on: [self-hosted, linux, x64, pop-os]
steps:
- uses: actions/checkout@v4
- name: Trivy vulnerability scan
run: trivy fs . --severity HIGH,CRITICAL --exit-code 1
174 changes: 174 additions & 0 deletions CI/rtl-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
name: RTL Check

on:
pull_request:
paths:
- "**/*.tsx"
- "**/*.jsx"
- "**/*.css"
- "**/*.vue"

jobs:
rtl-lint:
name: RTL Logical Properties Check
runs-on: ubuntu-latest
permissions:
Comment on lines +1 to +15
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds a top-level CI/ directory even though the repo already has a ci/ directory. On case-sensitive systems this is two distinct paths, which is easy to confuse and can cause tooling/docs drift. Consider consolidating under the existing ci/ directory name (or placing the workflow directly under .github/workflows).

Copilot uses AI. Check for mistakes.
contents: read
pull-requests: write

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install ripgrep
run: |
sudo apt-get update -qq
sudo apt-get install -y ripgrep

- name: Scan for physical direction classes
id: rtl-scan
run: |
# Pattern: physical direction classes that must be replaced with logical equivalents
# Suppression: any line containing "// rtl-ok" is skipped
#
# Classes checked:
# ml-{n} mr-{n} pl-{n} pr-{n} — physical margin/padding
# text-left text-right — physical text alignment
# border-l- border-r- — physical border sides
# rounded-tl rounded-tr — physical border radius (corners)
# rounded-bl rounded-br
# float-left float-right — physical float
# left-{n} right-{n} — physical inset positioning (when directional)

PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|border-l-|border-r-|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Include left/right physical classes in RTL violation regex

The checker documentation and replacement hints state that left-*/right-* should be blocked, but the actual PATTERN does not include either token family. As a result, physical inset usage (e.g. left-0, right-4) is never reported and can merge unnoticed despite being a core RTL regression this workflow is meant to catch.

Useful? React with 👍 / 👎.

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scan comment says left-{n} / right-{n} are checked, but the PATTERN regex doesn’t include any \bleft- / \bright- terms (so directional positioning violations won’t be detected). If you intend to enforce these, add them to PATTERN and also add replacement hints in the report step (similar to ml/mr/etc.).

Suggested change
PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|border-l-|border-r-|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b'
PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|border-l-|border-r-|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b|\bleft-[0-9a-z\[]|\bright-[0-9a-z\[]'

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +43
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PATTERN flags border-l-/border-r- but not the valid Tailwind classes border-l / border-r (no trailing dash), and it doesn’t include other physical direction utilities mentioned elsewhere in the repo docs (e.g., rounded-l-* / rounded-r-*). If the goal is to block all physical direction classes, expand the regex to cover these variants too.

Suggested change
# ml-{n} mr-{n} pl-{n} pr-{n} — physical margin/padding
# text-left text-right — physical text alignment
# border-l- border-r- — physical border sides
# rounded-tl rounded-tr — physical border radius (corners)
# rounded-bl rounded-br
# float-left float-right — physical float
# left-{n} right-{n} — physical inset positioning (when directional)
PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|border-l-|border-r-|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b'
# ml-{n} mr-{n} pl-{n} pr-{n} — physical margin/padding
# text-left text-right — physical text alignment
# border-l border-r border-l-* border-r-* — physical border sides
# rounded-l rounded-r rounded-l-* rounded-r-* — physical border radius (sides)
# rounded-tl rounded-tr — physical border radius (corners)
# rounded-bl rounded-br
# float-left float-right — physical float
# left-{n} right-{n} — physical inset positioning (when directional)
PATTERN='\bml-[0-9a-z\[]|\bmr-[0-9a-z\[]|\bpl-[0-9a-z\[]|\bpr-[0-9a-z\[]|text-left\b|text-right\b|\bborder-l(?:\b|-)|\bborder-r(?:\b|-)|\brounded-l(?:\b|-)|\brounded-r(?:\b|-)|\brounded-tl\b|\brounded-tr\b|\brounded-bl\b|\brounded-br\b|float-left\b|float-right\b'

Copilot uses AI. Check for mistakes.

# Run ripgrep:
# - search only changed files in the PR (fallback: all source files)
# - exclude lines containing "rtl-ok" suppression comment
# - show file, line number, and matching content
VIOLATIONS=$(rg --glob "*.tsx" --glob "*.jsx" --glob "*.css" --glob "*.vue" \
--line-number \
--no-heading \
--color never \
"$PATTERN" \
src/ components/ pages/ app/ styles/ 2>/dev/null \
Comment on lines +49 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Scan all changed source paths instead of hardcoded folders

The lint step claims it checks PR changes, but the rg invocation only searches src/ components/ pages/ app/ styles/. In repositories that keep frontend code elsewhere (for example packages/* or apps/*), this produces an empty scan and the workflow reports success even when violating classes were introduced, so the merge gate can silently fail to enforce RTL rules.

Useful? React with 👍 / 👎.

| grep -v "rtl-ok" \
| grep -v "//.*rtl-ok" \
|| true)

Comment on lines +46 to +58
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rg is currently run only against src/ components/ pages/ app/ styles/, but this repository doesn’t have those directories, so the job will always report zero violations. Consider scanning the full repo (with appropriate excludes) or derive the changed file list from git diff and pass those paths to rg.

Suggested change
# - search only changed files in the PR (fallback: all source files)
# - exclude lines containing "rtl-ok" suppression comment
# - show file, line number, and matching content
VIOLATIONS=$(rg --glob "*.tsx" --glob "*.jsx" --glob "*.css" --glob "*.vue" \
--line-number \
--no-heading \
--color never \
"$PATTERN" \
src/ components/ pages/ app/ styles/ 2>/dev/null \
| grep -v "rtl-ok" \
| grep -v "//.*rtl-ok" \
|| true)
# - search only changed files in the PR (fallback: all matching files in the repo)
# - exclude lines containing "rtl-ok" suppression comment
# - show file, line number, and matching content
CHANGED_FILES=/tmp/rtl-changed-files.txt
git fetch --no-tags --depth=1 origin "${{ github.base_ref }}" || true
git diff --name-only "origin/${{ github.base_ref }}"...HEAD \
| grep -E '\.(tsx|jsx|css|vue)$' > "$CHANGED_FILES" || true
if [ -s "$CHANGED_FILES" ]; then
VIOLATIONS=$(xargs -r rg \
--line-number \
--no-heading \
--color never \
"$PATTERN" < "$CHANGED_FILES" \
| grep -v "rtl-ok" \
| grep -v "//.*rtl-ok" \
|| true)
else
VIOLATIONS=$(rg --glob "*.tsx" --glob "*.jsx" --glob "*.css" --glob "*.vue" \
--glob "!.git" \
--line-number \
--no-heading \
--color never \
"$PATTERN" \
. 2>/dev/null \
| grep -v "rtl-ok" \
| grep -v "//.*rtl-ok" \
|| true)
fi

Copilot uses AI. Check for mistakes.
if [ -z "$VIOLATIONS" ]; then
echo "violations_found=false" >> "$GITHUB_OUTPUT"
echo "violation_count=0" >> "$GITHUB_OUTPUT"
else
COUNT=$(echo "$VIOLATIONS" | wc -l | tr -d ' ')
echo "violations_found=true" >> "$GITHUB_OUTPUT"
echo "violation_count=$COUNT" >> "$GITHUB_OUTPUT"
# Store violations in a file for use in later steps
echo "$VIOLATIONS" > /tmp/rtl-violations.txt
fi

- name: Generate violation report
if: steps.rtl-scan.outputs.violations_found == 'true'
id: report
run: |
COUNT="${{ steps.rtl-scan.outputs.violation_count }}"
VIOLATIONS=$(cat /tmp/rtl-violations.txt)

echo "## RTL Violations Found — Fix Before Merge" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${COUNT} violation(s) detected.** Physical direction classes break Arabic and Hebrew layouts." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Violations" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

# Format each violation with the recommended replacement
while IFS= read -r line; do
FILE_LINE=$(echo "$line" | cut -d: -f1-2)
CONTENT=$(echo "$line" | cut -d: -f3-)

# Determine the replacement hint
HINT=""
if echo "$CONTENT" | grep -qE '\bml-'; then HINT="→ use ms-* (margin-inline-start)"; fi
if echo "$CONTENT" | grep -qE '\bmr-'; then HINT="→ use me-* (margin-inline-end)"; fi
if echo "$CONTENT" | grep -qE '\bpl-'; then HINT="→ use ps-* (padding-inline-start)"; fi
if echo "$CONTENT" | grep -qE '\bpr-'; then HINT="→ use pe-* (padding-inline-end)"; fi
if echo "$CONTENT" | grep -qE 'text-left'; then HINT="→ use text-start"; fi
if echo "$CONTENT" | grep -qE 'text-right'; then HINT="→ use text-end"; fi
if echo "$CONTENT" | grep -qE 'border-l-'; then HINT="→ use border-s-* (border-inline-start)"; fi
if echo "$CONTENT" | grep -qE 'border-r-'; then HINT="→ use border-e-* (border-inline-end)"; fi
if echo "$CONTENT" | grep -qE 'rounded-tl'; then HINT="→ use rounded-ss-* (border-start-start-radius)"; fi
if echo "$CONTENT" | grep -qE 'rounded-tr'; then HINT="→ use rounded-se-* (border-start-end-radius)"; fi
if echo "$CONTENT" | grep -qE 'rounded-bl'; then HINT="→ use rounded-es-* (border-end-start-radius)"; fi
if echo "$CONTENT" | grep -qE 'rounded-br'; then HINT="→ use rounded-ee-* (border-end-end-radius)"; fi
if echo "$CONTENT" | grep -qE 'float-left'; then HINT="→ use float-start"; fi
if echo "$CONTENT" | grep -qE 'float-right'; then HINT="→ use float-end"; fi

echo "${FILE_LINE} ${HINT}" >> $GITHUB_STEP_SUMMARY
done <<< "$VIOLATIONS"

echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### How to fix" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "# In Claude Code:" >> $GITHUB_STEP_SUMMARY
echo "/rtl-fix" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "# Or with sed (preview first):" >> $GITHUB_STEP_SUMMARY
echo "# grep -rn 'ml-[0-9]' src/ | grep -v rtl-ok" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "> To suppress a line intentionally: add \`// rtl-ok — reason\` comment. Requires \`dir=\"ltr\"\` on the element or ancestor." >> $GITHUB_STEP_SUMMARY

# Also write to console for visibility in raw logs
echo ""
echo "=========================================="
echo "RTL violations found — fix before merge:"
echo "=========================================="
echo ""
cat /tmp/rtl-violations.txt
echo ""
echo "${COUNT} violation(s). Run /rtl-fix to auto-convert."
echo "Add // rtl-ok to suppress intentionally directional lines."
echo ""

- name: Post PR comment on first violation
if: steps.rtl-scan.outputs.violations_found == 'true' && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const count = ${{ steps.rtl-scan.outputs.violation_count }};
const body = [
`## RTL Check Failed — ${count} violation(s) found`,
'',
'Physical direction classes (`ml-`, `mr-`, `pl-`, `pr-`, `text-left`, `text-right`, `border-l-`, `border-r-`, `float-left`, `float-right`) break Arabic and Hebrew layouts.',
'',
'**Fix:** Run `/rtl-fix` in Claude Code, or see the [full class mapping](https://github.com/${{ github.repository }}/blob/main/cheatsheet/tailwind-rtl.md).',
'',
'**Suppress a specific line** by adding `// rtl-ok — reason` comment (requires `dir="ltr"` on element or ancestor).',
'',
'See the **Checks** tab for the full violation list with line numbers.',
].join('\n');

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
Comment on lines +136 to +159
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step creates a new PR comment on every failing run (issues.createComment), which can spam PRs when commits are pushed or workflows are re-run. Consider updating an existing comment (e.g., search for a prior comment marker and edit it) or using a “sticky comment” approach so there’s only one up-to-date RTL-check comment per PR.

Copilot uses AI. Check for mistakes.

- name: Fail if violations found
if: steps.rtl-scan.outputs.violations_found == 'true'
run: |
echo "RTL check failed with ${{ steps.rtl-scan.outputs.violation_count }} violation(s)."
echo "See the step summary for the full list and fix instructions."
exit 1

- name: Pass — no violations
if: steps.rtl-scan.outputs.violations_found == 'false'
run: |
echo "RTL check passed — all direction classes use logical properties."
echo "## RTL Check Passed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All direction classes use logical properties. No physical direction classes detected." >> $GITHUB_STEP_SUMMARY
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2026 Nadav011
Copyright (c) 2026 Nadav Cohen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Loading
Loading