From ccf377fbf6c549e69b51d1cce796fe676566fbe2 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 08:48:32 -0700 Subject: [PATCH 01/33] chore(typing): exclude skill dirs from ty analysis --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 737eaca..50a5f77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -176,6 +176,12 @@ source = ["four_charm"] show_missing = true skip_covered = true +[tool.ty.src] +exclude = [ + ".agent/**", + ".shared/**", +] + [tool.ty.environment] extra-paths = [ "src", From 7bf806d7fbfbdc323333f781b9ca5c0f0d63cd72 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:32:11 -0700 Subject: [PATCH 02/33] chore(ci): enforce Ruff SSOT and quality gates Centralize Ruff config through Apps/ruff.toml, add app-local extend shim, and enforce CI guard against reintroducing [tool.ruff] blocks in pyproject.toml. Also aligns local formatting so ruff format --check passes. --- .github/workflows/ci.yml | 18 ++++- pyproject.toml | 142 --------------------------------- ruff.toml | 1 + src/four_charm/core/scraper.py | 12 ++- 4 files changed, 27 insertions(+), 146 deletions(-) create mode 100644 ruff.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 832dae4..02c3054 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,23 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt pip install -e . - pip install pytest pytest-cov + pip install ruff ty pytest pytest-cov + + - name: Guard Ruff SSOT + run: | + if grep -nE '^\[tool\.ruff(\..*)?\]$' pyproject.toml; then + echo "Ruff must be configured via Apps/ruff.toml only." + exit 1 + fi + + - name: Run Ruff lint + run: ruff check . + + - name: Run Ruff format check + run: ruff format --check . + + - name: Run ty + run: ty check - name: Run tests env: diff --git a/pyproject.toml b/pyproject.toml index 50a5f77..e1a5d6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,148 +22,6 @@ dependencies = [ "html5lib>=1.1", ] -# ============================================================================ -# RUFF CONFIGURATION - STANDARDIZED ACROSS ALL RAZORBACKROAR PROJECTS -# ============================================================================ -# This exact configuration should be mirrored in: -# - /Users/home/Workspace/Apps/.razorcore/pyproject.toml -# - /Users/home/Workspace/Apps/4Charm/pyproject.toml -# - /Users/home/Workspace/Apps/Nexus/pyproject.toml -# - /Users/home/Workspace/Apps/Papyrus/pyproject.toml -# ============================================================================ - -[tool.ruff] -# Line length matches PEP 8 and Black defaults -line-length = 88 - -# Target Python 3.13 specifically -target-version = "py313" - -# Source code locations (adjust per project) -src = ["src"] - -# Exclude common directories that should never be linted -exclude = [ - ".git", - ".venv", - "venv", - "__pycache__", - "*.pyc", - ".pytest_cache", - ".ruff_cache", - ".agent", - ".shared", - "build", - "dist", - "*.egg-info", - ".DS_Store", -] - -# ============================================================================ -# RULE SELECTION -# ============================================================================ -# Starting with essential rules, expand gradually as team agrees -# WHY: Too many rules at once = developer frustration and ignored warnings - -[tool.ruff.lint] -select = [ - # ESSENTIAL RULES - Always enforce these - "E", # pycodestyle errors (PEP 8 violations) - "F", # pyflakes (undefined names, unused imports, etc.) - "I", # isort (import sorting) - - # CODE QUALITY - Catch common bugs - "B", # flake8-bugbear (likely bugs and design problems) - "C4", # flake8-comprehensions (better list/dict/set comprehensions) - "UP", # pyupgrade (modernize Python code for your version) - - # STYLE - Keep code readable - "N", # pep8-naming (function/variable naming conventions) - "W", # pycodestyle warnings - - # DOCUMENTATION - Enforce good practices - "D", # pydocstyle (docstring conventions) -] - -# Rules to ignore (with explanations) -ignore = [ - "D100", # Missing docstring in public module - "D101", # Missing docstring in public class - "D102", # Missing docstring in public method - "D103", # Missing docstring in public function - "D104", # Missing docstring in public package - "D107", # Missing docstring in __init__ - # WHY: Docstrings are good but enforcing them everywhere is overwhelming - # TODO: Remove these ignores gradually as documentation improves - - "E501", # Line too long - # WHY: Ruff's formatter handles this automatically -] - -# ============================================================================ -# SPECIFIC RULE CONFIGURATIONS -# ============================================================================ - -[tool.ruff.lint.per-file-ignores] -# Test files don't need docstrings and can use assert statements -"tests/**/*.py" = ["D", "S101"] - -# __init__.py files often just import for convenience -"__init__.py" = ["F401"] - -[tool.ruff.lint.pydocstyle] -# Use Google-style docstrings (most popular in Python community) -convention = "google" -# WHY: Google style is clean and widely recognized -# Example: -# def function(param1: str, param2: int) -> bool: -# """Brief description. -# -# Longer description if needed. -# -# Args: -# param1: Description of param1. -# param2: Description of param2. -# -# Returns: -# Description of return value. -# """ - -[tool.ruff.lint.isort] -# Organize imports consistently -known-first-party = ["four_charm"] -# WHY: This tells isort that razorcore is your code, not a third-party library - -# Separate import groups with a blank line -lines-after-imports = 2 -# WHY: Makes import section clearly separated from code - -[tool.ruff.format] -# Use double quotes for strings (Python community standard) -quote-style = "double" - -# Use spaces for indentation (never tabs) -indent-style = "space" - -# Keep line endings Unix-style (LF not CRLF) -line-ending = "lf" -# WHY: macOS uses Unix-style line endings - -# ============================================================================ -# MIGRATION NOTES -# ============================================================================ -# HISTORY: Migrated from Pylint to Ruff (2025) -# REASON: Ruff is 10-100x faster, combines multiple tools (Pylint, Black, -# isort, Flake8, pyupgrade), and has better IDE integration -# -# REPLACED TOOLS: -# - Pylint → Ruff (linting) -# - Black → Ruff (formatting) -# - isort → Ruff (import sorting) -# - Flake8 → Ruff (style checking) -# - pyupgrade → Ruff (syntax modernization) -# ============================================================================ - [tool.pytest.ini_options] pythonpath = ["src"] testpaths = ["tests"] diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..b9f8af9 --- /dev/null +++ b/ruff.toml @@ -0,0 +1 @@ +extend = "../ruff.toml" diff --git a/src/four_charm/core/scraper.py b/src/four_charm/core/scraper.py index 8387c2c..0a8f544 100644 --- a/src/four_charm/core/scraper.py +++ b/src/four_charm/core/scraper.py @@ -160,7 +160,9 @@ def _update_stats_on_success(self, file_path: Path, media_file: MediaFile): finally: self.stats_mutex.unlock() - def _mark_download_cancelled(self, media_url: str, file_path: Path | None = None) -> bool: + def _mark_download_cancelled( + self, media_url: str, file_path: Path | None = None + ) -> bool: """Handle cancellation by cleaning up and marking queue item failed.""" if file_path and file_path.exists(): file_path.unlink(missing_ok=True) @@ -576,7 +578,9 @@ def download_file( for attempt in range(Config.MAX_RETRIES): try: - file_path, _save_dir = self._prepare_download_path(media_file, url_folder_name) + file_path, _save_dir = self._prepare_download_path( + media_file, url_folder_name + ) if self._check_existing_file(file_path, media_file): return True @@ -620,7 +624,9 @@ def download_file( elapsed = time.time() - media_file.start_time if elapsed > 0: - media_file.download_speed = downloaded_size / elapsed / 1024 / 1024 + media_file.download_speed = ( + downloaded_size / elapsed / 1024 / 1024 + ) if progress_callback and total_size > 0: progress = (downloaded_size / total_size) * 100 From 7b13a94767ecf6a56dd93ca59386a23ecf9a9b03 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:38:33 -0700 Subject: [PATCH 03/33] fix(ci): load canonical ruff config before lint Fetch shared Ruff config from razorcore so app-local ruff.toml extend=../ruff.toml resolves in standalone GitHub Actions checkouts. --- .github/workflows/ci.yml | 4 ++++ .github/workflows/ruff.yml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02c3054..12f797d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,10 @@ jobs: pip install -e . pip install ruff ty pytest pytest-cov + - name: Fetch canonical Ruff config + run: | + curl -fsSL https://raw.githubusercontent.com/RazorBackRoar/razorcore/main/src/razorcore/configs/ruff.toml -o ../ruff.toml + - name: Guard Ruff SSOT run: | if grep -nE '^\[tool\.ruff(\..*)?\]$' pyproject.toml; then diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index de65d79..7e1fdd5 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -19,6 +19,9 @@ jobs: python -m pip install --upgrade pip pip install ruff if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Fetch canonical Ruff config + run: | + curl -fsSL https://raw.githubusercontent.com/RazorBackRoar/razorcore/main/src/razorcore/configs/ruff.toml -o ../ruff.toml - name: Run Ruff run: | ruff check --force-exclude $(git ls-files '*.py') From fe7b21182b131d0b7c0ddfc9cdd53acd027e08ef Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:39:55 -0700 Subject: [PATCH 04/33] fix(ci): use local Ruff fallback for Actions Materialize ../ruff.toml from repo-local ruff.ci.toml so app-local ruff.toml extends resolve in standalone CI without cross-repo fetches. --- .github/workflows/ci.yml | 2 +- .github/workflows/ruff.yml | 2 +- ruff.ci.toml | 131 +++++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 ruff.ci.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12f797d..7cb5272 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Fetch canonical Ruff config run: | - curl -fsSL https://raw.githubusercontent.com/RazorBackRoar/razorcore/main/src/razorcore/configs/ruff.toml -o ../ruff.toml + cp ruff.ci.toml ../ruff.toml - name: Guard Ruff SSOT run: | diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 7e1fdd5..738f970 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -21,7 +21,7 @@ jobs: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Fetch canonical Ruff config run: | - curl -fsSL https://raw.githubusercontent.com/RazorBackRoar/razorcore/main/src/razorcore/configs/ruff.toml -o ../ruff.toml + cp ruff.ci.toml ../ruff.toml - name: Run Ruff run: | ruff check --force-exclude $(git ls-files '*.py') diff --git a/ruff.ci.toml b/ruff.ci.toml new file mode 100644 index 0000000..e103e46 --- /dev/null +++ b/ruff.ci.toml @@ -0,0 +1,131 @@ +# ============================================================================ +# RAZORBACKROAR ECOSYSTEM - RUFF CONFIGURATION (SSOT) +# ============================================================================ +# Location: /Users/home/Workspace/Apps/ruff.toml +# Purpose: Single source of truth for linting & formatting across all projects +# +# Ruff automatically discovers this file by walking up the directory tree, +# so all sub-projects (4Charm, Nexus, Papyrus, .razorcore) inherit these rules. +# +# Last Updated: 2026-02-03 +# Python Version: 3.13.x (STRICT LOCK) +# ============================================================================ + +# Line length matches PEP 8 and Black defaults +line-length = 88 + +# Target Python 3.13 specifically +target-version = "py313" + +# Source code locations +src = ["src"] + +# Exclude common directories that should never be linted +exclude = [ + ".git", + ".venv", + "venv", + "__pycache__", + "*.pyc", + ".pytest_cache", + ".ruff_cache", + "build", + "dist", + "*.egg-info", + ".DS_Store", + # Third-party skills and shared utility scripts (not core ecosystem code) + ".agent", + ".shared", +] + +# ============================================================================ +# RULE SELECTION +# ============================================================================ +# Essential rules that catch real bugs without overwhelming developers + +[lint] +select = [ + # ESSENTIAL RULES - Always enforce + "E", # pycodestyle errors (PEP 8 violations) + "F", # pyflakes (undefined names, unused imports, etc.) + "I", # isort (import sorting) + + # CODE QUALITY - Catch common bugs + "B", # flake8-bugbear (likely bugs and design problems) + "C4", # flake8-comprehensions (better list/dict/set comprehensions) + "UP", # pyupgrade (modernize Python code for your version) + + # STYLE - Keep code readable + "N", # pep8-naming (function/variable naming conventions) + "W", # pycodestyle warnings + + # DOCUMENTATION - Enforce good practices + "D", # pydocstyle (docstring conventions) +] + +# Rules to ignore (with explanations) +ignore = [ + "D100", # Missing docstring in public module + "D101", # Missing docstring in public class + "D102", # Missing docstring in public method + "D103", # Missing docstring in public function + "D104", # Missing docstring in public package + "D107", # Missing docstring in __init__ + # WHY: Docstrings are good but enforcing them everywhere is overwhelming + + "E501", # Line too long + # WHY: Ruff's formatter handles this automatically +] + +# ============================================================================ +# SPECIFIC RULE CONFIGURATIONS +# ============================================================================ + +[lint.per-file-ignores] +# Test files don't need docstrings and can use assert statements +"tests/**/*.py" = ["D", "S101", "B905"] + +# __init__.py files often just import for convenience +"__init__.py" = ["F401"] + +# Qt method overrides MUST use camelCase to match Qt's API +# (keyPressEvent, paintEvent, closeEvent, enterEvent, leaveEvent, etc.) +"**/ui/**/*.py" = ["N802"] +"**/gui/**/*.py" = ["N802"] +"**/styling/**/*.py" = ["N802"] +"**/widgets.py" = ["N802"] +"**/highlighter.py" = ["N802"] +"**/app.py" = ["N802"] +"**/window*.py" = ["N802"] + +# Entry point scripts often manipulate sys.path before imports +"**/main.py" = ["E402"] +"**/__main__.py" = ["E402"] +"**/scripts/*.py" = ["E402", "D"] + +[lint.pydocstyle] +# Use Google-style docstrings (most popular in Python community) +convention = "google" + +[lint.isort] +# Organize imports consistently +known-first-party = ["razorcore", "four_charm", "nexus", "papyrus"] +# WHY: Tells isort these are your packages, not third-party + +# Separate import groups with a blank line +lines-after-imports = 2 + +# ============================================================================ +# FORMATTING +# ============================================================================ + +[format] +# Use double quotes for strings (Python community standard) +quote-style = "double" + +# Use spaces for indentation (never tabs) +indent-style = "space" + +# Keep line endings Unix-style (LF not CRLF) +line-ending = "lf" +# WHY: macOS uses Unix-style line endings From 3c6087d31eb6268e487c9d2835dc5d75d6d9e2d9 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 10:45:44 -0700 Subject: [PATCH 05/33] fix(ci): create ty compatibility paths on macos Create the expected 3.13 site-packages paths before ty check so strict type checking works on GitHub macOS runners with workspace-pinned ty config. --- .github/workflows/ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7cb5272..539fdcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,6 +44,18 @@ jobs: - name: Run Ruff format check run: ruff format --check . + - name: Prepare ty search paths + run: | + SITE_PACKAGES="$(python - <<'PY' + import sysconfig + print(sysconfig.get_paths()['purelib']) + PY + )" + sudo mkdir -p /Users/home/Library/Python/3.13/lib/python + sudo ln -sfn "$SITE_PACKAGES" /Users/home/Library/Python/3.13/lib/python/site-packages + sudo mkdir -p /opt/homebrew/lib/python3.13 + sudo ln -sfn "$SITE_PACKAGES" /opt/homebrew/lib/python3.13/site-packages + - name: Run ty run: ty check From d54974d90f4fa5410bab97a1e7492cc53ce664e3 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:30:50 -0700 Subject: [PATCH 06/33] test: update AGENTS.md, README.md, main_window.py --- 4Charm.dmg | 3 ++ AGENTS.md | 6 ++-- README.md | 2 +- src/four_charm/gui/main_window.py | 56 ++++++++++++++++++++----------- tests/test_clipboard_paste.py | 51 ++++++++++++++++++++++++++++ 5 files changed, 94 insertions(+), 24 deletions(-) create mode 100644 4Charm.dmg create mode 100644 tests/test_clipboard_paste.py diff --git a/4Charm.dmg b/4Charm.dmg new file mode 100644 index 0000000..7fbd9a9 --- /dev/null +++ b/4Charm.dmg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbe9a13bc7a157031b87133692fa8a8420030294151954939b47e4c891072e8d +size 13461313 diff --git a/AGENTS.md b/AGENTS.md index 4f16a5a..e5f4f95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,7 @@ RATE_LIMIT_DELAY = 1.0 # Minimum 1 second between requests def fetch_thread(self, board: str, thread_id: int): try: response = requests.get( -f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> timeout=10 ) response.raise_for_status() @@ -109,8 +109,8 @@ time.sleep(wait_time) **Limits:** - **Global:** 1 request/second to 4chan API -- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) -- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) ### 3. Download Queue Architecture (BaseWorker Required) diff --git a/README.md b/README.md index aec065e..7d07788 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This project uses `.razorcore` for build tooling. ### Setup ```bash -git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> cd 4Charm pip install -r requirements.txt pip install -e ../.razorcore # Install build tools diff --git a/src/four_charm/gui/main_window.py b/src/four_charm/gui/main_window.py index 689e848..a4fb135 100644 --- a/src/four_charm/gui/main_window.py +++ b/src/four_charm/gui/main_window.py @@ -55,6 +55,7 @@ from datetime import datetime from pathlib import Path from typing import override +from urllib.parse import urlparse from PySide6.QtCore import Qt, QThread, QTimer from PySide6.QtGui import ( @@ -90,6 +91,35 @@ logger = logging.getLogger("4Charm") +def _is_4chan_host(url: str) -> bool: + """Return True when the URL host matches an allowed 4chan domain.""" + try: + parsed = urlparse(url) + except Exception: + return False + + host = (parsed.netloc or "").split(":", 1)[0].lower() + if host.startswith("www."): + host = host[4:] + return host in {"boards.4chan.org", "4chan.org", "4channel.org"} + + +def _format_clipboard_paste_text(raw_text: str, position_in_block: int) -> str: + """Normalize pasted text for URL input and always end on a new line.""" + if not raw_text: + return "" + + urls = re.findall(r"https?://[^\s]+", raw_text) + valid_urls = [url for url in urls if _is_4chan_host(url)] + + paste_text = "\n".join(valid_urls) if valid_urls else raw_text + if position_in_block > 0 and not paste_text.startswith("\n"): + paste_text = "\n" + paste_text + if not paste_text.endswith("\n"): + paste_text += "\n" + return paste_text + + class MainWindow(QMainWindow): """Main application window.""" @@ -803,27 +833,13 @@ def paste_from_clipboard(self): if not text: return - # Extract URLs using regex - urls = re.findall(r"https?://[^\s]+", text) - valid_urls = [ - url for url in urls if "boards.4chan.org" in url or "4chan.org" in url - ] - - if valid_urls: - # If we found valid URLs, paste them nicely formatted - # Join with newlines - paste_text = "\n".join(valid_urls) + "\n" - - cursor = self.url_input.textCursor() - # If we're not at the start of a line, add a newline first - if cursor.positionInBlock() > 0: - paste_text = "\n" + paste_text + cursor = self.url_input.textCursor() + paste_text = _format_clipboard_paste_text(text, cursor.positionInBlock()) + if not paste_text: + return - cursor.insertText(paste_text) - self.url_input.setTextCursor(cursor) - else: - # Fallback: Normal paste if no valid URLs found - self.url_input.paste() + cursor.insertText(paste_text) + self.url_input.setTextCursor(cursor) # Ensure everything is visible and validated self.url_input.ensureCursorVisible() diff --git a/tests/test_clipboard_paste.py b/tests/test_clipboard_paste.py new file mode 100644 index 0000000..ed22048 --- /dev/null +++ b/tests/test_clipboard_paste.py @@ -0,0 +1,51 @@ +"""Clipboard paste formatting behavior for URL input.""" + +from four_charm.gui.main_window import _format_clipboard_paste_text, _is_4chan_host + + +def test_is_4chan_host_accepts_allowed_domains() -> None: + """Accept canonical 4chan hosts including www and 4channel.""" + assert _is_4chan_host("https://boards.4chan.org/g/thread/123") + assert _is_4chan_host("https://www.4chan.org/") + assert _is_4chan_host("https://4channel.org/") + + +def test_is_4chan_host_rejects_spoofed_hostnames() -> None: + """Reject hosts that only contain 4chan as a substring.""" + assert not _is_4chan_host("https://not4chan.org/boards.4chan.org/g/thread/123") + assert not _is_4chan_host("https://4chan.org.evil.com/") + + +def test_paste_format_always_appends_newline() -> None: + """Ensure paste content ends on a new line.""" + result = _format_clipboard_paste_text( + "https://boards.4chan.org/g/thread/123", position_in_block=0 + ) + assert result == "https://boards.4chan.org/g/thread/123\n" + + +def test_paste_format_adds_leading_newline_mid_line() -> None: + """When cursor is mid-line, start pasted content on the next line.""" + result = _format_clipboard_paste_text( + "https://boards.4chan.org/g/thread/123", position_in_block=5 + ) + assert result == "\nhttps://boards.4chan.org/g/thread/123\n" + + +def test_paste_format_filters_urls_and_preserves_newline() -> None: + """Extract and keep only valid 4chan URLs when URLs are present.""" + raw = ( + "https://not4chan.org/thread/1\n" + "https://boards.4chan.org/g/thread/2\n" + "https://4channel.org/v/thread/3" + ) + result = _format_clipboard_paste_text(raw, position_in_block=0) + assert result == ( + "https://boards.4chan.org/g/thread/2\nhttps://4channel.org/v/thread/3\n" + ) + + +def test_paste_format_keeps_raw_text_when_no_urls() -> None: + """If no URLs exist, preserve text and still end with a newline.""" + result = _format_clipboard_paste_text("just plain text", position_in_block=0) + assert result == "just plain text\n" From da4613c06131c2890ac633cd2f2271b73133578b Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:31:06 -0700 Subject: [PATCH 07/33] chore: bump version to 4.9.1 --- pyproject.toml | 2 +- src/four_charm/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e1a5d6d..3742ab5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ [project] name = "four-charm" -version = "4.9.0" +version = "4.9.1" description = "4Charm desktop app for bulk 4chan media downloads" requires-python = "==3.13.*" # WHY: Python 3.13.x is locked per IDE_CONTEXT.md requirements diff --git a/src/four_charm/__init__.py b/src/four_charm/__init__.py index 1b55e2b..0bfa73d 100644 --- a/src/four_charm/__init__.py +++ b/src/four_charm/__init__.py @@ -1,3 +1,3 @@ """4Charm - Advanced 4chan Media Downloader.""" -__version__ = "4.9.0" +__version__ = "4.9.1" From cce46a5ec9724a5b71884860d4d5eb6fa94a8f2e Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:31:49 -0700 Subject: [PATCH 08/33] chore: sync version docs --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index e5f4f95..0d76d32 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ Level 2 Document: Refer to /Users/home/Workspace/Apps/AGENTS.md (Level 1) for gl # 🍀 4Charm - 4chan Media Downloader Agent **Package:** `four_charm` -**Version:** 4.9.0 +**Version:** 4.9.1 **Context Level:** LEVEL 3 (Application-Specific) --- From 1c669ded82e25a4d4b58662d27d0042416761354 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 00:39:03 -0700 Subject: [PATCH 09/33] fix(gui): use strict host validation in URL checks --- src/four_charm/gui/main_window.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/four_charm/gui/main_window.py b/src/four_charm/gui/main_window.py index a4fb135..e47e060 100644 --- a/src/four_charm/gui/main_window.py +++ b/src/four_charm/gui/main_window.py @@ -567,13 +567,12 @@ def validate_urls(self): self.start_cancel_btn.setEnabled(False) return - # Simple string check for speed (avoid creating Scraper object repeatedly in UI thread) + # Use the same hostname validation logic as paste/drop handlers. valid_count = 0 invalid_count = 0 for url in raw_lines: - # Basic check for 4chan domains - if "boards.4chan.org" in url or "4channel.org" in url or "4chan.org" in url: + if _is_4chan_host(url): valid_count += 1 else: invalid_count += 1 @@ -865,9 +864,7 @@ def dragEnterEvent(self, event: QDragEnterEvent) -> None: def dropEvent(self, event: QDropEvent) -> None: text = event.mimeData().text().strip() urls = re.findall(r"https?://[^\s]+", text) - valid_urls = [ - url for url in urls if "boards.4chan.org" in url or "4chan.org" in url - ] + valid_urls = [url for url in urls if _is_4chan_host(url)] if valid_urls: # Just add URLs, no numbering self.url_input.setPlainText("\n".join(valid_urls)) From 673526974a5c5ba981a7af37e87519511324c000 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 02:16:49 -0700 Subject: [PATCH 10/33] chore: update AGENTS.md, README.md --- AGENTS.md | 8 ++++---- README.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 0d76d32..bcd9434 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,7 @@ When opening this project/workspace, load context in this order: ### ⚡ Performance Optimization (Bolt) - **Agent:** Bolt ⚡ -- **Activation:** `bolt` (alias `snake`) or `razorcore bolt` +- **Activation:** `bolt` or `razorcore bolt` - **Goal:** < 2s startup, < 50MB bundle - **Journal:** `.razorcore/bolt/journal.md` @@ -82,7 +82,7 @@ RATE_LIMIT_DELAY = 1.0 # Minimum 1 second between requests def fetch_thread(self, board: str, thread_id: int): try: response = requests.get( -f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> timeout=10 ) response.raise_for_status() @@ -109,8 +109,8 @@ time.sleep(wait_time) **Limits:** - **Global:** 1 request/second to 4chan API -- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) -- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) ### 3. Download Queue Architecture (BaseWorker Required) diff --git a/README.md b/README.md index 7d07788..21641e6 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This project uses `.razorcore` for build tooling. ### Setup ```bash -git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> cd 4Charm pip install -r requirements.txt pip install -e ../.razorcore # Install build tools From ad8b74de705fad060b5e2dc3a24e8bfa75b87030 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 02:16:55 -0700 Subject: [PATCH 11/33] chore: bump version to 4.9.2 --- pyproject.toml | 2 +- src/four_charm/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3742ab5..e9a409d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ [project] name = "four-charm" -version = "4.9.1" +version = "4.9.2" description = "4Charm desktop app for bulk 4chan media downloads" requires-python = "==3.13.*" # WHY: Python 3.13.x is locked per IDE_CONTEXT.md requirements diff --git a/src/four_charm/__init__.py b/src/four_charm/__init__.py index 0bfa73d..dc8d71b 100644 --- a/src/four_charm/__init__.py +++ b/src/four_charm/__init__.py @@ -1,3 +1,3 @@ """4Charm - Advanced 4chan Media Downloader.""" -__version__ = "4.9.1" +__version__ = "4.9.2" From d067400a582e1f90aaf094526879bba9bf3bf957 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 02:17:23 -0700 Subject: [PATCH 12/33] chore: sync version docs --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index bcd9434..576047b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ Level 2 Document: Refer to /Users/home/Workspace/Apps/AGENTS.md (Level 1) for gl # 🍀 4Charm - 4chan Media Downloader Agent **Package:** `four_charm` -**Version:** 4.9.1 +**Version:** 4.9.2 **Context Level:** LEVEL 3 (Application-Specific) --- From 30305c8978819fd298ba53c2bcced514ad568ede Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:21:18 -0700 Subject: [PATCH 13/33] chore: update .gitignore, AGENTS.md, README.md --- .gitignore | 3 +++ AGENTS.md | 6 +++--- README.md | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 80caf56..4d4034d 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,9 @@ temp-dmg-* .venv/ venv/ +# Ty cache +.ty/ + # IDE .vscode/ .idea/ diff --git a/AGENTS.md b/AGENTS.md index 576047b..d1d8a39 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,7 @@ RATE_LIMIT_DELAY = 1.0 # Minimum 1 second between requests def fetch_thread(self, board: str, thread_id: int): try: response = requests.get( -f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> timeout=10 ) response.raise_for_status() @@ -109,8 +109,8 @@ time.sleep(wait_time) **Limits:** - **Global:** 1 request/second to 4chan API -- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) -- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) ### 3. Download Queue Architecture (BaseWorker Required) diff --git a/README.md b/README.md index 21641e6..508ee79 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This project uses `.razorcore` for build tooling. ### Setup ```bash -git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> cd 4Charm pip install -r requirements.txt pip install -e ../.razorcore # Install build tools From ece3432e1f55ad3c655570bde5b8a25174396b50 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:21:27 -0700 Subject: [PATCH 14/33] chore: bump version to 4.9.3 --- pyproject.toml | 2 +- src/four_charm/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e9a409d..8ec37ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ [project] name = "four-charm" -version = "4.9.2" +version = "4.9.3" description = "4Charm desktop app for bulk 4chan media downloads" requires-python = "==3.13.*" # WHY: Python 3.13.x is locked per IDE_CONTEXT.md requirements diff --git a/src/four_charm/__init__.py b/src/four_charm/__init__.py index dc8d71b..631974f 100644 --- a/src/four_charm/__init__.py +++ b/src/four_charm/__init__.py @@ -1,3 +1,3 @@ """4Charm - Advanced 4chan Media Downloader.""" -__version__ = "4.9.2" +__version__ = "4.9.3" From 238e2027a84c451a52d5b25564bb7e723df96d85 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:22:05 -0700 Subject: [PATCH 15/33] chore: sync version docs --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index d1d8a39..f5a2af8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ Level 2 Document: Refer to /Users/home/Workspace/Apps/AGENTS.md (Level 1) for gl # 🍀 4Charm - 4chan Media Downloader Agent **Package:** `four_charm` -**Version:** 4.9.2 +**Version:** 4.9.3 **Context Level:** LEVEL 3 (Application-Specific) --- From 64d45662f6dbfa51d3833f01e56a150e67770502 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:27:22 -0700 Subject: [PATCH 16/33] chore: update .gitignore, AGENTS.md, README.md --- .gitignore | 4 ++++ AGENTS.md | 6 +++--- README.md | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 4d4034d..cd8e95f 100644 --- a/.gitignore +++ b/.gitignore @@ -37,11 +37,15 @@ venv/ # Ty cache .ty/ +# Personal documentation (IDE-hidden) +/Users/home/Workspace/Documentation/ + # IDE .vscode/ .idea/ # Secrets +.env .env.* !.env.example !.env.sample diff --git a/AGENTS.md b/AGENTS.md index f5a2af8..0811e77 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,7 +82,7 @@ RATE_LIMIT_DELAY = 1.0 # Minimum 1 second between requests def fetch_thread(self, board: str, thread_id: int): try: response = requests.get( -f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +f"<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> timeout=10 ) response.raise_for_status() @@ -109,8 +109,8 @@ time.sleep(wait_time) **Limits:** - **Global:** 1 request/second to 4chan API -- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) -- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Thread Fetching:** Use CDN URLs (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) +- **Media Downloads:** Use media CDN (`<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>`) ### 3. Download Queue Architecture (BaseWorker Required) diff --git a/README.md b/README.md index 508ee79..3f00934 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This project uses `.razorcore` for build tooling. ### Setup ```bash -git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +git clone <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> cd 4Charm pip install -r requirements.txt pip install -e ../.razorcore # Install build tools From f45692614fb0e1b8de1ba7c26aa12f9f19fb29e4 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:27:28 -0700 Subject: [PATCH 17/33] chore: bump version to 4.9.4 --- pyproject.toml | 2 +- src/four_charm/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8ec37ed..9d8b6a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ [project] name = "four-charm" -version = "4.9.3" +version = "4.9.4" description = "4Charm desktop app for bulk 4chan media downloads" requires-python = "==3.13.*" # WHY: Python 3.13.x is locked per IDE_CONTEXT.md requirements diff --git a/src/four_charm/__init__.py b/src/four_charm/__init__.py index 631974f..8b33992 100644 --- a/src/four_charm/__init__.py +++ b/src/four_charm/__init__.py @@ -1,3 +1,3 @@ """4Charm - Advanced 4chan Media Downloader.""" -__version__ = "4.9.3" +__version__ = "4.9.4" From 87a11585e6798e1a53b70e3e991aa72ea17b3cde Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Thu, 19 Feb 2026 05:27:58 -0700 Subject: [PATCH 18/33] chore: sync version docs --- AGENTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AGENTS.md b/AGENTS.md index 0811e77..ecca507 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -3,7 +3,7 @@ Level 2 Document: Refer to /Users/home/Workspace/Apps/AGENTS.md (Level 1) for gl # 🍀 4Charm - 4chan Media Downloader Agent **Package:** `four_charm` -**Version:** 4.9.3 +**Version:** 4.9.4 **Context Level:** LEVEL 3 (Application-Specific) --- From f07e6caa7e75b0dc62d3aef769f3f30c042b69a1 Mon Sep 17 00:00:00 2001 From: RazorBackRoar <184320956+RazorBackRoar@users.noreply.github.com> Date: Sat, 21 Feb 2026 01:43:56 -0700 Subject: [PATCH 19/33] test: update core.py, design_system.py, search.py and 7 more --- .agent/skills/ui-ux-pro-max/scripts/core.py | 29 +- .../ui-ux-pro-max/scripts/design_system.py | 272 +++-- .agent/skills/ui-ux-pro-max/scripts/search.py | 31 +- .codex/skills/ui-ux-pro-max/SKILL.md | 297 +++++ .codex/skills/ui-ux-pro-max/data/charts.csv | 26 + .codex/skills/ui-ux-pro-max/data/colors.csv | 97 ++ .codex/skills/ui-ux-pro-max/data/icons.csv | 101 ++ .codex/skills/ui-ux-pro-max/data/landing.csv | 31 + .codex/skills/ui-ux-pro-max/data/products.csv | 97 ++ .../ui-ux-pro-max/data/react-performance.csv | 45 + .../ui-ux-pro-max/data/stacks/astro.csv | 54 + .../ui-ux-pro-max/data/stacks/flutter.csv | 53 + .../data/stacks/html-tailwind.csv | 56 + .../data/stacks/jetpack-compose.csv | 53 + .../ui-ux-pro-max/data/stacks/nextjs.csv | 53 + .../ui-ux-pro-max/data/stacks/nuxt-ui.csv | 51 + .../ui-ux-pro-max/data/stacks/nuxtjs.csv | 59 + .../data/stacks/react-native.csv | 52 + .../ui-ux-pro-max/data/stacks/react.csv | 54 + .../ui-ux-pro-max/data/stacks/shadcn.csv | 61 + .../ui-ux-pro-max/data/stacks/svelte.csv | 54 + .../ui-ux-pro-max/data/stacks/swiftui.csv | 51 + .../skills/ui-ux-pro-max/data/stacks/vue.csv | 50 + .codex/skills/ui-ux-pro-max/data/styles.csv | 68 ++ .../skills/ui-ux-pro-max/data/typography.csv | 58 + .../ui-ux-pro-max/data/ui-reasoning.csv | 101 ++ .../ui-ux-pro-max/data/ux-guidelines.csv | 100 ++ .../ui-ux-pro-max/data/web-interface.csv | 31 + .codex/skills/ui-ux-pro-max/scripts/core.py | 253 ++++ .../ui-ux-pro-max/scripts/design_system.py | 1067 +++++++++++++++++ .codex/skills/ui-ux-pro-max/scripts/search.py | 114 ++ .gitignore | 3 + .skills/ui-ux-pro-max | 1 + .ui-ux-pro-max/skills/ui-ux-pro-max/SKILL.md | 293 +++++ .../skills/ui-ux-pro-max/data/charts.csv | 26 + .../skills/ui-ux-pro-max/data/colors.csv | 97 ++ .../skills/ui-ux-pro-max/data/icons.csv | 101 ++ .../skills/ui-ux-pro-max/data/landing.csv | 31 + .../skills/ui-ux-pro-max/data/products.csv | 97 ++ .../ui-ux-pro-max/data/react-performance.csv | 45 + .../ui-ux-pro-max/data/stacks/astro.csv | 54 + .../ui-ux-pro-max/data/stacks/flutter.csv | 53 + .../data/stacks/html-tailwind.csv | 56 + .../data/stacks/jetpack-compose.csv | 53 + .../ui-ux-pro-max/data/stacks/nextjs.csv | 53 + .../ui-ux-pro-max/data/stacks/nuxt-ui.csv | 51 + .../ui-ux-pro-max/data/stacks/nuxtjs.csv | 59 + .../data/stacks/react-native.csv | 52 + .../ui-ux-pro-max/data/stacks/react.csv | 54 + .../ui-ux-pro-max/data/stacks/shadcn.csv | 61 + .../ui-ux-pro-max/data/stacks/svelte.csv | 54 + .../ui-ux-pro-max/data/stacks/swiftui.csv | 51 + .../skills/ui-ux-pro-max/data/stacks/vue.csv | 50 + .../skills/ui-ux-pro-max/data/styles.csv | 68 ++ .../skills/ui-ux-pro-max/data/typography.csv | 58 + .../ui-ux-pro-max/data/ui-reasoning.csv | 101 ++ .../ui-ux-pro-max/data/ux-guidelines.csv | 100 ++ .../ui-ux-pro-max/data/web-interface.csv | 31 + .../skills/ui-ux-pro-max/scripts/core.py | 253 ++++ .../ui-ux-pro-max/scripts/design_system.py | 1067 +++++++++++++++++ .../skills/ui-ux-pro-max/scripts/search.py | 114 ++ .../workflows/ui-ux-pro-max.md | 0 .windsurf | 1 + 4Charm.dmg | 4 +- AGENTS.md | 410 +------ README.md | 2 +- pyrightconfig.json | 1 + tests/conftest.py | 21 + tests/test_clipboard_paste.py | 0 tests/test_scraper_logic.py | 0 70 files changed, 6634 insertions(+), 511 deletions(-) create mode 100644 .codex/skills/ui-ux-pro-max/SKILL.md create mode 100644 .codex/skills/ui-ux-pro-max/data/charts.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/colors.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/icons.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/landing.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/products.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/react-performance.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/astro.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/flutter.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/nextjs.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/react-native.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/react.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/shadcn.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/svelte.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/swiftui.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/stacks/vue.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/styles.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/typography.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/ui-reasoning.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/ux-guidelines.csv create mode 100644 .codex/skills/ui-ux-pro-max/data/web-interface.csv create mode 100644 .codex/skills/ui-ux-pro-max/scripts/core.py create mode 100644 .codex/skills/ui-ux-pro-max/scripts/design_system.py create mode 100644 .codex/skills/ui-ux-pro-max/scripts/search.py create mode 120000 .skills/ui-ux-pro-max create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/SKILL.md create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/charts.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/colors.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/icons.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/landing.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/products.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/react-performance.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/astro.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/flutter.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nextjs.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/react-native.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/react.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/shadcn.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/svelte.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/swiftui.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/stacks/vue.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/styles.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/typography.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/ui-reasoning.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/ux-guidelines.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/data/web-interface.csv create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/scripts/core.py create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/scripts/design_system.py create mode 100644 .ui-ux-pro-max/skills/ui-ux-pro-max/scripts/search.py rename {.windsurf => .ui-ux-pro-max}/workflows/ui-ux-pro-max.md (100%) create mode 120000 .windsurf mode change 100644 => 120000 AGENTS.md create mode 100644 tests/conftest.py mode change 100644 => 100755 tests/test_clipboard_paste.py mode change 100644 => 100755 tests/test_scraper_logic.py diff --git a/.agent/skills/ui-ux-pro-max/scripts/core.py b/.agent/skills/ui-ux-pro-max/scripts/core.py index 69607ad..b7ba227 100644 --- a/.agent/skills/ui-ux-pro-max/scripts/core.py +++ b/.agent/skills/ui-ux-pro-max/scripts/core.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 -"""UI/UX Pro Max Core - BM25 search engine for UI/UX style guides. +# -*- coding: utf-8 -*- +""" +UI/UX Pro Max Core - BM25 search engine for UI/UX style guides """ import csv import re -from collections import defaultdict -from math import log from pathlib import Path - +from math import log +from collections import defaultdict # ============ CONFIGURATION ============ DATA_DIR = Path(__file__).parent.parent / "data" @@ -93,7 +94,7 @@ # ============ BM25 IMPLEMENTATION ============ class BM25: - """BM25 ranking algorithm for text search.""" + """BM25 ranking algorithm for text search""" def __init__(self, k1=1.5, b=0.75): self.k1 = k1 @@ -106,12 +107,12 @@ def __init__(self, k1=1.5, b=0.75): self.N = 0 def tokenize(self, text): - """Lowercase, split, remove punctuation, filter short words.""" + """Lowercase, split, remove punctuation, filter short words""" text = re.sub(r'[^\w\s]', ' ', str(text).lower()) return [w for w in text.split() if len(w) > 2] def fit(self, documents): - """Build BM25 index from documents.""" + """Build BM25 index from documents""" self.corpus = [self.tokenize(doc) for doc in documents] self.N = len(self.corpus) if self.N == 0: @@ -130,7 +131,7 @@ def fit(self, documents): self.idf[word] = log((self.N - freq + 0.5) / (freq + 0.5) + 1) def score(self, query): - """Score all documents against query.""" + """Score all documents against query""" query_tokens = self.tokenize(query) scores = [] @@ -156,13 +157,13 @@ def score(self, query): # ============ SEARCH FUNCTIONS ============ def _load_csv(filepath): - """Load CSV and return list of dicts.""" - with open(filepath, encoding='utf-8') as f: + """Load CSV and return list of dicts""" + with open(filepath, 'r', encoding='utf-8') as f: return list(csv.DictReader(f)) def _search_csv(filepath, search_cols, output_cols, query, max_results): - """Core search function using BM25.""" + """Core search function using BM25""" if not filepath.exists(): return [] @@ -187,7 +188,7 @@ def _search_csv(filepath, search_cols, output_cols, query, max_results): def detect_domain(query): - """Auto-detect the most relevant domain from query.""" + """Auto-detect the most relevant domain from query""" query_lower = query.lower() domain_keywords = { @@ -209,7 +210,7 @@ def detect_domain(query): def search(query, domain=None, max_results=MAX_RESULTS): - """Main search function with auto-domain detection.""" + """Main search function with auto-domain detection""" if domain is None: domain = detect_domain(query) @@ -231,7 +232,7 @@ def search(query, domain=None, max_results=MAX_RESULTS): def search_stack(query, stack, max_results=MAX_RESULTS): - """Search stack-specific guidelines.""" + """Search stack-specific guidelines""" if stack not in STACK_CONFIG: return {"error": f"Unknown stack: {stack}. Available: {', '.join(AVAILABLE_STACKS)}"} diff --git a/.agent/skills/ui-ux-pro-max/scripts/design_system.py b/.agent/skills/ui-ux-pro-max/scripts/design_system.py index a53f9b0..209de20 100644 --- a/.agent/skills/ui-ux-pro-max/scripts/design_system.py +++ b/.agent/skills/ui-ux-pro-max/scripts/design_system.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 -"""Design System Generator - Aggregates search results and applies reasoning +# -*- coding: utf-8 -*- +""" +Design System Generator - Aggregates search results and applies reasoning to generate comprehensive design system recommendations. Usage: @@ -13,11 +15,10 @@ import csv import json +import os from datetime import datetime from pathlib import Path - -from core import DATA_DIR, search -from razorcore.ascii_box import format_design_system_ascii_box +from core import search, DATA_DIR # ============ CONFIGURATION ============ @@ -44,7 +45,7 @@ def _load_reasoning(self) -> list: filepath = DATA_DIR / REASONING_FILE if not filepath.exists(): return [] - with open(filepath, encoding='utf-8') as f: + with open(filepath, 'r', encoding='utf-8') as f: return list(csv.DictReader(f)) def _multi_domain_search(self, query: str, style_priority: list = None) -> dict: @@ -240,7 +241,127 @@ def generate(self, query: str, project_name: str = None) -> dict: def format_ascii_box(design_system: dict) -> str: """Format design system as ASCII box with emojis (MCP-style).""" - return format_design_system_ascii_box(design_system, box_width=BOX_WIDTH) + project = design_system.get("project_name", "PROJECT") + pattern = design_system.get("pattern", {}) + style = design_system.get("style", {}) + colors = design_system.get("colors", {}) + typography = design_system.get("typography", {}) + effects = design_system.get("key_effects", "") + anti_patterns = design_system.get("anti_patterns", "") + + def wrap_text(text: str, prefix: str, width: int) -> list: + """Wrap long text into multiple lines.""" + if not text: + return [] + words = text.split() + lines = [] + current_line = prefix + for word in words: + if len(current_line) + len(word) + 1 <= width - 2: + current_line += (" " if current_line != prefix else "") + word + else: + if current_line != prefix: + lines.append(current_line) + current_line = prefix + word + if current_line != prefix: + lines.append(current_line) + return lines + + # Build sections from pattern + sections = pattern.get("sections", "").split(">") + sections = [s.strip() for s in sections if s.strip()] + + # Build output lines + lines = [] + w = BOX_WIDTH - 1 + + lines.append("+" + "-" * w + "+") + lines.append(f"| TARGET: {project} - RECOMMENDED DESIGN SYSTEM".ljust(BOX_WIDTH) + "|") + lines.append("+" + "-" * w + "+") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Pattern section + lines.append(f"| PATTERN: {pattern.get('name', '')}".ljust(BOX_WIDTH) + "|") + if pattern.get('conversion'): + lines.append(f"| Conversion: {pattern.get('conversion', '')}".ljust(BOX_WIDTH) + "|") + if pattern.get('cta_placement'): + lines.append(f"| CTA: {pattern.get('cta_placement', '')}".ljust(BOX_WIDTH) + "|") + lines.append("| Sections:".ljust(BOX_WIDTH) + "|") + for i, section in enumerate(sections, 1): + lines.append(f"| {i}. {section}".ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Style section + lines.append(f"| STYLE: {style.get('name', '')}".ljust(BOX_WIDTH) + "|") + if style.get("keywords"): + for line in wrap_text(f"Keywords: {style.get('keywords', '')}", "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + if style.get("best_for"): + for line in wrap_text(f"Best For: {style.get('best_for', '')}", "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + if style.get("performance") or style.get("accessibility"): + perf_a11y = f"Performance: {style.get('performance', '')} | Accessibility: {style.get('accessibility', '')}" + lines.append(f"| {perf_a11y}".ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Colors section + lines.append("| COLORS:".ljust(BOX_WIDTH) + "|") + lines.append(f"| Primary: {colors.get('primary', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"| Secondary: {colors.get('secondary', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"| CTA: {colors.get('cta', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"| Background: {colors.get('background', '')}".ljust(BOX_WIDTH) + "|") + lines.append(f"| Text: {colors.get('text', '')}".ljust(BOX_WIDTH) + "|") + if colors.get("notes"): + for line in wrap_text(f"Notes: {colors.get('notes', '')}", "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Typography section + lines.append(f"| TYPOGRAPHY: {typography.get('heading', '')} / {typography.get('body', '')}".ljust(BOX_WIDTH) + "|") + if typography.get("mood"): + for line in wrap_text(f"Mood: {typography.get('mood', '')}", "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + if typography.get("best_for"): + for line in wrap_text(f"Best For: {typography.get('best_for', '')}", "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + if typography.get("google_fonts_url"): + lines.append(f"| Google Fonts: {typography.get('google_fonts_url', '')}".ljust(BOX_WIDTH) + "|") + if typography.get("css_import"): + lines.append(f"| CSS Import: {typography.get('css_import', '')[:70]}...".ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Key Effects section + if effects: + lines.append("| KEY EFFECTS:".ljust(BOX_WIDTH) + "|") + for line in wrap_text(effects, "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Anti-patterns section + if anti_patterns: + lines.append("| AVOID (Anti-patterns):".ljust(BOX_WIDTH) + "|") + for line in wrap_text(anti_patterns, "| ", BOX_WIDTH): + lines.append(line.ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + # Pre-Delivery Checklist section + lines.append("| PRE-DELIVERY CHECKLIST:".ljust(BOX_WIDTH) + "|") + checklist_items = [ + "[ ] No emojis as icons (use SVG: Heroicons/Lucide)", + "[ ] cursor-pointer on all clickable elements", + "[ ] Hover states with smooth transitions (150-300ms)", + "[ ] Light mode: text contrast 4.5:1 minimum", + "[ ] Focus states visible for keyboard nav", + "[ ] prefers-reduced-motion respected", + "[ ] Responsive: 375px, 768px, 1024px, 1440px" + ] + for item in checklist_items: + lines.append(f"| {item}".ljust(BOX_WIDTH) + "|") + lines.append("|" + " " * BOX_WIDTH + "|") + + lines.append("+" + "-" * w + "+") + + return "\n".join(lines) def format_markdown(design_system: dict) -> str: @@ -282,8 +403,8 @@ def format_markdown(design_system: dict) -> str: # Colors section lines.append("### Colors") - lines.append("| Role | Hex |") - lines.append("|------|-----|") + lines.append(f"| Role | Hex |") + lines.append(f"|------|-----|") lines.append(f"| Primary | {colors.get('primary', '')} |") lines.append(f"| Secondary | {colors.get('secondary', '')} |") lines.append(f"| CTA | {colors.get('cta', '')} |") @@ -304,10 +425,10 @@ def format_markdown(design_system: dict) -> str: if typography.get("google_fonts_url"): lines.append(f"- **Google Fonts:** {typography.get('google_fonts_url', '')}") if typography.get("css_import"): - lines.append("- **CSS Import:**") - lines.append("```css") + lines.append(f"- **CSS Import:**") + lines.append(f"```css") lines.append(f"{typography.get('css_import', '')}") - lines.append("```") + lines.append(f"```") lines.append("") # Key Effects section @@ -338,9 +459,10 @@ def format_markdown(design_system: dict) -> str: # ============ MAIN ENTRY POINT ============ -def generate_design_system(query: str, project_name: str = None, output_format: str = "ascii", +def generate_design_system(query: str, project_name: str = None, output_format: str = "ascii", persist: bool = False, page: str = None, output_dir: str = None) -> str: - """Main entry point for design system generation. + """ + Main entry point for design system generation. Args: query: Search query (e.g., "SaaS dashboard", "e-commerce luxury") @@ -355,7 +477,7 @@ def generate_design_system(query: str, project_name: str = None, output_format: """ generator = DesignSystemGenerator() design_system = generator.generate(query, project_name) - + # Persist to files if requested if persist: persist_design_system(design_system, page, output_dir, query) @@ -367,7 +489,8 @@ def generate_design_system(query: str, project_name: str = None, output_format: # ============ PERSISTENCE FUNCTIONS ============ def persist_design_system(design_system: dict, page: str = None, output_dir: str = None, page_query: str = None) -> dict: - """Persist design system to design-system// folder using Master + Overrides pattern. + """ + Persist design system to design-system// folder using Master + Overrides pattern. Args: design_system: The generated design system dictionary @@ -379,28 +502,28 @@ def persist_design_system(design_system: dict, page: str = None, output_dir: str dict with created file paths and status """ base_dir = Path(output_dir) if output_dir else Path.cwd() - + # Use project name for project-specific folder project_name = design_system.get("project_name", "default") project_slug = project_name.lower().replace(' ', '-') - + design_system_dir = base_dir / "design-system" / project_slug pages_dir = design_system_dir / "pages" - + created_files = [] - + # Create directories design_system_dir.mkdir(parents=True, exist_ok=True) pages_dir.mkdir(parents=True, exist_ok=True) - + master_file = design_system_dir / "MASTER.md" - + # Generate and write MASTER.md master_content = format_master_md(design_system) with open(master_file, 'w', encoding='utf-8') as f: f.write(master_content) created_files.append(str(master_file)) - + # If page is specified, create page override file with intelligent content if page: page_file = pages_dir / f"{page.lower().replace(' ', '-')}.md" @@ -408,7 +531,7 @@ def persist_design_system(design_system: dict, page: str = None, output_dir: str with open(page_file, 'w', encoding='utf-8') as f: f.write(page_content) created_files.append(str(page_file)) - + return { "status": "success", "design_system_dir": str(design_system_dir), @@ -425,11 +548,11 @@ def format_master_md(design_system: dict) -> str: typography = design_system.get("typography", {}) effects = design_system.get("key_effects", "") anti_patterns = design_system.get("anti_patterns", "") - + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - + lines = [] - + # Logic header lines.append("# Design System Master File") lines.append("") @@ -445,11 +568,11 @@ def format_master_md(design_system: dict) -> str: lines.append("") lines.append("---") lines.append("") - + # Global Rules section lines.append("## Global Rules") lines.append("") - + # Color Palette lines.append("### Color Palette") lines.append("") @@ -464,7 +587,7 @@ def format_master_md(design_system: dict) -> str: if colors.get("notes"): lines.append(f"**Color Notes:** {colors.get('notes', '')}") lines.append("") - + # Typography lines.append("### Typography") lines.append("") @@ -481,7 +604,7 @@ def format_master_md(design_system: dict) -> str: lines.append(typography.get("css_import", "")) lines.append("```") lines.append("") - + # Spacing Variables lines.append("### Spacing Variables") lines.append("") @@ -495,7 +618,7 @@ def format_master_md(design_system: dict) -> str: lines.append("| `--space-2xl` | `48px` / `3rem` | Section margins |") lines.append("| `--space-3xl` | `64px` / `4rem` | Hero padding |") lines.append("") - + # Shadow Depths lines.append("### Shadow Depths") lines.append("") @@ -506,13 +629,13 @@ def format_master_md(design_system: dict) -> str: lines.append("| `--shadow-lg` | `0 10px 15px rgba(0,0,0,0.1)` | Modals, dropdowns |") lines.append("| `--shadow-xl` | `0 20px 25px rgba(0,0,0,0.15)` | Hero images, featured cards |") lines.append("") - + # Component Specs section lines.append("---") lines.append("") lines.append("## Component Specs") lines.append("") - + # Buttons lines.append("### Buttons") lines.append("") @@ -535,7 +658,7 @@ def format_master_md(design_system: dict) -> str: lines.append("") lines.append("/* Secondary Button */") lines.append(".btn-secondary {") - lines.append(" background: transparent;") + lines.append(f" background: transparent;") lines.append(f" color: {colors.get('primary', '#2563EB')};") lines.append(f" border: 2px solid {colors.get('primary', '#2563EB')};") lines.append(" padding: 12px 24px;") @@ -546,7 +669,7 @@ def format_master_md(design_system: dict) -> str: lines.append("}") lines.append("```") lines.append("") - + # Cards lines.append("### Cards") lines.append("") @@ -566,7 +689,7 @@ def format_master_md(design_system: dict) -> str: lines.append("}") lines.append("```") lines.append("") - + # Inputs lines.append("### Inputs") lines.append("") @@ -586,7 +709,7 @@ def format_master_md(design_system: dict) -> str: lines.append("}") lines.append("```") lines.append("") - + # Modals lines.append("### Modals") lines.append("") @@ -606,7 +729,7 @@ def format_master_md(design_system: dict) -> str: lines.append("}") lines.append("```") lines.append("") - + # Style section lines.append("---") lines.append("") @@ -623,7 +746,7 @@ def format_master_md(design_system: dict) -> str: if effects: lines.append(f"**Key Effects:** {effects}") lines.append("") - + # Layout Pattern lines.append("### Page Pattern") lines.append("") @@ -635,7 +758,7 @@ def format_master_md(design_system: dict) -> str: lines.append(f"- **CTA Placement:** {pattern.get('cta_placement', '')}") lines.append(f"- **Section Order:** {pattern.get('sections', '')}") lines.append("") - + # Anti-Patterns section lines.append("---") lines.append("") @@ -656,7 +779,7 @@ def format_master_md(design_system: dict) -> str: lines.append("- ❌ **Instant state changes** — Always use transitions (150-300ms)") lines.append("- ❌ **Invisible focus states** — Focus states must be visible for a11y") lines.append("") - + # Pre-Delivery Checklist lines.append("---") lines.append("") @@ -675,7 +798,7 @@ def format_master_md(design_system: dict) -> str: lines.append("- [ ] No content hidden behind fixed navbars") lines.append("- [ ] No horizontal scroll on mobile") lines.append("") - + return "\n".join(lines) @@ -684,12 +807,12 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str project = design_system.get("project_name", "PROJECT") timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") page_title = page_name.replace("-", " ").replace("_", " ").title() - + # Detect page type and generate intelligent overrides page_overrides = _generate_intelligent_overrides(page_name, page_query, design_system) - + lines = [] - + lines.append(f"# {page_title} Page Overrides") lines.append("") lines.append(f"> **PROJECT:** {project}") @@ -701,11 +824,11 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str lines.append("") lines.append("---") lines.append("") - + # Page-specific rules with actual content lines.append("## Page-Specific Rules") lines.append("") - + # Layout Overrides lines.append("### Layout Overrides") lines.append("") @@ -716,7 +839,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No overrides — use Master layout") lines.append("") - + # Spacing Overrides lines.append("### Spacing Overrides") lines.append("") @@ -727,7 +850,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No overrides — use Master spacing") lines.append("") - + # Typography Overrides lines.append("### Typography Overrides") lines.append("") @@ -738,7 +861,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No overrides — use Master typography") lines.append("") - + # Color Overrides lines.append("### Color Overrides") lines.append("") @@ -749,7 +872,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No overrides — use Master colors") lines.append("") - + # Component Overrides lines.append("### Component Overrides") lines.append("") @@ -760,7 +883,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No overrides — use Master component specs") lines.append("") - + # Page-Specific Components lines.append("---") lines.append("") @@ -773,7 +896,7 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str else: lines.append("- No unique components for this page") lines.append("") - + # Recommendations lines.append("---") lines.append("") @@ -784,35 +907,36 @@ def format_page_override_md(design_system: dict, page_name: str, page_query: str for rec in recommendations: lines.append(f"- {rec}") lines.append("") - + return "\n".join(lines) def _generate_intelligent_overrides(page_name: str, page_query: str, design_system: dict) -> dict: - """Generate intelligent overrides based on page type using layered search. + """ + Generate intelligent overrides based on page type using layered search. Uses the existing search infrastructure to find relevant style, UX, and layout data instead of hardcoded page types. """ from core import search - + page_lower = page_name.lower() query_lower = (page_query or "").lower() combined_context = f"{page_lower} {query_lower}" - + # Search across multiple domains for page-specific guidance style_search = search(combined_context, "style", max_results=1) ux_search = search(combined_context, "ux", max_results=3) landing_search = search(combined_context, "landing", max_results=1) - + # Extract results from search response style_results = style_search.get("results", []) ux_results = ux_search.get("results", []) landing_results = landing_search.get("results", []) - + # Detect page type from search results or context page_type = _detect_page_type(combined_context, style_results) - + # Build overrides from search results layout = {} spacing = {} @@ -821,7 +945,7 @@ def _generate_intelligent_overrides(page_name: str, page_query: str, design_syst components = [] unique_components = [] recommendations = [] - + # Extract style-based overrides if style_results: style = style_results[0] @@ -829,7 +953,7 @@ def _generate_intelligent_overrides(page_name: str, page_query: str, design_syst keywords = style.get("Keywords", "") best_for = style.get("Best For", "") effects = style.get("Effects & Animation", "") - + # Infer layout from style keywords if any(kw in keywords.lower() for kw in ["data", "dense", "dashboard", "grid"]): layout["Max Width"] = "1400px or full-width" @@ -842,10 +966,10 @@ def _generate_intelligent_overrides(page_name: str, page_query: str, design_syst else: layout["Max Width"] = "1200px (standard)" layout["Layout"] = "Full-width sections, centered content" - + if effects: recommendations.append(f"Effects: {effects}") - + # Extract UX guidelines as recommendations for ux in ux_results: category = ux.get("Category", "") @@ -855,32 +979,32 @@ def _generate_intelligent_overrides(page_name: str, page_query: str, design_syst recommendations.append(f"{category}: {do_text}") if dont_text: components.append(f"Avoid: {dont_text}") - + # Extract landing pattern info for section structure if landing_results: landing = landing_results[0] sections = landing.get("Section Order", "") cta_placement = landing.get("Primary CTA Placement", "") color_strategy = landing.get("Color Strategy", "") - + if sections: layout["Sections"] = sections if cta_placement: recommendations.append(f"CTA Placement: {cta_placement}") if color_strategy: colors["Strategy"] = color_strategy - + # Add page-type specific defaults if no search results if not layout: layout["Max Width"] = "1200px" layout["Layout"] = "Responsive grid" - + if not recommendations: recommendations = [ "Refer to MASTER.md for all design rules", "Add specific overrides as needed for this page" ] - + return { "page_type": page_type, "layout": layout, @@ -896,7 +1020,7 @@ def _generate_intelligent_overrides(page_name: str, page_query: str, design_syst def _detect_page_type(context: str, style_results: list) -> str: """Detect page type from context and search results.""" context_lower = context.lower() - + # Check for common page type patterns page_patterns = [ (["dashboard", "admin", "analytics", "data", "metrics", "stats", "monitor", "overview"], "Dashboard / Data View"), @@ -910,21 +1034,21 @@ def _detect_page_type(context: str, style_results: list) -> str: (["search", "results", "browse", "filter", "catalog", "list"], "Search Results"), (["empty", "404", "error", "not found", "zero"], "Empty State"), ] - + for keywords, page_type in page_patterns: if any(kw in context_lower for kw in keywords): return page_type - + # Fallback: try to infer from style results if style_results: style_name = style_results[0].get("Style Category", "").lower() best_for = style_results[0].get("Best For", "").lower() - + if "dashboard" in best_for or "data" in best_for: return "Dashboard / Data View" elif "landing" in best_for or "marketing" in best_for: return "Landing / Marketing" - + return "General" diff --git a/.agent/skills/ui-ux-pro-max/scripts/search.py b/.agent/skills/ui-ux-pro-max/scripts/search.py index 4385f79..3981cd2 100644 --- a/.agent/skills/ui-ux-pro-max/scripts/search.py +++ b/.agent/skills/ui-ux-pro-max/scripts/search.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 -"""UI/UX Pro Max Search - BM25 search engine for UI/UX style guides +# -*- coding: utf-8 -*- +""" +UI/UX Pro Max Search - BM25 search engine for UI/UX style guides Usage: python search.py "" [--domain ] [--stack ] [--max-results 3] python search.py "" --design-system [-p "Project Name"] - python search.py "" --design-system --persist [-p "Project Name"] [--page "dashboard"]. + python search.py "" --design-system --persist [-p "Project Name"] [--page "dashboard"] Domains: style, prompt, color, chart, landing, product, ux, typography Stacks: html-tailwind, react, nextjs @@ -13,22 +15,29 @@ """ import argparse +import sys +import io +from core import CSV_CONFIG, AVAILABLE_STACKS, MAX_RESULTS, search, search_stack +from design_system import generate_design_system, persist_design_system -from core import AVAILABLE_STACKS, CSV_CONFIG, MAX_RESULTS, search, search_stack -from design_system import generate_design_system +# Force UTF-8 for stdout/stderr to handle emojis on Windows (cp1252 default) +if sys.stdout.encoding and sys.stdout.encoding.lower() != 'utf-8': + sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') +if sys.stderr.encoding and sys.stderr.encoding.lower() != 'utf-8': + sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8') def format_output(result): - """Format results for Claude consumption (token-optimized).""" + """Format results for Claude consumption (token-optimized)""" if "error" in result: return f"Error: {result['error']}" output = [] if result.get("stack"): - output.append("## UI Pro Max Stack Guidelines") + output.append(f"## UI Pro Max Stack Guidelines") output.append(f"**Stack:** {result['stack']} | **Query:** {result['query']}") else: - output.append("## UI Pro Max Search Results") + output.append(f"## UI Pro Max Search Results") output.append(f"**Domain:** {result['domain']} | **Query:** {result['query']}") output.append(f"**Source:** {result['file']} | **Found:** {result['count']} results\n") @@ -65,15 +74,15 @@ def format_output(result): # Design system takes priority if args.design_system: result = generate_design_system( - args.query, - args.project_name, + args.query, + args.project_name, args.format, persist=args.persist, page=args.page, output_dir=args.output_dir ) print(result) - + # Print persistence confirmation if args.persist: project_slug = args.project_name.lower().replace(' ', '-') if args.project_name else "default" @@ -85,7 +94,7 @@ def format_output(result): print(f" 📄 design-system/{project_slug}/pages/{page_filename}.md (Page Overrides)") print("") print(f"📖 Usage: When building a page, check design-system/{project_slug}/pages/[page].md first.") - print(" If exists, its rules override MASTER.md. Otherwise, use MASTER.md.") + print(f" If exists, its rules override MASTER.md. Otherwise, use MASTER.md.") print("=" * 60) # Stack search elif args.stack: diff --git a/.codex/skills/ui-ux-pro-max/SKILL.md b/.codex/skills/ui-ux-pro-max/SKILL.md new file mode 100644 index 0000000..d218d16 --- /dev/null +++ b/.codex/skills/ui-ux-pro-max/SKILL.md @@ -0,0 +1,297 @@ +--- +name: ui-ux-pro-max +description: UI/UX design intelligence with searchable database +--- +# ui-ux-pro-max + +Comprehensive design guide for web and mobile applications. Contains 67 styles, 96 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 13 technology stacks. Searchable database with priority-based recommendations. + +## Prerequisites + +Check if Python is installed: + +```bash +python3 --version || python --version +``` + +If Python is not installed, install it based on user's OS: + +**macOS:** + +```bash +brew install python3 +``` + +**Ubuntu/Debian:** + +```bash +sudo apt update && sudo apt install python3 +``` + +**Windows:** + +```powershell +winget install Python.Python.3.12 +``` + +--- + +## How to Use This Skill + +When user requests UI/UX work (design, build, create, implement, review, fix, improve), follow this workflow: + +### Step 1: Analyze User Requirements + +Extract key information from user request: +- **Product type**: SaaS, e-commerce, portfolio, dashboard, landing page, etc. +- **Style keywords**: minimal, playful, professional, elegant, dark mode, etc. +- **Industry**: healthcare, fintech, gaming, education, etc. +- **Stack**: React, Vue, Next.js, or default to `html-tailwind` + +### Step 2: Generate Design System (REQUIRED) + +**Always start with `--design-system`** to get comprehensive recommendations with reasoning: + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py " " --design-system [-p "Project Name"] +``` + +This command: +1. Searches 5 domains in parallel (product, style, color, landing, typography) +2. Applies reasoning rules from `ui-reasoning.csv` to select best matches +3. Returns complete design system: pattern, style, colors, typography, effects +4. Includes anti-patterns to avoid + +**Example:** + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa" +``` + +### Step 2b: Persist Design System (Master + Overrides Pattern) + +To save the design system for hierarchical retrieval across sessions, add `--persist`: + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "" --design-system --persist -p "Project Name" +``` + +This creates: +- `design-system/MASTER.md` — Global Source of Truth with all design rules +- `design-system/pages/` — Folder for page-specific overrides + +**With page-specific override:** + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "" --design-system --persist -p "Project Name" --page "dashboard" +``` + +This also creates: +- `design-system/pages/dashboard.md` — Page-specific deviations from Master + +**How hierarchical retrieval works:** +1. When building a specific page (e.g., "Checkout"), first check `design-system/pages/checkout.md` +2. If the page file exists, its rules **override** the Master file +3. If not, use `design-system/MASTER.md` exclusively + +### Step 3: Supplement with Detailed Searches (as needed) + +After getting the design system, use domain searches to get additional details: + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "" --domain [-n ] +``` + +**When to use detailed searches:** + +| Need | Domain | Example | +| --- | --- | --- | +| More style options | `style` | `--domain style "glassmorphism dark"` | +| Chart recommendations | `chart` | `--domain chart "real-time dashboard"` | +| UX best practices | `ux` | `--domain ux "animation accessibility"` | +| Alternative fonts | `typography` | `--domain typography "elegant luxury"` | +| Landing structure | `landing` | `--domain landing "hero social-proof"` | + +### Step 4: Stack Guidelines (Default: html-tailwind) + +Get implementation-specific best practices. If user doesn't specify a stack, **default to `html-tailwind`**. + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "" --stack html-tailwind +``` + +Available stacks: `html-tailwind`, `react`, `nextjs`, `vue`, `svelte`, `swiftui`, `react-native`, `flutter`, `shadcn`, `jetpack-compose` + +--- + +## Search Reference + +### Available Domains + +| Domain | Use For | Example Keywords | +| --- | --- | --- | +| `product` | Product type recommendations | SaaS, e-commerce, portfolio, healthcare, beauty, service | +| `style` | UI styles, colors, effects | glassmorphism, minimalism, dark mode, brutalism | +| `typography` | Font pairings, Google Fonts | elegant, playful, professional, modern | +| `color` | Color palettes by product type | saas, ecommerce, healthcare, beauty, fintech, service | +| `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof | +| `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie | +| `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading | +| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache | +| `web` | Web interface guidelines | aria, focus, keyboard, semantic, virtualize | +| `prompt` | AI prompts, CSS keywords | (style name) | + +### Available Stacks + +| Stack | Focus | +| --- | --- | +| `html-tailwind` | Tailwind utilities, responsive, a11y (DEFAULT) | +| `react` | State, hooks, performance, patterns | +| `nextjs` | SSR, routing, images, API routes | +| `vue` | Composition API, Pinia, Vue Router | +| `svelte` | Runes, stores, SvelteKit | +| `swiftui` | Views, State, Navigation, Animation | +| `react-native` | Components, Navigation, Lists | +| `flutter` | Widgets, State, Layout, Theming | +| `shadcn` | shadcn/ui components, theming, forms, patterns | +| `jetpack-compose` | Composables, Modifiers, State Hoisting, Recomposition | + +--- + +## Example Workflow + +**User request:** "Làm landing page cho dịch vụ chăm sóc da chuyên nghiệp" + +### Step 1: Analyze Requirements +- Product type: Beauty/Spa service +- Style keywords: elegant, professional, soft +- Industry: Beauty/Wellness +- Stack: html-tailwind (default) + +### Step 2: Generate Design System (REQUIRED) + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "beauty spa wellness service elegant" --design-system -p "Serenity Spa" +``` + +**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns. + +### Step 3: Supplement with Detailed Searches (as needed) + +```bash +# Get UX guidelines for animation and accessibility +python3 skills/ui-ux-pro-max/scripts/search.py "animation accessibility" --domain ux + +# Get alternative typography options if needed +python3 skills/ui-ux-pro-max/scripts/search.py "elegant luxury serif" --domain typography +``` + +### Step 4: Stack Guidelines + +```bash +python3 skills/ui-ux-pro-max/scripts/search.py "layout responsive form" --stack html-tailwind +``` + +**Then:** Synthesize design system + detailed searches and implement the design. + +--- + +## Output Formats + +The `--design-system` flag supports two output formats: + +```bash +# ASCII box (default) - best for terminal display +python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system + +# Markdown - best for documentation +python3 skills/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown +``` + +--- + +## Tips for Better Results + +1. **Be specific with keywords** - "healthcare SaaS dashboard" > "app" +2. **Search multiple times** - Different keywords reveal different insights +3. **Combine domains** - Style + Typography + Color = Complete design system +4. **Always check UX** - Search "animation", "z-index", "accessibility" for common issues +5. **Use stack flag** - Get implementation-specific best practices +6. **Iterate** - If first search doesn't match, try different keywords + +--- + +## Common Rules for Professional UI + +These are frequently overlooked issues that make UI look unprofessional: + +### Icons & Visual Elements + +| Rule | Do | Don't | +| --- | --- | --- | +| **No emoji icons** | Use SVG icons (Heroicons, Lucide, Simple Icons) | Use emojis like 🎨 🚀 ⚙️ as UI icons | +| **Stable hover states** | Use color/opacity transitions on hover | Use scale transforms that shift layout | +| **Correct brand logos** | Research official SVG from Simple Icons | Guess or use incorrect logo paths | +| **Consistent icon sizing** | Use fixed viewBox (24x24) with w-6 h-6 | Mix different icon sizes randomly | + +### Interaction & Cursor + +| Rule | Do | Don't | +| --- | --- | --- | +| **Cursor pointer** | Add `cursor-pointer` to all clickable/hoverable cards | Leave default cursor on interactive elements | +| **Hover feedback** | Provide visual feedback (color, shadow, border) | No indication element is interactive | +| **Smooth transitions** | Use `transition-colors duration-200` | Instant state changes or too slow (>500ms) | + +### Light/Dark Mode Contrast + +| Rule | Do | Don't | +| --- | --- | --- | +| **Glass card light mode** | Use `bg-white/80` or higher opacity | Use `bg-white/10` (too transparent) | +| **Text contrast light** | Use `#0F172A` (slate-900) for text | Use `#94A3B8` (slate-400) for body text | +| **Muted text light** | Use `#475569` (slate-600) minimum | Use gray-400 or lighter | +| **Border visibility** | Use `border-gray-200` in light mode | Use `border-white/10` (invisible) | + +### Layout & Spacing + +| Rule | Do | Don't | +| --- | --- | --- | +| **Floating navbar** | Add `top-4 left-4 right-4` spacing | Stick navbar to `top-0 left-0 right-0` | +| **Content padding** | Account for fixed navbar height | Let content hide behind fixed elements | +| **Consistent max-width** | Use same `max-w-6xl` or `max-w-7xl` | Mix different container widths | + +--- + +## Pre-Delivery Checklist + +Before delivering UI code, verify these items: + +### Visual Quality +- [ ] No emojis used as icons (use SVG instead) +- [ ] All icons from consistent icon set (Heroicons/Lucide) +- [ ] Brand logos are correct (verified from Simple Icons) +- [ ] Hover states don't cause layout shift +- [ ] Use theme colors directly (bg-primary) not var() wrapper + +### Interaction +- [ ] All clickable elements have `cursor-pointer` +- [ ] Hover states provide clear visual feedback +- [ ] Transitions are smooth (150-300ms) +- [ ] Focus states visible for keyboard navigation + +### Light/Dark Mode +- [ ] Light mode text has sufficient contrast (4.5:1 minimum) +- [ ] Glass/transparent elements visible in light mode +- [ ] Borders visible in both modes +- [ ] Test both modes before delivery + +### Layout +- [ ] Floating elements have proper spacing from edges +- [ ] No content hidden behind fixed navbars +- [ ] Responsive at 375px, 768px, 1024px, 1440px +- [ ] No horizontal scroll on mobile + +### Accessibility +- [ ] All images have alt text +- [ ] Form inputs have labels +- [ ] Color is not the only indicator +- [ ] `prefers-reduced-motion` respected diff --git a/.codex/skills/ui-ux-pro-max/data/charts.csv b/.codex/skills/ui-ux-pro-max/data/charts.csv new file mode 100644 index 0000000..8b99663 --- /dev/null +++ b/.codex/skills/ui-ux-pro-max/data/charts.csv @@ -0,0 +1,26 @@ +No,Data Type,Keywords,Best Chart Type,Secondary Options,Color Guidance,Performance Impact,Accessibility Notes,Library Recommendation,Interactive Level +1,Trend Over Time,"trend, time-series, line, growth, timeline, progress",Line Chart,"Area Chart, Smooth Area",Primary: #0080FF. Multiple series: use distinct colors. Fill: 20% opacity,⚡ Excellent (optimized),✓ Clear line patterns for colorblind users. Add pattern overlays.,"Chart.js, Recharts, ApexCharts",Hover + Zoom +2,Compare Categories,"compare, categories, bar, comparison, ranking",Bar Chart (Horizontal or Vertical),"Column Chart, Grouped Bar",Each bar: distinct color. Category: grouped same color. Sorted: descending order,⚡ Excellent,✓ Easy to compare. Add value labels on bars for clarity.,"Chart.js, Recharts, D3.js",Hover + Sort +3,Part-to-Whole,"part-to-whole, pie, donut, percentage, proportion, share",Pie Chart or Donut,"Stacked Bar, Treemap",Colors: 5-6 max. Contrasting palette. Large slices first. Use labels.,⚡ Good (limit 6 slices),⚠ Hard for accessibility. Better: Stacked bar with legend. Avoid pie if >5 items.,"Chart.js, Recharts, D3.js",Hover + Drill +4,Correlation/Distribution,"correlation, distribution, scatter, relationship, pattern",Scatter Plot or Bubble Chart,"Heat Map, Matrix",Color axis: gradient (blue-red). Size: relative. Opacity: 0.6-0.8 to show density,⚠ Moderate (many points),⚠ Provide data table alternative. Use pattern + color distinction.,"D3.js, Plotly, Recharts",Hover + Brush +5,Heatmap/Intensity,"heatmap, heat-map, intensity, density, matrix",Heat Map or Choropleth,"Grid Heat Map, Bubble Heat",Gradient: Cool (blue) to Hot (red). Scale: clear legend. Divergent for ±data,⚡ Excellent (color CSS),⚠ Colorblind: Use pattern overlay. Provide numerical legend.,"D3.js, Plotly, ApexCharts",Hover + Zoom +6,Geographic Data,"geographic, map, location, region, geo, spatial","Choropleth Map, Bubble Map",Geographic Heat Map,Regional: single color gradient or categorized colors. Legend: clear scale,⚠ Moderate (rendering),⚠ Include text labels for regions. Provide data table alternative.,"D3.js, Mapbox, Leaflet",Pan + Zoom + Drill +7,Funnel/Flow,funnel/flow,"Funnel Chart, Sankey",Waterfall (for flows),Stages: gradient (starting color → ending color). Show conversion %,⚡ Good,✓ Clear stage labels + percentages. Good for accessibility if labeled.,"D3.js, Recharts, Custom SVG",Hover + Drill +8,Performance vs Target,performance-vs-target,Gauge Chart or Bullet Chart,"Dial, Thermometer",Performance: Red→Yellow→Green gradient. Target: marker line. Threshold colors,⚡ Good,✓ Add numerical value + percentage label beside gauge.,"D3.js, ApexCharts, Custom SVG",Hover +9,Time-Series Forecast,time-series-forecast,Line with Confidence Band,Ribbon Chart,Actual: solid line #0080FF. Forecast: dashed #FF9500. Band: light shading,⚡ Good,✓ Clearly distinguish actual vs forecast. Add legend.,"Chart.js, ApexCharts, Plotly",Hover + Toggle +10,Anomaly Detection,anomaly-detection,Line Chart with Highlights,Scatter with Alert,Normal: blue #0080FF. Anomaly: red #FF0000 circle/square marker + alert,⚡ Good,✓ Circle/marker for anomalies. Add text alert annotation.,"D3.js, Plotly, ApexCharts",Hover + Alert +11,Hierarchical/Nested Data,hierarchical/nested-data,Treemap,"Sunburst, Nested Donut, Icicle",Parent: distinct hues. Children: lighter shades. White borders 2-3px.,⚠ Moderate,⚠ Poor - provide table alternative. Label large areas.,"D3.js, Recharts, ApexCharts",Hover + Drilldown +12,Flow/Process Data,flow/process-data,Sankey Diagram,"Alluvial, Chord Diagram",Gradient from source to target. Opacity 0.4-0.6 for flows.,⚠ Moderate,⚠ Poor - provide flow table alternative.,"D3.js (d3-sankey), Plotly",Hover + Drilldown +13,Cumulative Changes,cumulative-changes,Waterfall Chart,"Stacked Bar, Cascade",Increases: #4CAF50. Decreases: #F44336. Start: #2196F3. End: #0D47A1.,⚡ Good,✓ Good - clear directional colors with labels.,"ApexCharts, Highcharts, Plotly",Hover +14,Multi-Variable Comparison,multi-variable-comparison,Radar/Spider Chart,"Parallel Coordinates, Grouped Bar",Single: #0080FF 20% fill. Multiple: distinct colors per dataset.,⚡ Good,⚠ Moderate - limit 5-8 axes. Add data table.,"Chart.js, Recharts, ApexCharts",Hover + Toggle +15,Stock/Trading OHLC,stock/trading-ohlc,Candlestick Chart,"OHLC Bar, Heikin-Ashi",Bullish: #26A69A. Bearish: #EF5350. Volume: 40% opacity below.,⚡ Good,⚠ Moderate - provide OHLC data table.,"Lightweight Charts (TradingView), ApexCharts",Real-time + Hover + Zoom +16,Relationship/Connection Data,relationship/connection-data,Network Graph,"Hierarchical Tree, Adjacency Matrix",Node types: categorical colors. Edges: #90A4AE 60% opacity.,❌ Poor (500+ nodes struggles),❌ Very Poor - provide adjacency list alternative.,"D3.js (d3-force), Vis.js, Cytoscape.js",Drilldown + Hover + Drag +17,Distribution/Statistical,distribution/statistical,Box Plot,"Violin Plot, Beeswarm",Box: #BBDEFB. Border: #1976D2. Median: #D32F2F. Outliers: #F44336.,⚡ Excellent,"✓ Good - include stats table (min, Q1, median, Q3, max).","Plotly, D3.js, Chart.js (plugin)",Hover +18,Performance vs Target (Compact),performance-vs-target-(compact),Bullet Chart,"Gauge, Progress Bar","Ranges: #FFCDD2, #FFF9C4, #C8E6C9. Performance: #1976D2. Target: black 3px.",⚡ Excellent,✓ Excellent - compact with clear values.,"D3.js, Plotly, Custom SVG",Hover +19,Proportional/Percentage,proportional/percentage,Waffle Chart,"Pictogram, Stacked Bar 100%",10x10 grid. 3-5 categories max. 2-3px spacing between squares.,⚡ Good,✓ Good - better than pie for accessibility.,"D3.js, React-Waffle, Custom CSS Grid",Hover +20,Hierarchical Proportional,hierarchical-proportional,Sunburst Chart,"Treemap, Icicle, Circle Packing",Center to outer: darker to lighter. 15-20% lighter per level.,⚠ Moderate,⚠ Poor - provide hierarchy table alternative.,"D3.js (d3-hierarchy), Recharts, ApexCharts",Drilldown + Hover +21,Root Cause Analysis,"root cause, decomposition, tree, hierarchy, drill-down, ai-split",Decomposition Tree,"Decision Tree, Flow Chart",Nodes: #2563EB (Primary) vs #EF4444 (Negative impact). Connectors: Neutral grey.,⚠ Moderate (calculation heavy),✓ clear hierarchy. Allow keyboard navigation for nodes.,"Power BI (native), React-Flow, Custom D3.js",Drill + Expand +22,3D Spatial Data,"3d, spatial, immersive, terrain, molecular, volumetric",3D Scatter/Surface Plot,"Volumetric Rendering, Point Cloud",Depth cues: lighting/shading. Z-axis: color gradient (cool to warm).,❌ Heavy (WebGL required),❌ Poor - requires alternative 2D view or data table.,"Three.js, Deck.gl, Plotly 3D",Rotate + Zoom + VR +23,Real-Time Streaming,"streaming, real-time, ticker, live, velocity, pulse",Streaming Area Chart,"Ticker Tape, Moving Gauge",Current: Bright Pulse (#00FF00). History: Fading opacity. Grid: Dark.,⚡ Optimized (canvas/webgl),⚠ Flashing elements - provide pause button. High contrast.,Smoothed D3.js, CanvasJS +24,Sentiment/Emotion,"sentiment, emotion, nlp, opinion, feeling",Word Cloud with Sentiment,"Sentiment Arc, Radar Chart",Positive: #22C55E. Negative: #EF4444. Neutral: #94A3B8. Size = Frequency.,⚡ Good,⚠ Word clouds poor for screen readers. Use list view.,"D3-cloud, Highcharts, Nivo",Hover + Filter +25,Process Mining,"process, mining, variants, path, bottleneck, log",Process Map / Graph,"Directed Acyclic Graph (DAG), Petri Net",Happy path: #10B981 (Thick). Deviations: #F59E0B (Thin). Bottlenecks: #EF4444.,⚠ Moderate to Heavy,⚠ Complex graphs hard to navigate. Provide path summary.,"React-Flow, Cytoscape.js, Recharts",Drag + Node-Click diff --git a/.codex/skills/ui-ux-pro-max/data/colors.csv b/.codex/skills/ui-ux-pro-max/data/colors.csv new file mode 100644 index 0000000..d9fd043 --- /dev/null +++ b/.codex/skills/ui-ux-pro-max/data/colors.csv @@ -0,0 +1,97 @@ +No,Product Type,Primary (Hex),Secondary (Hex),CTA (Hex),Background (Hex),Text (Hex),Border (Hex),Notes +1,SaaS (General),#2563EB,#3B82F6,#F97316,#F8FAFC,#1E293B,#E2E8F0,Trust blue + orange CTA contrast +2,Micro SaaS,#6366F1,#818CF8,#10B981,#F5F3FF,#1E1B4B,#E0E7FF,Indigo primary + emerald CTA +3,E-commerce,#059669,#10B981,#F97316,#ECFDF5,#064E3B,#A7F3D0,Success green + urgency orange +4,E-commerce Luxury,#1C1917,#44403C,#CA8A04,#FAFAF9,#0C0A09,#D6D3D1,Premium dark + gold accent +5,Service Landing Page,#0EA5E9,#38BDF8,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Sky blue trust + warm CTA +6,B2B Service,#0F172A,#334155,#0369A1,#F8FAFC,#020617,#E2E8F0,Professional navy + blue CTA +7,Financial Dashboard,#0F172A,#1E293B,#22C55E,#020617,#F8FAFC,#334155,Dark bg + green positive indicators +8,Analytics Dashboard,#1E40AF,#3B82F6,#F59E0B,#F8FAFC,#1E3A8A,#DBEAFE,Blue data + amber highlights +9,Healthcare App,#0891B2,#22D3EE,#059669,#ECFEFF,#164E63,#A5F3FC,Calm cyan + health green +10,Educational App,#4F46E5,#818CF8,#F97316,#EEF2FF,#1E1B4B,#C7D2FE,Playful indigo + energetic orange +11,Creative Agency,#EC4899,#F472B6,#06B6D4,#FDF2F8,#831843,#FBCFE8,Bold pink + cyan accent +12,Portfolio/Personal,#18181B,#3F3F46,#2563EB,#FAFAFA,#09090B,#E4E4E7,Monochrome + blue accent +13,Gaming,#7C3AED,#A78BFA,#F43F5E,#0F0F23,#E2E8F0,#4C1D95,Neon purple + rose action +14,Government/Public Service,#0F172A,#334155,#0369A1,#F8FAFC,#020617,#E2E8F0,High contrast navy + blue +15,Fintech/Crypto,#F59E0B,#FBBF24,#8B5CF6,#0F172A,#F8FAFC,#334155,Gold trust + purple tech +16,Social Media App,#E11D48,#FB7185,#2563EB,#FFF1F2,#881337,#FECDD3,Vibrant rose + engagement blue +17,Productivity Tool,#0D9488,#14B8A6,#F97316,#F0FDFA,#134E4A,#99F6E4,Teal focus + action orange +18,Design System/Component Library,#4F46E5,#6366F1,#F97316,#EEF2FF,#312E81,#C7D2FE,Indigo brand + doc hierarchy +19,AI/Chatbot Platform,#7C3AED,#A78BFA,#06B6D4,#FAF5FF,#1E1B4B,#DDD6FE,AI purple + cyan interactions +20,NFT/Web3 Platform,#8B5CF6,#A78BFA,#FBBF24,#0F0F23,#F8FAFC,#4C1D95,Purple tech + gold value +21,Creator Economy Platform,#EC4899,#F472B6,#F97316,#FDF2F8,#831843,#FBCFE8,Creator pink + engagement orange +22,Sustainability/ESG Platform,#059669,#10B981,#0891B2,#ECFDF5,#064E3B,#A7F3D0,Nature green + ocean blue +23,Remote Work/Collaboration Tool,#6366F1,#818CF8,#10B981,#F5F3FF,#312E81,#E0E7FF,Calm indigo + success green +24,Mental Health App,#8B5CF6,#C4B5FD,#10B981,#FAF5FF,#4C1D95,#EDE9FE,Calming lavender + wellness green +25,Pet Tech App,#F97316,#FB923C,#2563EB,#FFF7ED,#9A3412,#FED7AA,Playful orange + trust blue +26,Smart Home/IoT Dashboard,#1E293B,#334155,#22C55E,#0F172A,#F8FAFC,#475569,Dark tech + status green +27,EV/Charging Ecosystem,#0891B2,#22D3EE,#22C55E,#ECFEFF,#164E63,#A5F3FC,Electric cyan + eco green +28,Subscription Box Service,#D946EF,#E879F9,#F97316,#FDF4FF,#86198F,#F5D0FE,Excitement purple + urgency orange +29,Podcast Platform,#1E1B4B,#312E81,#F97316,#0F0F23,#F8FAFC,#4338CA,Dark audio + warm accent +30,Dating App,#E11D48,#FB7185,#F97316,#FFF1F2,#881337,#FECDD3,Romantic rose + warm orange +31,Micro-Credentials/Badges Platform,#0369A1,#0EA5E9,#CA8A04,#F0F9FF,#0C4A6E,#BAE6FD,Trust blue + achievement gold +32,Knowledge Base/Documentation,#475569,#64748B,#2563EB,#F8FAFC,#1E293B,#E2E8F0,Neutral grey + link blue +33,Hyperlocal Services,#059669,#10B981,#F97316,#ECFDF5,#064E3B,#A7F3D0,Location green + action orange +34,Beauty/Spa/Wellness Service,#EC4899,#F9A8D4,#8B5CF6,#FDF2F8,#831843,#FBCFE8,Soft pink + lavender luxury +35,Luxury/Premium Brand,#1C1917,#44403C,#CA8A04,#FAFAF9,#0C0A09,#D6D3D1,Premium black + gold accent +36,Restaurant/Food Service,#DC2626,#F87171,#CA8A04,#FEF2F2,#450A0A,#FECACA,Appetizing red + warm gold +37,Fitness/Gym App,#F97316,#FB923C,#22C55E,#1F2937,#F8FAFC,#374151,Energy orange + success green +38,Real Estate/Property,#0F766E,#14B8A6,#0369A1,#F0FDFA,#134E4A,#99F6E4,Trust teal + professional blue +39,Travel/Tourism Agency,#0EA5E9,#38BDF8,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Sky blue + adventure orange +40,Hotel/Hospitality,#1E3A8A,#3B82F6,#CA8A04,#F8FAFC,#1E40AF,#BFDBFE,Luxury navy + gold service +41,Wedding/Event Planning,#DB2777,#F472B6,#CA8A04,#FDF2F8,#831843,#FBCFE8,Romantic pink + elegant gold +42,Legal Services,#1E3A8A,#1E40AF,#B45309,#F8FAFC,#0F172A,#CBD5E1,Authority navy + trust gold +43,Insurance Platform,#0369A1,#0EA5E9,#22C55E,#F0F9FF,#0C4A6E,#BAE6FD,Security blue + protected green +44,Banking/Traditional Finance,#0F172A,#1E3A8A,#CA8A04,#F8FAFC,#020617,#E2E8F0,Trust navy + premium gold +45,Online Course/E-learning,#0D9488,#2DD4BF,#F97316,#F0FDFA,#134E4A,#5EEAD4,Progress teal + achievement orange +46,Non-profit/Charity,#0891B2,#22D3EE,#F97316,#ECFEFF,#164E63,#A5F3FC,Compassion blue + action orange +47,Music Streaming,#1E1B4B,#4338CA,#22C55E,#0F0F23,#F8FAFC,#312E81,Dark audio + play green +48,Video Streaming/OTT,#0F0F23,#1E1B4B,#E11D48,#000000,#F8FAFC,#312E81,Cinema dark + play red +49,Job Board/Recruitment,#0369A1,#0EA5E9,#22C55E,#F0F9FF,#0C4A6E,#BAE6FD,Professional blue + success green +50,Marketplace (P2P),#7C3AED,#A78BFA,#22C55E,#FAF5FF,#4C1D95,#DDD6FE,Trust purple + transaction green +51,Logistics/Delivery,#2563EB,#3B82F6,#F97316,#EFF6FF,#1E40AF,#BFDBFE,Tracking blue + delivery orange +52,Agriculture/Farm Tech,#15803D,#22C55E,#CA8A04,#F0FDF4,#14532D,#BBF7D0,Earth green + harvest gold +53,Construction/Architecture,#64748B,#94A3B8,#F97316,#F8FAFC,#334155,#E2E8F0,Industrial grey + safety orange +54,Automotive/Car Dealership,#1E293B,#334155,#DC2626,#F8FAFC,#0F172A,#E2E8F0,Premium dark + action red +55,Photography Studio,#18181B,#27272A,#F8FAFC,#000000,#FAFAFA,#3F3F46,Pure black + white contrast +56,Coworking Space,#F59E0B,#FBBF24,#2563EB,#FFFBEB,#78350F,#FDE68A,Energetic amber + booking blue +57,Cleaning Service,#0891B2,#22D3EE,#22C55E,#ECFEFF,#164E63,#A5F3FC,Fresh cyan + clean green +58,Home Services (Plumber/Electrician),#1E40AF,#3B82F6,#F97316,#EFF6FF,#1E3A8A,#BFDBFE,Professional blue + urgent orange +59,Childcare/Daycare,#F472B6,#FBCFE8,#22C55E,#FDF2F8,#9D174D,#FCE7F3,Soft pink + safe green +60,Senior Care/Elderly,#0369A1,#38BDF8,#22C55E,#F0F9FF,#0C4A6E,#E0F2FE,Calm blue + reassuring green +61,Medical Clinic,#0891B2,#22D3EE,#22C55E,#F0FDFA,#134E4A,#CCFBF1,Medical teal + health green +62,Pharmacy/Drug Store,#15803D,#22C55E,#0369A1,#F0FDF4,#14532D,#BBF7D0,Pharmacy green + trust blue +63,Dental Practice,#0EA5E9,#38BDF8,#FBBF24,#F0F9FF,#0C4A6E,#BAE6FD,Fresh blue + smile yellow +64,Veterinary Clinic,#0D9488,#14B8A6,#F97316,#F0FDFA,#134E4A,#99F6E4,Caring teal + warm orange +65,Florist/Plant Shop,#15803D,#22C55E,#EC4899,#F0FDF4,#14532D,#BBF7D0,Natural green + floral pink +66,Bakery/Cafe,#92400E,#B45309,#F8FAFC,#FEF3C7,#78350F,#FDE68A,Warm brown + cream white +67,Coffee Shop,#78350F,#92400E,#FBBF24,#FEF3C7,#451A03,#FDE68A,Coffee brown + warm gold +68,Brewery/Winery,#7C2D12,#B91C1C,#CA8A04,#FEF2F2,#450A0A,#FECACA,Deep burgundy + craft gold +69,Airline,#1E3A8A,#3B82F6,#F97316,#EFF6FF,#1E40AF,#BFDBFE,Sky blue + booking orange +70,News/Media Platform,#DC2626,#EF4444,#1E40AF,#FEF2F2,#450A0A,#FECACA,Breaking red + link blue +71,Magazine/Blog,#18181B,#3F3F46,#EC4899,#FAFAFA,#09090B,#E4E4E7,Editorial black + accent pink +72,Freelancer Platform,#6366F1,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Creative indigo + hire green +73,Consulting Firm,#0F172A,#334155,#CA8A04,#F8FAFC,#020617,#E2E8F0,Authority navy + premium gold +74,Marketing Agency,#EC4899,#F472B6,#06B6D4,#FDF2F8,#831843,#FBCFE8,Bold pink + creative cyan +75,Event Management,#7C3AED,#A78BFA,#F97316,#FAF5FF,#4C1D95,#DDD6FE,Excitement purple + action orange +76,Conference/Webinar Platform,#1E40AF,#3B82F6,#22C55E,#EFF6FF,#1E3A8A,#BFDBFE,Professional blue + join green +77,Membership/Community,#7C3AED,#A78BFA,#22C55E,#FAF5FF,#4C1D95,#DDD6FE,Community purple + join green +78,Newsletter Platform,#0369A1,#0EA5E9,#F97316,#F0F9FF,#0C4A6E,#BAE6FD,Trust blue + subscribe orange +79,Digital Products/Downloads,#6366F1,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Digital indigo + buy green +80,Church/Religious Organization,#7C3AED,#A78BFA,#CA8A04,#FAF5FF,#4C1D95,#DDD6FE,Spiritual purple + warm gold +81,Sports Team/Club,#DC2626,#EF4444,#FBBF24,#FEF2F2,#7F1D1D,#FECACA,Team red + championship gold +82,Museum/Gallery,#18181B,#27272A,#F8FAFC,#FAFAFA,#09090B,#E4E4E7,Gallery black + white space +83,Theater/Cinema,#1E1B4B,#312E81,#CA8A04,#0F0F23,#F8FAFC,#4338CA,Dramatic dark + spotlight gold +84,Language Learning App,#4F46E5,#818CF8,#22C55E,#EEF2FF,#312E81,#C7D2FE,Learning indigo + progress green +85,Coding Bootcamp,#0F172A,#1E293B,#22C55E,#020617,#F8FAFC,#334155,Terminal dark + success green +86,Cybersecurity Platform,#00FF41,#0D0D0D,#FF3333,#000000,#E0E0E0,#1F1F1F,Matrix green + alert red +87,Developer Tool / IDE,#1E293B,#334155,#22C55E,#0F172A,#F8FAFC,#475569,Code dark + run green +88,Biotech / Life Sciences,#0EA5E9,#0284C7,#10B981,#F0F9FF,#0C4A6E,#BAE6FD,DNA blue + life green +89,Space Tech / Aerospace,#F8FAFC,#94A3B8,#3B82F6,#0B0B10,#F8FAFC,#1E293B,Star white + launch blue +90,Architecture / Interior,#171717,#404040,#D4AF37,#FFFFFF,#171717,#E5E5E5,Minimal black + accent gold +91,Quantum Computing,#00FFFF,#7B61FF,#FF00FF,#050510,#E0E0FF,#333344,Quantum cyan + interference purple +92,Biohacking / Longevity,#FF4D4D,#4D94FF,#00E676,#F5F5F7,#1C1C1E,#E5E5EA,Bio red/blue + vitality green +93,Autonomous Systems,#00FF41,#008F11,#FF3333,#0D1117,#E6EDF3,#30363D,Terminal green + alert red +94,Generative AI Art,#18181B,#3F3F46,#EC4899,#FAFAFA,#09090B,#E4E4E7,Canvas neutral + creative pink +95,Spatial / Vision OS,#FFFFFF,#E5E5E5,#007AFF,#888888,#000000,#CCCCCC,Glass white + system blue +96,Climate Tech,#059669,#10B981,#FBBF24,#ECFDF5,#064E3B,#A7F3D0,Nature green + solar gold diff --git a/.codex/skills/ui-ux-pro-max/data/icons.csv b/.codex/skills/ui-ux-pro-max/data/icons.csv new file mode 100644 index 0000000..a85e97f --- /dev/null +++ b/.codex/skills/ui-ux-pro-max/data/icons.csv @@ -0,0 +1,101 @@ +No,Category,Icon Name,Keywords,Library,Import Code,Usage,Best For,Style +1,Navigation,menu,hamburger menu navigation toggle bars,Lucide,import { Menu } from 'lucide-react',,Mobile navigation drawer toggle sidebar,Outline +2,Navigation,arrow-left,back previous return navigate,Lucide,import { ArrowLeft } from 'lucide-react',,Back button breadcrumb navigation,Outline +3,Navigation,arrow-right,next forward continue navigate,Lucide,import { ArrowRight } from 'lucide-react',,Forward button next step CTA,Outline +4,Navigation,chevron-down,dropdown expand accordion select,Lucide,import { ChevronDown } from 'lucide-react',,Dropdown toggle accordion header,Outline +5,Navigation,chevron-up,collapse close accordion minimize,Lucide,import { ChevronUp } from 'lucide-react',,Accordion collapse minimize,Outline +6,Navigation,home,homepage main dashboard start,Lucide,import { Home } from 'lucide-react',,Home navigation main page,Outline +7,Navigation,x,close cancel dismiss remove exit,Lucide,import { X } from 'lucide-react',,Modal close dismiss button,Outline +8,Navigation,external-link,open new tab external link,Lucide,import { ExternalLink } from 'lucide-react',,External link indicator,Outline +9,Action,plus,add create new insert,Lucide,import { Plus } from 'lucide-react',,Add button create new item,Outline +10,Action,minus,remove subtract decrease delete,Lucide,import { Minus } from 'lucide-react',,Remove item quantity decrease,Outline +11,Action,trash-2,delete remove discard bin,Lucide,import { Trash2 } from 'lucide-react',,Delete action destructive,Outline +12,Action,edit,pencil modify change update,Lucide,import { Edit } from 'lucide-react',,Edit button modify content,Outline +13,Action,save,disk store persist save,Lucide,import { Save } from 'lucide-react',,Save button persist changes,Outline +14,Action,download,export save file download,Lucide,import { Download } from 'lucide-react',,Download file export,Outline +15,Action,upload,import file attach upload,Lucide,import { Upload } from 'lucide-react',,Upload file import,Outline +16,Action,copy,duplicate clipboard paste,Lucide,import { Copy } from 'lucide-react',,Copy to clipboard,Outline +17,Action,share,social distribute send,Lucide,import { Share } from 'lucide-react',,Share button social,Outline +18,Action,search,find lookup filter query,Lucide,import { Search } from 'lucide-react',,Search input bar,Outline +19,Action,filter,sort refine narrow options,Lucide,import { Filter } from 'lucide-react',,Filter dropdown sort,Outline +20,Action,settings,gear cog preferences config,Lucide,import { Settings } from 'lucide-react',,Settings page configuration,Outline +21,Status,check,success done complete verified,Lucide,import { Check } from 'lucide-react',,Success state checkmark,Outline +22,Status,check-circle,success verified approved complete,Lucide,import { CheckCircle } from 'lucide-react',,Success badge verified,Outline +23,Status,x-circle,error failed cancel rejected,Lucide,import { XCircle } from 'lucide-react',,Error state failed,Outline +24,Status,alert-triangle,warning caution attention danger,Lucide,import { AlertTriangle } from 'lucide-react',,Warning message caution,Outline +25,Status,alert-circle,info notice information help,Lucide,import { AlertCircle } from 'lucide-react',,Info notice alert,Outline +26,Status,info,information help tooltip details,Lucide,import { Info } from 'lucide-react',,Information tooltip help,Outline +27,Status,loader,loading spinner processing wait,Lucide,import { Loader } from 'lucide-react',,Loading state spinner,Outline +28,Status,clock,time schedule pending wait,Lucide,import { Clock } from 'lucide-react',,Pending time schedule,Outline +29,Communication,mail,email message inbox letter,Lucide,import { Mail } from 'lucide-react',,Email contact inbox,Outline +30,Communication,message-circle,chat comment bubble conversation,Lucide,import { MessageCircle } from 'lucide-react',,Chat comment message,Outline +31,Communication,phone,call mobile telephone contact,Lucide,import { Phone } from 'lucide-react',,Phone contact call,Outline +32,Communication,send,submit dispatch message airplane,Lucide,import { Send } from 'lucide-react',,Send message submit,Outline +33,Communication,bell,notification alert ring reminder,Lucide,import { Bell } from 'lucide-react',,Notification bell alert,Outline +34,User,user,profile account person avatar,Lucide,import { User } from 'lucide-react',,User profile account,Outline +35,User,users,team group people members,Lucide,import { Users } from 'lucide-react',,Team group members,Outline +36,User,user-plus,add invite new member,Lucide,import { UserPlus } from 'lucide-react',,Add user invite,Outline +37,User,log-in,signin authenticate enter,Lucide,import { LogIn } from 'lucide-react',,Login signin,Outline +38,User,log-out,signout exit leave logout,Lucide,import { LogOut } from 'lucide-react',,Logout signout,Outline +39,Media,image,photo picture gallery thumbnail,Lucide,import { Image } from 'lucide-react',,Image photo gallery,Outline +40,Media,video,movie film play record,Lucide,import { Video } from 'lucide-react',