diff --git a/.claude/commands/code-review.md b/.claude/commands/code-review.md new file mode 100644 index 0000000..4bdeed1 --- /dev/null +++ b/.claude/commands/code-review.md @@ -0,0 +1,284 @@ +--- +name: code-review +description: Analyzes local git changes and generates a detailed code review report as an HTML file (code-review.html) with the project's dark GitHub-style theme. +context: fork +agent: general-purpose +--- + +# Code Review → HTML Report + +Performs a thorough code review of the local diff and writes the result to `code-review.html` in the repository root using the project's established dark-theme HTML format. + +## Workflow + +### 1. Gather context + +Run these commands to understand the changes: + +```bash +git status +git diff HEAD # unstaged + staged vs last commit +git diff --staged # staged only (use if diff HEAD is empty) +git log -5 --oneline # recent commits for context +``` + +Also read the files that are new or heavily changed (use the Read tool) to understand surrounding code, not just the diff lines. + +### 2. Analyse findings + +Categorise every finding into one of four types: + +| Type | Tag class | When to use | +|-------------|-----------|-------------| +| **Good** | `good` | Positive patterns worth calling out — good abstractions, idiomatic code, nice refactors | +| **Issue** | `fix` | Bugs, correctness problems, security issues, broken behaviour — should be fixed | +| **Warning** | `warn` | Code smells, non-idiomatic patterns, fragile assumptions, missing tests — worth addressing | +| **Suggestion** | `info` | Nice-to-haves, ergonomic improvements, optional refactors — take or leave | + +Aim for at least 1–2 findings per type when the diff warrants it. Count each category's findings for the summary badges. + +### 3. Generate `code-review.html` + +Write the full HTML file to `code-review.html` at the repository root. +Use **exactly** the CSS variables, class names, and structure shown in the template below. Do not alter the design — only fill in the content. + +Fill in dynamic values: +- `{REPO}` — repository name from `git remote get-url origin` or directory name +- `{BRANCH}` — current branch from `git branch --show-current` +- `{DATE}` — today's date (YYYY-MM-DD) +- `{FILES_CHANGED}` — number of files with changes +- Badge counts — number of findings per category +- Cards — one `
` per finding + +For code snippets inside cards: use `` for removed lines, `` for added lines, `` for context lines. +Escape HTML entities in code (`<` → `<`, `>` → `>`, `&` → `&`). + +### 4. Open the file (macOS) + +After writing the file, run: + +```bash +open code-review.html +``` + +### 5. Confirm to the user + +Tell the user: +- The file was written to `code-review.html` +- A one-line summary of findings (e.g. "2 issues, 3 warnings, 1 suggestion, 2 positives") + +--- + +## HTML Template + +```html + + + + + +Code Review — {REPO} + + + +
+ + +
+

Code Review Report

+

Local diff review for the current working changes

+
+
Repository {REPO}
+
Branch {BRANCH}
+
Date {DATE}
+
Changed files {FILES_CHANGED}
+
+
+ + +
+
✔ {N_GOOD} Positive
+
⚠ {N_WARN} Warnings
+
✖ {N_FIX} Issues
+
ℹ {N_INFO} Suggestions
+
+ + +
✔ What looks good
+ + +
+
+ Good +

{FINDING TITLE}

+ {file/path · location} +
+
+

{Explanation of why this is good.}

+
+
+ +
+ + +
✖ Issues — should fix
+ + +
+
+ Issue +

{FINDING TITLE}

+ {file/path · location} +
+
+

{Explanation of the problem.}

+
+
Before (current)
+
// problematic code
+
+
+
After (suggested)
+
// fixed code
+
+
+
+ +
+ + +
⚠ Warnings — worth addressing
+ + + +
+ + +
ℹ Suggestions
+ + + +
+
Generated by Claude Code · local diff review · {REPO}
+
+ + +``` + +## Rules + +- Omit a section entirely (including its `
`) if there are zero findings for that category. +- Always include the Positives section even for small diffs — find something worth praising. +- Code blocks inside cards are optional for Good/Info findings but strongly recommended for Issue/Warning findings. +- Keep card titles short (one line). Put all detail in `

` tags inside `.card-body`. +- Do not truncate the HTML — write the complete file in one Write tool call. diff --git a/.claude/commands/pr-summary.md b/.claude/commands/pr-summary.md new file mode 100644 index 0000000..8e17d50 --- /dev/null +++ b/.claude/commands/pr-summary.md @@ -0,0 +1,73 @@ +--- +name: pr-summary +description: Reviews local git changes and generates a pull request summary following the project template. Use when the user asks to automate PR summary, generate PR description, fill PR template, or review local changes for a PR. +context: fork +agent: general-purpose +--- + +# PR Summary from Local Changes + +Generates a PR description that matches `.github/pull_request_template.md` by analyzing the repository's local changes (staged and/or unstaged). + +## Workflow + +1. **Gather change context** + - Run `git status` to see modified/added/deleted files and current branch. + - Run `git diff --staged` for staged changes; if empty, run `git diff` for unstaged changes (or both if the user wants everything). + - Optionally run `git log -1 --oneline` or `git log origin/main..HEAD --oneline` to see recent commits on the branch. + +2. **Infer type of change** + From the diff and file list, choose one or more types that fit: + - **Enhancement** – improvements to existing behavior + - **Bug fix** – fixes to incorrect behavior + - **Security fix** – security-related changes + - **Breaking change** – API or behavior changes that break compatibility + - **New feature** – new functionality + - **New release** – version/release bumps or release notes + - **Documentation** – docs, comments, README only + - **Refactor** – restructuring without changing behavior + +3. **Write the summary as topic bullets** + - Use only bullet points (topics), no paragraph text. + - One short line per change, e.g. "add ...", "fix ...", "update ...", "remove ...". + - No extra explanation; keep each line concise. + +4. **Output in template format** + Use this structure (from the project template). Replace the summary text and check the applicable type(s). + +5. **Suggest a commit message** + Based on the inferred type of change, suggest a conventional commit message: + - `feat: ` – for new features or enhancements + - `fix: ` – for bug fixes + - `refactor: ` – for refactors + - `docs: ` – for documentation only + - `chore: ` – for release bumps or maintenance + - `security: ` – for security fixes + +6. **Deliver the result** + - Reply with the PR summary in a **markdown code block** (so the user can copy it from the chat). + - **Also write** the same content to `pr-description.md` in the repository root so the user can open the file and copy from there. Tell the user the file path; they can delete the file after pasting into the PR. + +## Output Template + +Use this exact structure. Under "Summary", list only bullet-point topics (no paragraph). In "Type of Change", check only the box(es) that apply (use `[x]` for checked, `[ ]` for unchecked). + +```markdown +## ✨ Summary + +- +- +- ... + +## 🔧 Type of Change + +- [ ] ✨ Enhancement +- [ ] 🐞 Bug fix +- [ ] 🔐 Security fix +- [ ] 💥 Breaking change +- [ ] 🚀 New feature +- [ ] 📦 New release +- [ ] 📚 Documentation +- [ ] ♻️ Refactor + +``` diff --git a/.claude/skills/open-pr/SKILL.md b/.claude/skills/open-pr/SKILL.md new file mode 100644 index 0000000..a44f704 --- /dev/null +++ b/.claude/skills/open-pr/SKILL.md @@ -0,0 +1,77 @@ +--- +name: open-pr +description: Generates a PR summary from local git changes and opens the pull request on GitHub automatically. Use when the user asks to open, create, or submit a PR. +--- + +# Open Pull Request + +Generates a PR description from local changes and creates the pull request on GitHub using `gh`. + +## Workflow + +1. **Gather change context** + - Run `git status` to get the current branch name. + - Run `git log origin/main..HEAD --oneline` to see commits on the branch. + - Run `git diff origin/main..HEAD` to understand what changed. + +2. **Ensure changes are on a feature branch** + - If the current branch is `main` (or the default branch): + - Inspect the unstaged/staged changes (via `git diff` and `git status`) to infer a branch name in kebab-case that matches the type of change (e.g. `docs/update-readme`, `feat/add-login`, `fix/crash-on-startup`). + - Run `git checkout -b ` to create and switch to the new branch. + - Stage all changed files with `git add -A` and commit them with an appropriate conventional-commit message. + - Continue with the rest of the workflow from the new branch. + - If there are no commits ahead of `main` **and** no staged/unstaged changes, inform the user and stop. + +3. **Infer the PR title** + - Use the most recent commit message as the PR title (it is already in conventional commit format). + +4. **Infer type of change** + From the diff and file list, choose one or more types that fit: + - **Enhancement** – improvements to existing behavior + - **Bug fix** – fixes to incorrect behavior + - **Security fix** – security-related changes + - **Breaking change** – API or behavior changes that break compatibility + - **New feature** – new functionality + - **New release** – version/release bumps or release notes + - **Documentation** – docs, comments, README only + - **Refactor** – restructuring without changing behavior + +5. **Write the summary as topic bullets** + - Use only bullet points, one short line per change. + - e.g. "add ...", "fix ...", "update ...", "remove ...". + +6. **Push and create the PR** + - Push the branch to the remote: `git push -u origin `. + - Create the PR with `gh pr create` using `--title`, `--head`, `--base main`, and `--body` with the generated description. + - Return the PR URL to the user. + +## Output Template (PR body) + +Use this exact structure. Do NOT include a "Suggested Commit" section. + +```markdown +## ✨ Summary + +- +- +- ... + +## 🔧 Type of Change + +- [ ] ✨ Enhancement +- [ ] 🐞 Bug fix +- [ ] 🔐 Security fix +- [ ] 💥 Breaking change +- [ ] 🚀 New feature +- [ ] 📦 New release +- [ ] 📚 Documentation +- [ ] ♻️ Refactor +``` + +## Notes + +- Do NOT include a "Suggested Commit" section in the PR body. +- Never push directly to `main`. If the current branch is `main`, automatically create a feature branch, commit the changes, and continue. +- If there are no commits ahead of `main` and no local changes exist, inform the user and stop. +- If the branch is already up-to-date on the remote, skip the push step. +- If a PR already exists for the branch, inform the user of the existing PR URL instead of creating a new one. diff --git a/.cursor/skills/pr-summary/SKILL.md b/.claude/skills/pr-summary/SKILL.md similarity index 78% rename from .cursor/skills/pr-summary/SKILL.md rename to .claude/skills/pr-summary/SKILL.md index ec246d2..282df8f 100644 --- a/.cursor/skills/pr-summary/SKILL.md +++ b/.claude/skills/pr-summary/SKILL.md @@ -33,7 +33,16 @@ Generates a PR description that matches `.github/pull_request_template.md` by an 4. **Output in template format** Use this structure (from the project template). Replace the summary text and check the applicable type(s). -5. **Deliver the result** +5. **Suggest a commit message** + Based on the inferred type of change, suggest a conventional commit message: + - `feat: ` – for new features or enhancements + - `fix: ` – for bug fixes + - `refactor: ` – for refactors + - `docs: ` – for documentation only + - `chore: ` – for release bumps or maintenance + - `security: ` – for security fixes + +6. **Deliver the result** - Reply with the PR summary in a **markdown code block** (so the user can copy it from the chat). - **Also write** the same content to `pr-description.md` in the repository root so the user can open the file and copy from there. Tell the user the file path; they can delete the file after pasting into the PR. @@ -58,31 +67,7 @@ Use this exact structure. Under "Summary", list only bullet-point topics (no par - [ ] 📦 New release - [ ] 📚 Documentation - [ ] ♻️ Refactor -``` - -## Example - -**After reviewing a diff that adds a new Swift API and updates README:** - -```markdown -## ✨ Summary -- add public `validate(input:)` method -- update README with usage docs - -## 🔧 Type of Change +## 💬 Suggested Commit -- [ ] ✨ Enhancement -- [ ] 🐞 Bug fix -- [ ] 🔐 Security fix -- [ ] 💥 Breaking change -- [x] 🚀 New feature -- [ ] 📦 New release -- [x] 📚 Documentation -- [ ] ♻️ Refactor ``` - -## Notes - -- If there are no local changes, say so and suggest running from a branch with changes or staging files first. -- Prefer the project template at `.github/pull_request_template.md` if it diverges from the template above; the template in this skill matches the current project standard. diff --git a/.xcode-version b/.xcode-version index 478ecee..bb1930c 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -26.1.1 \ No newline at end of file +26.5 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..49ca229 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,39 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Overview + +ViewState is a Swift Package Manager library that provides a typed state machine for asynchronous data loading in UIKit apps. It handles loading, success, and error states with an observer pattern, dispatching callbacks on the main thread. + +## Commands + +```bash +# Run tests on iOS Simulator +make test-ios + +# Run tests on macOS +make test-macos + +# Run tests via SPM (faster for iteration) +swift test + +# Run a single test +swift test --filter ViewStateTests/testVerifyFetchDatasWithSuccess +``` + +Xcode version is pinned in `.xcode-version`. CI uses `bundle install` + `make test-ios` + `make test-macos`. + +## Architecture + +Three source files in `Sources/ViewState/`: + +- **`ViewState.swift`** — Public generic class `ViewState` (success type, error type). Consumers call `.fetchSource {}` to register the async work, then chain `.loadingObserver`, `.successObserver`, `.errorObserver` to subscribe. The request fires automatically once both `successObserver` and `errorObserver` are registered (loading is optional). All state mutations run through a concurrent `DispatchQueue` with `.barrier` writes; callbacks are dispatched to `DispatchQueue.main`. +- **`Observable.swift`** — Internal value wrapper that holds a dictionary of `[Int: CompletionHandler]` observers keyed by `ObserverProtocol.id`. Notifies all observers synchronously on `didSet`. +- **`ObserverProtocol.swift`** — Protocol requiring an `id: Int` used as the observer key. + +## Testing Pattern + +Tests inject a `ViewStateSpy` (subclass of `ViewState`) through the ViewModel constructor, overriding the three observer methods to capture boolean flags instead of executing real async logic. Test doubles live in `Tests/ViewStateTests/Doubles/`. + +SwiftLint is disabled in `Tests/` and `Example/`. The main lint rule to know: all classes must be `final` unless they explicitly allow inheritance. diff --git a/Makefile b/Makefile index 82d7e15..eae816f 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test-ios: set -o pipefail && \ xcodebuild test \ -scheme ViewState \ - -destination "platform=iOS Simulator,name=iPhone 17,OS=26.1" \ + -destination "platform=iOS Simulator,name=iPhone 17,OS=26.5" \ clean test | xcpretty test-macos: