Gentleman-Skills Documentation

repository·main·Indexed 20 days ago

https://github.com/gentleman-programming/gentleman-skills

A collection of community-driven and curated instruction sets (skills) designed to enhance AI coding assistants such as Claude Code, Cursor, and VS Code Copilot. These skills provide specialized context, patterns, and best practices for various frameworks and workflows, including React 19, Next.js 15, Angular, Elixir/Phoenix anti-patterns, and Electron. The repository includes installation guides for multiple AI agents and a structured format for creating new skills using SKILL.md files.

Tokens
51.3K
Snippets
162
Records
188
Agent score
69%

What's inside Gentleman-Skills

  1. Choose between Angular Signal Forms and Reactive Forms

    main

    When working with Angular forms, select your approach based on your application's requirements and stability needs:

    • Signal Forms (v21+, experimental): Recommended for new applications leveraging Angular Signals. Provides automatic two-way binding, type-safe field access, and schema-based validation.
    • Reactive Forms (production): The stable, recommended choice for production applications. These are synchronous and easier to test.
    • Template-driven Forms: Suitable for very simple forms where complex logic or validation is not required.
  2. Handle errors as values instead of using `raise`

    main

    In Elixir, expected failures (e.g., user not found, validation errors) should be returned as values (typically {:error, reason}) rather than raising exceptions. Using raise for business logic hides error cases from @spec, forces non-idiomatic try/rescue blocks, and treats expected failures as unexpected crashes.

    Correct Pattern: Return tagged tuples like {:ok, result} or {:error, reason} to allow the caller to handle the error using pattern matching.

    # ❌ BAD - Crashes on expected error
    def fetch_user(id) do
      Repo.get(User, id) || raise "User not found"
    end
    
    # ✅ CORRECT - Return error as a value
    def fetch_user(id) do
      case Repo.get(User, id) do
        nil -> {:error, :not_found}
        user -> {:ok, user}
      end
    end
  3. App Router File Conventions in Next.js 15

    main

    Next.js 15 uses a file-system based router within the app/ directory. Specific filenames determine the behavior of the route:

    • layout.tsx: Defines a shared UI for a segment and its children (Root layout is required).
    • page.tsx: The unique UI for a specific route.
    • loading.tsx: Provides a loading state using React Suspense.
    • error.tsx: An error boundary for catching runtime errors in that segment.
    • not-found.tsx: The UI shown when the notFound() function is triggered or a route doesn't exist.
    • (group-name)/: Route groups (wrapped in parentheses) allow organizing routes without affecting the URL structure.
    • api/route.ts: Defines API endpoints (Route Handlers).
    • _folder-name/: Folday names prefixed with an underscore are treated as private folders and are not included in routing.
    app/
    ├── layout.tsx          # Root layout (required)
    ├── page.tsx            # Home page (/)
    ├── loading.tsx         # Loading UI (Suspense)
    ├── error.tsx           # Error boundary
    ├── not-found.tsx       # 404 page
    ├── (auth)/             # Route group (no URL impact)
    │   ├── login/page.tsx  # /login
    │   └── signup/page.tsx # /signup
    ├── api/
    │   └── route.ts        # API handler
    └── _components/        # Private folder (not routed)
  4. Manage Module Size by Splitting Responsibilities

    main

    Avoid creating massive modules (e.g., 800+ lines). Instead, split large modules into cohesive, smaller modules based on responsibility:

    • Validator modules: For input and business rule validation.
    • Repository modules: For database-specific queries.
    • Service/Coordinator modules: To orchestrate the flow between validators, repositories, and other side-effecting services (like Mailers).
    # Example of split modules
    defmodule MyApp.UserValidator do
      def validate_registration(attrs) do
        # Email format, password strength, etc.
      end
    end
    
    defmodule MyApp.UserRepository do
      def get_by_id(id), do: # ...
      def list_active_users, do: # ...
    end
    
    defmodule MyApp.UserService do
      alias MyApp.{UserValidator, UserRepository, Mailer}
      
      def create_user(attrs) do
        with {:ok, validated} <- UserValidator.validate_registration(attrs),
             {:ok, user} <- UserRepository.insert(validated),
             :ok <- Mailer.send_welcome_email(user) do
          {:ok, user}
        end
      end
    end
  5. Use Server Components by default

    main

    In the App Router, components are Server Components by default. You do not need a directive to make them server-side. They can be async functions, allowing you to fetch data directly within the component body.

    // No directive needed - async by default
    export default async function Page() {
      const data = await db.query();
      return <Component data={data} />;
    }
  6. Format Jira Epic content using Wiki Markup

    main

    When using the Jira MCP to update an epic's description, you must not use Markdown. Instead, use Jira Wiki markup for the customfield_10363 (Work Item Description) field to ensure correct rendering in the Jira UI.

    Wiki Markup Mapping

    • Headings: Use h2. instead of ##.
    • Bold: Use *text* instead of **text**.
    • Bullets: Use * item for bullets and ** subitem for nested bullets.

    Example Payload for customfield_10363

    {
      "customfield_10363": "h2. Feature Overview\n\n{overview}\n\nh2. Requirements\n\n*{Section 1}*\n* {requirement 1}\n\nh2. Technical Considerations\n\n*Performance:*\n* {consideration 1}"
    }
  7. Ensure ExUnit tests include assertions

    main

    Every test case should assert an expected outcome. A test that simply calls a function without using assert (or similar) does not actually verify behavior and can pass even if the code is broken.

    # ✅ CORRECT - Assert expected behavior
    test "creates user successfully" do
      assert {:ok, user} = UserService.create_user(%{name: "Juan"})
      assert user.name == "Juan"
    end
  8. Organize a React Native project structure

    main

    For scalable mobile applications, follow a modular directory structure. A recommended pattern separates routing, reusable UI, feature logic, and global state:

    src/
    ├── app/                    # Expo Router screens (if using)
    │   ├── (tabs)/            # Tab navigator group
    │   ├── (auth)/            # Auth flow group
    │   └── _layout.tsx        # Root layout
    ├── components/
    │   ├── ui/                # Reusable UI components
    │   └── features/          # Feature-specific components
    ├── hooks/                 # Custom hooks
    ├── services/              # API and external services
    ├── stores/                # State management (Zustand)
    ├── utils/                 # Utility functions
    ├── constants/             # App constants, themes
    └── types/                 # TypeScript types
    src/
    ├── app/
    │   ├── (tabs)/
    │   ├── (auth)/
    │   └── _layout.tsx
    ├── components/
    │   ├── ui/
    │   └── features/
    ├── hooks/
    ├── services/
    ├── stores/
    ├── utils/
    ├── constants/
    └── types/
  9. Avoid I/O in pure functions

    main

    Pure functions should only perform computations and should not have side effects like logging or database calls. Including I/O in pure functions makes them difficult to test, as you must mock the I/O or capture logs to verify behavior.

    # ❌ BAD - Logger call in calculation
    def calculate_total(items) do
      total = Enum.reduce(items, 0, &(&1.price + &2))
      Logger.info("Total: #{total}")  # Side effect!
      total
    end
  10. Limit the number of steps in a `with` block

    main

    Chaining more than 4 steps in a with block violates the Single Responsibility Principle and makes the code hard to read. If you have a long chain of operations, group related steps into cohesive, named functions to simplify the main flow.

    # ❌ BAD - Too many responsibilities
    with {:ok, a} <- step1(),
         {:ok, b} <- step2(a),
         {:ok, c} <- step3(b),
         {:ok, d} <- step4(c),
         {:ok, e} <- step5(d),
         {:ok, f} <- step6(e) do
      {:ok, f}
    end
    
    # ✅ CORRECT - Group into cohesive functions
    with {:ok, validated} <- validate_and_fetch(id),
         {:ok, processed} <- process_business_rules(validated),
         {:ok, result} <- persist_and_notify(processed) do
      {:ok, result}
    end
  11. Optimize re-renders with Selectors and useShallow

    main

    To prevent unnecessary re-renders, avoid selecting the entire store (e.g., const store = useUserStore()). Instead, select specific fields.

    • Single field: Pass a selector function: useUserStore((state) => state.name).
    • Multiple fields: Use the useShallow hook from zustand/react/shallow to ensure the component only re-renders if the selected object's properties actually change.
    import { useShallow } from "zustand/react/shallow";
    
    // ✅ Select specific fields to prevent unnecessary re-renders
    function UserName() {
      const name = useUserStore((state) => state.name);
      return <span>{name}</span>;
    }
    
    // ✅ For multiple fields, use useShallow
    function UserInfo() {
      const { name, email } = useUserStore(
        useShallow((state) => ({ name: state.name, email: state.email }))
      );
      return <div>{name} - {email}</div>;
    }
    
    // ❌ AVOID: Selecting entire store
    const store = useUserStore();