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
+
+
+
+
+
+
+
+
+```
+
+## 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: