{"owner":"umbraco","repo":"Umbraco-CMS","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md",".github/copilot-instructions.md"],"skills":{"CLAUDE.md":"# Umbraco CMS - Multi-Project Repository\n\nEnterprise-grade CMS built on .NET 10.0. This repository contains 21 production projects organized in a layered architecture with clear separation of concerns.\n\n**Repository**: https://github.com/umbraco/Umbraco-CMS\n**License**: MIT\n**Main Branch**: `main`\n\n---\n\n## 1. Overview\n\n### What This Repository Contains\n\n**21 Production Projects** organized in 3 main categories:\n\n1. **Core Architecture** (Domain & Infrastructure)\n   - `Umbraco.Core` - Interface contracts, domain models, notifications\n   - `Umbraco.Infrastructure` - Service implementations, data access, caching\n\n2. **Web & APIs** (Presentation Layer)\n   - `Umbraco.Web.UI` - Main ASP.NET Core web application\n   - `Umbraco.Web.Common` - Shared web functionality, controllers, middleware\n   - `Umbraco.Cms.Api.Management` - Backoffice Management API (REST)\n   - `Umbraco.Cms.Api.Delivery` - Content Delivery API (headless)\n   - `Umbraco.Cms.Api.Common` - Shared API infrastructure\n\n3. **Specialized Features** (Pluggable Modules)\n   - Persistence: EF Core (modern), NPoco (legacy) for SQL Server & SQLite\n   - Caching: `PublishedCache.HybridCache` (in-memory + distributed)\n   - Search: `Examine.Lucene` (full-text search)\n   - Imaging: `Imaging.ImageSharp` v1 & v2 (image processing)\n   - Other: Static assets, targets, development tools\n\n**6 Test Projects**:\n- `Umbraco.Tests.Common` - Shared test utilities\n- `Umbraco.Tests.UnitTests` - Unit tests\n- `Umbraco.Tests.Integration` - Integration tests\n- `Umbraco.Tests.Benchmarks` - Performance benchmarks\n- `Umbraco.Tests.AcceptanceTest` - E2E tests\n- `Umbraco.Tests.AcceptanceTest.UmbracoProject` - Test instance\n\n### Key Technologies\n\n- **.NET 10.0** - Target framework for all projects\n- **ASP.NET Core** - Web framework\n- **Entity Framework Core** - Modern ORM\n- **OpenIddict** - OAuth 2.0/OpenID Connect authentication\n- **Microsoft.AspNetCore.OpenApi** - OpenAPI document generation\n- **Swashbuckle.AspNetCore.SwaggerUI** - Swagger UI for API documentation\n- **Lucene.NET** - Full-text search via Examine\n- **ImageSharp** - Image processing\n\n---\n\n## 2. General-Purpose by Default\n\nThis repository is a product platform, not an application. Every layer is consumed by code that does not live here — implementors, package developers, and other parts of the CMS. A change is finished when it serves the use case that prompted it *and* the use cases nobody has described yet.\n\n**Design to the contract.** When you change shared code, work out the rule the layer must uphold for *any* implementation of it, and make that rule hold there. Name a concrete implementation freely in the code that owns it — a specific service, package, or project; a shared or generic layer implements only the contract. A specific editor alias, content type alias, or class name appearing in generic code is the signal that a fix has been fitted to one caller — express it instead as a capability the contract exposes.\n\n**Describe the contract.** Comments and public docs use the vocabulary of the layer they sit in. In public XML doc / JSDoc a concrete illustration is welcome where it reads as one possible implementation (\"for example, an editor that emits several groups may…\"), never as the definition of the behaviour.\n\n---\n\n## 3. Repository Structure\n\n```\nUmbraco-CMS/\n├── src/                                    # 21 production projects\n│   ├── Umbraco.Core/                      # Domain contracts (interfaces only)\n│   │   └── CLAUDE.md                      # ⭐ Core architecture guide\n│   ├── Umbraco.Infrastructure/            # Service implementations\n│   ├── Umbraco.Web.Common/                # Web utilities\n│   ├── Umbraco.Web.UI/                    # Main web application\n│   ├── Umbraco.Cms.Api.Management/        # Management API\n│   ├── Umbraco.Cms.Api.Delivery/          # Delivery API (headless)\n│   ├── Umbraco.Cms.Api.Common/            # Shared API infrastructure\n│   │   └── CLAUDE.md                      # ⭐ API patterns guide\n│   ├── Umbraco.PublishedCache.HybridCache/ # Content caching\n│   ├── Umbraco.Examine.Lucene/            # Search indexing\n│   ├── Umbraco.Cms.Persistence.EFCore/    # EF Core data access\n│   ├── Umbraco.Cms.Persistence.EFCore.Sqlite/\n│   ├── Umbraco.Cms.Persistence.EFCore.SqlServer/\n│   ├── Umbraco.Cms.Persistence.Sqlite/    # Legacy SQLite\n│   ├── Umbraco.Cms.Persistence.SqlServer/ # Legacy SQL Server\n│   ├── Umbraco.Cms.Imaging.ImageSharp/    # Image processing v1\n│   ├── Umbraco.Cms.Imaging.ImageSharp2/   # Image processing v2\n│   ├── Umbraco.Cms.StaticAssets/          # Embedded assets\n│   ├── Umbraco.Cms.DevelopmentMode.Backoffice/\n│   ├── Umbraco.Cms.Targets/               # NuGet targets\n│   └── Umbraco.Cms/                       # Meta-package\n│\n├── tests/                                  # 6 test projects\n│   ├── Umbraco.Tests.Common/\n│   ├── Umbraco.Tests.UnitTests/\n│   ├── Umbraco.Tests.Integration/\n│   ├── Umbraco.Tests.Benchmarks/\n│   ├── Umbraco.Tests.AcceptanceTest/\n│   └── Umbraco.Tests.AcceptanceTest.UmbracoProject/\n│\n├── templates/                              # Project templates\n│   └── Umbraco.Templates/\n│\n├── tools/                                  # Build tools\n│   └── Umbraco.JsonSchema/\n│\n├── umbraco.sln                            # Main solution file\n├── Directory.Build.props                  # Shared build configuration\n├── Directory.Packages.props               # Centralized package versions\n├── .editorconfig                          # Code style\n└── .globalconfig                          # Roslyn analyzers\n```\n\n### Architecture Layers\n\n**Dependency Flow** (unidirectional, always flows inward):\n\n```\nWeb.UI → Web.Common → Infrastructure → Core\n                ↓\n          Api.Management → Api.Common → Infrastructure → Core\n                ↓\n          Api.Delivery → Api.Common → Infrastructure → Core\n```\n\n**Key Principle**: Core has NO dependencies (pure contracts). Infrastructure implements Core. Web/APIs depend on Infrastructure.\n\n### Project Dependencies\n\n**Core Layer**:\n- `Umbraco.Core` → No dependencies (only Microsoft.Extensions.*)\n\n**Infrastructure Layer**:\n- `Umbraco.Infrastructure` → `Umbraco.Core`\n- `Umbraco.PublishedCache.*` → `Umbraco.Infrastructure`\n- `Umbraco.Examine.Lucene` → `Umbraco.Infrastructure`\n- `Umbraco.Cms.Persistence.*` → `Umbraco.Infrastructure`\n\n**Web Layer**:\n- `Umbraco.Web.Common` → `Umbraco.Infrastructure` + caching + search\n- `Umbraco.Web.UI` → `Umbraco.Web.Common` + all features\n\n**API Layer**:\n- `Umbraco.Cms.Api.Common` → `Umbraco.Web.Common`\n- `Umbraco.Cms.Api.Management` → `Umbraco.Cms.Api.Common`\n- `Umbraco.Cms.Api.Delivery` → `Umbraco.Cms.Api.Common`\n\n---\n\n## 4. Teamwork & Collaboration\n\n### Branching Strategy\n\n- **Main branch**: `main` (protected)\n- **Branch naming convention**: `v<version>/<type>/<description>`\n\n**Format**: `v{major-version}/{type}/{kebab-case-description}`\n\n**Version**: Read from `version.json` in the repository root. Use the major version number (e.g., `v17` for version 17.x.x).\n\n**Types**:\n| Type | Use Case |\n|------|----------|\n| `feature` | New feature being introduced to the product |\n| `bugfix` | Fix to an existing issue with the product |\n| `qa` | Adding or updating unit, integration, or end-to-end tests |\n| `improvement` | Update to something that already exists but isn't broken (UI finessing, refactoring) |\n| `task` | Update that doesn't directly impact product behavior (dependency updates, build pipeline) |\n\n**Description**: A short, kebab-case description (a few words). This should be prefixed with the GitHub issue number if the update is related to resolving a tracked issue.\n\n**Examples**:\n```\nv17/bugfix/12345-correct-display-of-pending-migrations\nv17/feature/add-webhook-support\nv17/improvement/optimize-content-cache\nv17/qa/add-media-service-tests\nv17/task/update-ef-core-dependency\n```\n\nSee `.github/CONTRIBUTING.md` for full guidelines.\n\n### Pull Request Process\n\n- **PR Template**: `.github/pull_request_template.md`\n- **Required CI Checks**:\n  - All tests pass\n  - Code formatting (dotnet format)\n  - No build warnings\n- **Merge Strategy**: Squash and merge (via GitHub UI)\n- **Reviews**: Required from code owners\n\n#### PR Naming Convention\n\nUse the format: `Area: Description (closes #IssueID)`\n\n**Examples**:\n| Area | Description | Issue |\n|------|-------------|-------|\n| Relations: | Move persistence of relations from repository into notification handlers | (closes #00000) |\n| Management API: | Correct the population of the parent for sibling items when retrieved under a folder | |\n| Docs: | Updated contributing guidelines to welcome contributions on bugfixes | |\n\n**Area**: The feature or aspect affected (e.g., UFM, TipTap, Docs, Segmentation, Migrations). Helps readers quickly understand what is being changed.\n\n**Description Best Practices**:\n- Include the area of change (Relations, Management API, etc.)\n- Describe the change and its impact\n- Be specific, not vague (describe \"a golden retriever\" not just \"a dog\")\n\n**Issue Linking**: Add `(closes #IssueID)` to the title for readability, AND include a closing keyword on its own line in the PR body (e.g., `Fixes #IssueID`) so GitHub actually auto-links and auto-closes the issue on merge. GitHub only parses closing keywords (`closes`, `fixes`, `resolves`) from the PR body or commit messages — the title suffix is cosmetic and does **not** trigger auto-close on its own.\n\n### Commit Messages\n\nFollow Conventional Commits format:\n```\n<type>(<scope>): <description>\n\nTypes: feat, fix, docs, style, refactor, test, chore\nScope: project name (core, web, api, etc.)\n\nExamples:\nfeat(core): add IContentService.GetByIds method\nfix(api): resolve null reference in schema handler\ndocs(web): update routing documentation\n```\n\n### Code Owners\n\nProject ownership is distributed across teams. Check individual project directories for ownership.\n\n---\n\n## 5. Architecture Patterns\n\n### Core Architectural Decisions\n\n1. **Layered Architecture with Dependency Inversion**\n   - Core defines contracts (interfaces)\n   - Infrastructure implements contracts that need Infrastructure-owned machinery\n   - Web/APIs consume implementations via DI\n\n   **Where service implementations live**: Services whose dependencies are satisfiable from Core interfaces alone (repositories, scope, config, other Core services) live in `Umbraco.Core/Services/` — this covers the majority of domain services (`MemberService`, `ContentService`, `MediaService`, `ContentTypeService`, `EntityService`, `AuditService`, `ExternalMemberService`, etc.). Service implementations only live in `Umbraco.Infrastructure/Services/Implement/` when they genuinely need Infrastructure concerns — Examine indexes (`ContentSearchService`, `MediaSearchService`, `IndexedEntitySearchService`), log files (`LogViewerRepository`), packaging internals (`PackagingService`), webhook firing (`WebhookFiringService`), distributed-job coordination (`DistributedJobService`). When adding a new service, default to Core and only move to Infrastructure if a concrete dependency forces it.\n\n2. **Interface-First Design**\n   - All services defined as interfaces in Core\n   - Enables testing, polymorphism, extensibility\n\n3. **Notification Pattern** (not C# events)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"2. Notification System (Event Handling)\"\n\n4. **Composer Pattern** (DI registration)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"3. Composer Pattern (DI Registration)\"\n\n5. **Scoping Pattern** (Unit of Work)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"5. Scoping Pattern (Unit of Work)\"\n\n6. **Attempt Pattern** (operation results)\n   - `Attempt<TResult, TStatus>` instead of exceptions\n   - Strongly-typed operation status enums\n\n### Key Design Patterns Used\n\n- **Repository Pattern** - Data access abstraction\n- **Unit of Work** - Scoping for transactions\n- **Builder Pattern** - `ProblemDetailsBuilder` for API errors\n- **Strategy Pattern** - OpenAPI handlers (schema ID, operation ID)\n- **Options Pattern** - All configuration via `IOptions<T>`\n- **Factory Pattern** - Content type factories\n- **Mediator Pattern** - Notification aggregator\n\n---\n\n## 6. Avoiding Breaking Changes\n\nNo binary breaking changes are allowed within a major version. Three patterns are used:\n\n### 6.1 Obsolete Constructor + StaticServiceProvider\n\nWhen a public class needs new dependencies, obsolete the existing constructor and add a new one. The old constructor delegates to the new one, resolving missing deps via `StaticServiceProvider`.\n\n```csharp\n[Obsolete(\"Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.\")]\npublic MyService(IDependencyA depA)\n    : this(\n        depA,\n        StaticServiceProvider.Instance.GetRequiredService<IDependencyB>())\n{\n}\n\npublic MyService(IDependencyA depA, IDependencyB depB)\n{\n    _depA = depA;\n    _depB = depB;\n}\n```\n\n**Examples**:\n- `ContentCollectionPresentationFactory` - added `FlagProviderCollection`\n- `CacheInstructionService` - added `ILastSyncedManager`, `IRepositoryCacheVersionService`\n- `DocumentPresentationFactory` - added `FlagProviderCollection`\n\n**Rules**:\n- Old constructor marked `[Obsolete(\"... Scheduled for removal in Umbraco {current-major+2}.\")]`\n- Old constructor calls new constructor via `: this(...)`\n- Uses `StaticServiceProvider.Instance.GetRequiredService<T>()` for new params only\n- DI registration must use the NEW constructor (old is for external consumers only)\n\n### 6.2 Obsolete Method + New Overload\n\nWhen a public method signature needs to change, add the new method/overload and obsolete the old. The obsolete method should call the new one with suitable defaults.\n\n```csharp\n[Obsolete(\"Use the overload taking all parameters. Scheduled for removal in Umbraco 19.\")]\npublic void DoThing(string name)\n    => DoThing(name, extraParam: null);\n\npublic void DoThing(string name, string? extraParam)\n{\n    // Real implementation here\n}\n```\n\n**Rules**:\n- Old method marked `[Obsolete]` with removal schedule\n- DRY: old method calls new method, providing defaults for new parameters\n- All internal callers must be updated to use the new method\n- No callers should remain on the obsolete method within the codebase\n\n### 6.3 Default Interface Implementation\n\nWhen adding methods to a public interface, provide a default implementation so existing external implementations don't break.\n\n```csharp\npublic interface IMyService\n{\n    // Existing method\n    void ExistingMethod();\n\n    // New method with default implementation\n    void NewMethod(string param)\n        => ExistingMethod(); // delegate to existing if possible\n}\n```\n\n**Strategies for the default** (in order of preference):\n1. **Use existing interface methods** to satisfy the contract (even if not optimal)\n2. **Return a sensible default** like empty collection, null, etc.\n3. **Throw `NotImplementedException`** if no reasonable default exists\n\n**Example**: `IContentService.SaveBlueprint` - new overload with `IContent? createdFromContent` has a default impl that calls the old method (ignoring the new param).\n\n**Example**: `IDocumentPresentationFactory.CreateCulturePublishScheduleModels` - full default implementation with logic, uses `StaticServiceProvider` for dependency resolution within the interface.\n\n**Rules**:\n- Add `// TODO (V{next-major}): Remove the default implementation when {obsolete method} is removed.` comment\n- Default impl should be functionally correct even if not optimal\n- If using `StaticServiceProvider` in a default impl, note this is temporary\n\n### 6.4 General Rules\n\n- **Removal policy**: Obsoleted members must remain for at least one full major version before removal. If obsoleted in version N, the earliest removal is version N+2. For example, something obsoleted in v17 is scheduled for removal in v19 (giving the whole of v18 as a deprecation period).\n- All `[Obsolete]` attributes must include **\"Scheduled for removal in Umbraco {current+2}\"**\n- Read `version.json` to determine the current major version\n- Suppress `CS0618` warnings where obsolete members must call each other:\n  ```csharp\n  #pragma warning disable CS0618 // Type or member is obsolete\n      => OldMethod(param);\n  #pragma warning restore CS0618 // Type or member is obsolete\n  ```\n- Update ALL internal callers to use the new API - no internal code should use obsolete members\n\n---\n\n## 7. Project-Specific Notes\n\n### Centralized Package Management\n\n**NuGet package versions** are centralized in `Directory.Packages.props`. There are two `Directory.Packages.props` files in the source tree, with multi-level merging enabled so the test file inherits from the root:\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production source code packages — referenced by all `src/**` projects |\n| `tests/Directory.Packages.props` | Test-only packages (NUnit, Moq, Bogus, BenchmarkDotNet, etc.) — adds entries on top of the inherited root file |\n\nWhen updating dependencies, decide which file the package belongs in:\n- A package used only by test projects → `tests/Directory.Packages.props`\n- A package used by any production project (or by both production and tests) → root `Directory.Packages.props`\n\n```xml\n<!-- Individual projects reference WITHOUT version -->\n<PackageReference Include=\"Microsoft.AspNetCore.OpenApi\" />\n\n<!-- Versions defined in Directory.Packages.props -->\n<PackageVersion Include=\"Microsoft.AspNetCore.OpenApi\" Version=\"10.0.0\" />\n```\n\n**Opt-out**: `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` sets `<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>` and specifies versions inline (for `Microsoft.EntityFrameworkCore.Design`, `Microsoft.Build.Tasks.Core`, `Microsoft.ICU.ICU4C.Runtime`, etc.). Update those versions directly in that csproj when bumping. Two further `Directory.Packages.props` files exist under `templates/` for the project/extension templates and have their own version sets — keep `Microsoft.AspNetCore.OpenApi` aligned between the root file and `templates/UmbracoExtension/`.\n\n### Build Configuration\n\n- `Directory.Build.props` - Shared properties (target framework, company, copyright)\n- `.editorconfig` - Code style rules\n- `.globalconfig` - Roslyn analyzer rules\n\n### Persistence Layer - NPoco and EF Core\n\nThe repository contains BOTH (actively supported):\n- **Current**: NPoco-based persistence (`Umbraco.Cms.Persistence.Sqlite`, `Umbraco.Cms.Persistence.SqlServer`) - widely used and fully supported\n- **Future**: EF Core-based persistence (`Umbraco.Cms.Persistence.EFCore.*`) - migration in progress\n\n**Note**: The codebase is actively migrating to EF Core, but NPoco remains the primary persistence layer and is not deprecated. Both are fully supported.\n\n### Authentication: OpenIddict\n\nAll APIs use **OpenIddict** (OAuth 2.0/OpenID Connect):\n- Reference tokens (not JWT) for better security\n- **Secure cookie-based token storage** (v17+) - tokens stored in HTTP-only cookies with `__Host-` prefix\n- Tokens are redacted from client-side responses and passed via secure cookies only (`[redacted]` placeholder)\n- ASP.NET Core Data Protection for token encryption\n- Configured in `Umbraco.Cms.Api.Common`\n- API requests must include credentials (`credentials: include` for fetch)\n\n**Load Balancing Requirement**: All servers must share the same Data Protection key ring.\n\n**Frontend auth pitfalls** — see `src/Umbraco.Web.UI.Client/docs/edge-cases.md` (Auth & Cross-tab section) and `docs/security.md`. Key points:\n- Never call `validateToken()` per API request — it revokes the previous reference token (ID2019 errors)\n- `window.opener` is set for ANY `window.open()` target, not only OAuth popups — scope guards to the pathname too\n- BroadcastChannel does not deliver messages to the sender's own tab\n\n### Content Caching Strategy\n\n**HybridCache** (`Umbraco.PublishedCache.HybridCache`):\n- In-memory cache + distributed cache support\n- Published content only (not draft)\n- Invalidated via notifications and cache refreshers\n\n### API Versioning\n\nAPIs use `Asp.Versioning.Mvc`:\n- Management API: `/umbraco/management/api/v{version}/*`\n- Delivery API: `/umbraco/delivery/api/v{version}/*`\n- OpenAPI docs: `/umbraco/openapi/management.json`, `/umbraco/openapi/delivery.json`\n- Swagger UI: `/umbraco/openapi/`\n\n### Updating `OpenApi.json` (Management API)\n\nWhen a PR changes Management API controllers or models, the `OpenApi.json` file in the Management API project must be updated, along with the generated backoffice client that is derived from it.\n\nWith the Umbraco instance running locally (in a non-Production environment — Swagger isn't mapped in Production):\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\n```\n\nThe first fetches the document from `/umbraco/swagger/management/swagger.json` byte-for-byte into `src/Umbraco.Cms.Api.Management/OpenApi.json`; the second regenerates the hey-api client from that committed file. Both results must be committed together.\n\nThey are two separate commands on purpose. The client generator only ever reads the committed schema — never a running site — so the schema and the client it produced always land in the same commit.\n\nThe `/umb-update-openapi` skill wraps this: it starts and stops a backend for you if one isn't already running, and explains the diff. Reach for it when you want the whole round trip handled.\n\n**Important**: The fetch is byte-for-byte on purpose, so the endpoint stays the source of truth. Don't reformat the result — commit only the substantive changes, not IDE-applied formatting (whitespace, reordering, etc.). Extraneous formatting diffs make PRs harder to review and merge-ups more error-prone.\n\n### Backoffice npm Package\n\nThe backoffice is published to npm as `@umbraco-cms/backoffice`. Runtime dependencies are provided via importmap; npm peerDependencies provide types only. For full details on dependency hoisting, version range logic, and plugin development, see `/src/Umbraco.Web.UI.Client/CLAUDE.md` → \"npm Package Publishing\".\n\n### SQL Server 2100-parameter limit\n\nAny `WHERE IN (@0, @1, ...)` built from a runtime-sized collection risks hitting SQL Server's 2100-parameter ceiling and throwing `SqlException` 8003 in production.\n\nBatch with `IEnumerable<T>.InGroupsOf(Constants.Sql.MaxParameterCount)` or `Database.FetchByGroups(...)` whenever the collection size is driven by user data — not just when it currently fits. Watch for products of two scaling dimensions (documents × languages, properties × versions) and config-tunable batch sizes whose defaults are safe but ceilings aren't.\n\nFull guidance, safe patterns and decision rule: see `/src/Umbraco.Infrastructure/CLAUDE.md` → \"Avoiding the SQL Server 2100-parameter limit\".\n\n### Known Limitations\n\n1. **Circular Dependencies**: Avoided via `Lazy<T>` or event notifications\n2. **Multi-Server**: Requires shared Data Protection key ring and synchronized clocks (NTP)\n3. **Database Support**: SQL Server, SQLite\n\n---\n\n## 8. CI/CD — Claude AI Assistant\n\nTwo GitHub Actions workflows powered by `anthropics/claude-code-action@v1`. Advisory only — does not block merging.\n\n### Workflows\n\n| File | Trigger | Purpose |\n|------|---------|---------|\n| `claude-review.yml` | `pull_request: [opened, ready_for_review]` | Auto-review every non-draft PR using the `umb-review` skill |\n| `claude.yml` | `@claude` comments, issue assign/label | Interactive assistant for PRs and issues |\n\n### Auto-Review (`claude-review.yml`)\n\nRuns the full `.claude/skills/umb-review/SKILL.md` procedure on every newly opened or un-drafted PR. Produces inline comments per finding and one summary comment with a verdict. Skips draft PRs. No turn limit.\n\n### Interactive (`claude.yml`)\n\nResponds to `@claude` mentions on PRs and issues. The trigger phrase is stripped before Claude sees the message, so:\n\n- `@claude review` → light review using `gh pr diff` (not the umb-review skill)\n- `@claude fix ...` → implements a fix on a new branch\n- `@claude help` → answers questions about the codebase\n- `@claude label` → applies labels\n- `@claude` (empty) → defaults to `review` on PRs, `help` on issues\n\nAlso triggers on issue assignment to `claude` or adding the `claude` label. Gated: only runs when `@claude` appears in the comment/issue body. Max 25 turns.\n\n**Allowed Bash tools**: `gh`, `git`, `npm`, `dotnet` (interactive only; auto-review allows `gh` and `git`).\n\n### Labels\n\nBoth workflows apply labels based on content:\n\n**On PRs** (based on changed files):\n\n| Label | Condition |\n|-------|-----------|\n| `area/frontend` | Files under `src/Umbraco.Web.UI.Client/` |\n| `area/backend` | `.cs` files outside the frontend client |\n| `area/test` | Only test files changed |\n| `category/api` | Management or Delivery API files |\n| `category/breaking` | Breaking changes detected |\n| `category/localization` | Localization/language files |\n| `category/test-automation` | Only test files changed |\n| `category/refactor` | Pure refactoring, no new features |\n| `category/performance` | Performance-related changes |\n| `category/ux` | User-facing changes |\n| `category/ui` | UI layer changes |\n\n**On Issues** (based on content): same `area/*` and `category/*` labels, plus `affected/v14` through `affected/v17` and `affected/backoffice`.\n\nLabels are only added, never removed. Claude applies only labels it is confident about.\n\n### Key Implementation Notes\n\n- **Checkout required** — the action internally runs `git fetch origin main` for trusted file restoration. Without `actions/checkout`, it fails with `fatal: not a git repository`.\n- **`id-token: write` permission** — required for OIDC token exchange with the Claude GitHub App.\n- **Trigger phrase stripping** — the action strips `@claude` from comments before passing to Claude. Prompts must reference commands without the prefix (e.g., `review` not `@claude review`).\n- **PR number injection** — the interactive workflow injects the PR/issue number into the prompt via `${{ github.event.issue.number }}` since Claude can't discover it from `gh pr view` when checked out on `main`.\n\n---\n\n## 9. Code Comment Policy\n\n**Default to no comment.** Applies to all code in this repository — C#, TypeScript, Razor, build scripts. Well-named identifiers and small functions carry the meaning; a comment is a fallback for what the code genuinely cannot say — a non-obvious *why*, a subtle invariant the types don't enforce, or a surprising edge case the code handles deliberately. Add XML doc / JSDoc on public members, but keep it concise.\n\n**Write the rule, at the altitude of the code it sits in.** A comment states what must hold going forward, in the vocabulary of the layer it lives in — see §2. Where a comment exists because something once went wrong, the rule is what survives; the incident and the reported scenario belong in the commit message and PR body:\n\n```typescript\n// A resolver may emit several groups of inner values.\n// Pair each draft group with its persisted group by the\n// identifier the resolver supplies, not by call order.\n```\n\n**Keep issue references where they stay actionable.** A tracked issue link (`(#21996)`, `https://...`) is welcome wherever it explains a non-obvious *why* — a guard whose reason isn't clear from the code, a workaround for a defect this code cannot fix (so it can be deleted when the fix lands), or a regression test recording why it exists. Elsewhere the comment stands on its own in general terms.\n\n**Let commit messages and PR descriptions carry provenance.** Which task, PR, or issue produced a change (`Fix for X`, `Used by Y`, `Added for the Z flow`, `See PR #1234`) is recorded in git history, where it stays accurate. Source describes the code as it is now.\n\n### TODOs\n\nAllowed, and can name the specific issue, implementation, or use case it concerns — the one exception to §2, since the comment is deleted once the TODO is done. Keep them short and trackable: `// TODO (V19): remove once obsolete overload is gone` or `// TODO: pagination [NL]`. A TODO should have an author or a version trigger.\n\n---\n\n## 10. Testing Practices\n\nA test for shared code asserts the general rule from §2, not the scenario that reported it, and is named for the rule.\n\n### Tests for a bug fix must fail before the fix\n\nVerify any test you add for a bug fix actually catches the bug: either write the failing test first (TDD), or temporarily revert the production change and confirm the test fails before re-applying. A test that passes both ways proves nothing. Watch for coincidental passes — default seed/sort orders can make a buggy path produce the right answer for the test's specific inputs; construct inputs so the broken and fixed behaviours give visibly different results.\n\nFor integration tests that exercise caching or cache refreshers, see `tests/Umbraco.Tests.Integration/CLAUDE.md` — the harness disables caching by default, which can produce false greens.\n\n---\n\n## 11. Verification Discipline\n\n- **Fresh build before trusting a green.** Never treat `--no-build` or cached/incremental output as proof a change compiles or passes — a stale run can mask a compile error. Rebuild before reporting build or test state. (Integration tests have a related false-green trap — see `tests/Umbraco.Tests.Integration/CLAUDE.md`.)\n- **Grep the branch you think you're on.** A search only supports a claim against the branch actually checked out, so confirm HEAD is where you expect before drawing a conclusion from a grep. Easy to get wrong whenever the tree moves under you — reviewing a PR head, switching worktrees, or mid merge-up/rebase.\n\n---\n\n## Quick Reference\n\n### Essential Commands\n\n```bash\n# Build solution\ndotnet build\n\n# Run all tests\ndotnet test\n\n# Run specific test category\ndotnet test --filter \"Category=Integration\"\n\n# Format code\ndotnet format\n\n# Pack all projects\ndotnet pack -c Release\n```\n\n### Integration Test Database Configuration\n\nIntegration tests are configured in `tests/Umbraco.Tests.Integration/appsettings.Tests.json`.\n\nThe `Tests:Database:DatabaseType` setting controls which database is used:\n- `\"SQLite\"` (default) - No external dependencies\n- `\"LocalDb\"` - Uses SQL Server LocalDB, required for SQL Server-specific tests (e.g., page-level locking, `sys.dm_tran_locks`)\n\nSQL Server-specific tests use `BaseTestDatabase.IsSqlite()` to skip when running on SQLite.\n\n### Key Projects\n\n| Project | Type | Description |\n|---------|------|-------------|\n| **Umbraco.Core** | Library | Interface contracts and domain models |\n| **Umbraco.Infrastructure** | Library | Service implementations and data access |\n| **Umbraco.Web.UI** | Application | Main web application (Razor/MVC) |\n| **Umbraco.Cms.Api.Management** | Library | Management API (backoffice) |\n| **Umbraco.Cms.Api.Delivery** | Library | Delivery API (headless CMS) |\n| **Umbraco.Cms.Api.Common** | Library | Shared API infrastructure |\n| **Umbraco.PublishedCache.HybridCache** | Library | Published content caching |\n| **Umbraco.Examine.Lucene** | Library | Full-text search indexing |\n\n### Important Files\n\n- **Solution**: `umbraco.sln`\n- **Build Config**: `Directory.Build.props`, `Directory.Packages.props`\n- **Code Style**: `.editorconfig`, `.globalconfig`\n- **Documentation**: `/CLAUDE.md`, `/src/Umbraco.Core/CLAUDE.md`, `/src/Umbraco.Cms.Api.Common/CLAUDE.md`\n\n### Project-Specific Documentation\n\nFor detailed information about individual projects, see their CLAUDE.md files:\n- **Core Architecture**: `/src/Umbraco.Core/CLAUDE.md` - Service contracts, notification patterns\n- **API Infrastructure**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md` - OpenAPI, authentication, serialization\n- **Backoffice Frontend**: `/src/Umbraco.Web.UI.Client/CLAUDE.md` - Lit web components, extension system, auth client\n\n**Important**: When working on backoffice client code (anything under `src/Umbraco.Web.UI.Client/`), read `/src/Umbraco.Web.UI.Client/CLAUDE.md` first. It contains action-specific checklists (deprecation, testing, security, etc.) that are not duplicated here.\n\n### Getting Help\n\n- **Official Docs**: https://docs.umbraco.com/\n- **Contributing Guide**: `.github/CONTRIBUTING.md`\n- **Issues**: https://github.com/umbraco/Umbraco-CMS/issues\n- **Community**: https://forum.umbraco.com/\n- **Releases**: https://releases.umbraco.com/\n\n---\n\n**This repository follows a layered architecture with strict dependency rules. The Core defines contracts, Infrastructure implements them, and Web/APIs consume them. Each layer can be understood independently, but dependencies always flow inward toward Core.**\n",".github/copilot-instructions.md":"The full development guide for this repository lives in [CLAUDE.md](../CLAUDE.md). Please read that file for complete instructions on architecture, build steps, testing, branching conventions, and coding patterns.\n"},"files":{"CLAUDE.md":"# Umbraco CMS - Multi-Project Repository\n\nEnterprise-grade CMS built on .NET 10.0. This repository contains 21 production projects organized in a layered architecture with clear separation of concerns.\n\n**Repository**: https://github.com/umbraco/Umbraco-CMS\n**License**: MIT\n**Main Branch**: `main`\n\n---\n\n## 1. Overview\n\n### What This Repository Contains\n\n**21 Production Projects** organized in 3 main categories:\n\n1. **Core Architecture** (Domain & Infrastructure)\n   - `Umbraco.Core` - Interface contracts, domain models, notifications\n   - `Umbraco.Infrastructure` - Service implementations, data access, caching\n\n2. **Web & APIs** (Presentation Layer)\n   - `Umbraco.Web.UI` - Main ASP.NET Core web application\n   - `Umbraco.Web.Common` - Shared web functionality, controllers, middleware\n   - `Umbraco.Cms.Api.Management` - Backoffice Management API (REST)\n   - `Umbraco.Cms.Api.Delivery` - Content Delivery API (headless)\n   - `Umbraco.Cms.Api.Common` - Shared API infrastructure\n\n3. **Specialized Features** (Pluggable Modules)\n   - Persistence: EF Core (modern), NPoco (legacy) for SQL Server & SQLite\n   - Caching: `PublishedCache.HybridCache` (in-memory + distributed)\n   - Search: `Examine.Lucene` (full-text search)\n   - Imaging: `Imaging.ImageSharp` v1 & v2 (image processing)\n   - Other: Static assets, targets, development tools\n\n**6 Test Projects**:\n- `Umbraco.Tests.Common` - Shared test utilities\n- `Umbraco.Tests.UnitTests` - Unit tests\n- `Umbraco.Tests.Integration` - Integration tests\n- `Umbraco.Tests.Benchmarks` - Performance benchmarks\n- `Umbraco.Tests.AcceptanceTest` - E2E tests\n- `Umbraco.Tests.AcceptanceTest.UmbracoProject` - Test instance\n\n### Key Technologies\n\n- **.NET 10.0** - Target framework for all projects\n- **ASP.NET Core** - Web framework\n- **Entity Framework Core** - Modern ORM\n- **OpenIddict** - OAuth 2.0/OpenID Connect authentication\n- **Microsoft.AspNetCore.OpenApi** - OpenAPI document generation\n- **Swashbuckle.AspNetCore.SwaggerUI** - Swagger UI for API documentation\n- **Lucene.NET** - Full-text search via Examine\n- **ImageSharp** - Image processing\n\n---\n\n## 2. General-Purpose by Default\n\nThis repository is a product platform, not an application. Every layer is consumed by code that does not live here — implementors, package developers, and other parts of the CMS. A change is finished when it serves the use case that prompted it *and* the use cases nobody has described yet.\n\n**Design to the contract.** When you change shared code, work out the rule the layer must uphold for *any* implementation of it, and make that rule hold there. Name a concrete implementation freely in the code that owns it — a specific service, package, or project; a shared or generic layer implements only the contract. A specific editor alias, content type alias, or class name appearing in generic code is the signal that a fix has been fitted to one caller — express it instead as a capability the contract exposes.\n\n**Describe the contract.** Comments and public docs use the vocabulary of the layer they sit in. In public XML doc / JSDoc a concrete illustration is welcome where it reads as one possible implementation (\"for example, an editor that emits several groups may…\"), never as the definition of the behaviour.\n\n---\n\n## 3. Repository Structure\n\n```\nUmbraco-CMS/\n├── src/                                    # 21 production projects\n│   ├── Umbraco.Core/                      # Domain contracts (interfaces only)\n│   │   └── CLAUDE.md                      # ⭐ Core architecture guide\n│   ├── Umbraco.Infrastructure/            # Service implementations\n│   ├── Umbraco.Web.Common/                # Web utilities\n│   ├── Umbraco.Web.UI/                    # Main web application\n│   ├── Umbraco.Cms.Api.Management/        # Management API\n│   ├── Umbraco.Cms.Api.Delivery/          # Delivery API (headless)\n│   ├── Umbraco.Cms.Api.Common/            # Shared API infrastructure\n│   │   └── CLAUDE.md                      # ⭐ API patterns guide\n│   ├── Umbraco.PublishedCache.HybridCache/ # Content caching\n│   ├── Umbraco.Examine.Lucene/            # Search indexing\n│   ├── Umbraco.Cms.Persistence.EFCore/    # EF Core data access\n│   ├── Umbraco.Cms.Persistence.EFCore.Sqlite/\n│   ├── Umbraco.Cms.Persistence.EFCore.SqlServer/\n│   ├── Umbraco.Cms.Persistence.Sqlite/    # Legacy SQLite\n│   ├── Umbraco.Cms.Persistence.SqlServer/ # Legacy SQL Server\n│   ├── Umbraco.Cms.Imaging.ImageSharp/    # Image processing v1\n│   ├── Umbraco.Cms.Imaging.ImageSharp2/   # Image processing v2\n│   ├── Umbraco.Cms.StaticAssets/          # Embedded assets\n│   ├── Umbraco.Cms.DevelopmentMode.Backoffice/\n│   ├── Umbraco.Cms.Targets/               # NuGet targets\n│   └── Umbraco.Cms/                       # Meta-package\n│\n├── tests/                                  # 6 test projects\n│   ├── Umbraco.Tests.Common/\n│   ├── Umbraco.Tests.UnitTests/\n│   ├── Umbraco.Tests.Integration/\n│   ├── Umbraco.Tests.Benchmarks/\n│   ├── Umbraco.Tests.AcceptanceTest/\n│   └── Umbraco.Tests.AcceptanceTest.UmbracoProject/\n│\n├── templates/                              # Project templates\n│   └── Umbraco.Templates/\n│\n├── tools/                                  # Build tools\n│   └── Umbraco.JsonSchema/\n│\n├── umbraco.sln                            # Main solution file\n├── Directory.Build.props                  # Shared build configuration\n├── Directory.Packages.props               # Centralized package versions\n├── .editorconfig                          # Code style\n└── .globalconfig                          # Roslyn analyzers\n```\n\n### Architecture Layers\n\n**Dependency Flow** (unidirectional, always flows inward):\n\n```\nWeb.UI → Web.Common → Infrastructure → Core\n                ↓\n          Api.Management → Api.Common → Infrastructure → Core\n                ↓\n          Api.Delivery → Api.Common → Infrastructure → Core\n```\n\n**Key Principle**: Core has NO dependencies (pure contracts). Infrastructure implements Core. Web/APIs depend on Infrastructure.\n\n### Project Dependencies\n\n**Core Layer**:\n- `Umbraco.Core` → No dependencies (only Microsoft.Extensions.*)\n\n**Infrastructure Layer**:\n- `Umbraco.Infrastructure` → `Umbraco.Core`\n- `Umbraco.PublishedCache.*` → `Umbraco.Infrastructure`\n- `Umbraco.Examine.Lucene` → `Umbraco.Infrastructure`\n- `Umbraco.Cms.Persistence.*` → `Umbraco.Infrastructure`\n\n**Web Layer**:\n- `Umbraco.Web.Common` → `Umbraco.Infrastructure` + caching + search\n- `Umbraco.Web.UI` → `Umbraco.Web.Common` + all features\n\n**API Layer**:\n- `Umbraco.Cms.Api.Common` → `Umbraco.Web.Common`\n- `Umbraco.Cms.Api.Management` → `Umbraco.Cms.Api.Common`\n- `Umbraco.Cms.Api.Delivery` → `Umbraco.Cms.Api.Common`\n\n---\n\n## 4. Teamwork & Collaboration\n\n### Branching Strategy\n\n- **Main branch**: `main` (protected)\n- **Branch naming convention**: `v<version>/<type>/<description>`\n\n**Format**: `v{major-version}/{type}/{kebab-case-description}`\n\n**Version**: Read from `version.json` in the repository root. Use the major version number (e.g., `v17` for version 17.x.x).\n\n**Types**:\n| Type | Use Case |\n|------|----------|\n| `feature` | New feature being introduced to the product |\n| `bugfix` | Fix to an existing issue with the product |\n| `qa` | Adding or updating unit, integration, or end-to-end tests |\n| `improvement` | Update to something that already exists but isn't broken (UI finessing, refactoring) |\n| `task` | Update that doesn't directly impact product behavior (dependency updates, build pipeline) |\n\n**Description**: A short, kebab-case description (a few words). This should be prefixed with the GitHub issue number if the update is related to resolving a tracked issue.\n\n**Examples**:\n```\nv17/bugfix/12345-correct-display-of-pending-migrations\nv17/feature/add-webhook-support\nv17/improvement/optimize-content-cache\nv17/qa/add-media-service-tests\nv17/task/update-ef-core-dependency\n```\n\nSee `.github/CONTRIBUTING.md` for full guidelines.\n\n### Pull Request Process\n\n- **PR Template**: `.github/pull_request_template.md`\n- **Required CI Checks**:\n  - All tests pass\n  - Code formatting (dotnet format)\n  - No build warnings\n- **Merge Strategy**: Squash and merge (via GitHub UI)\n- **Reviews**: Required from code owners\n\n#### PR Naming Convention\n\nUse the format: `Area: Description (closes #IssueID)`\n\n**Examples**:\n| Area | Description | Issue |\n|------|-------------|-------|\n| Relations: | Move persistence of relations from repository into notification handlers | (closes #00000) |\n| Management API: | Correct the population of the parent for sibling items when retrieved under a folder | |\n| Docs: | Updated contributing guidelines to welcome contributions on bugfixes | |\n\n**Area**: The feature or aspect affected (e.g., UFM, TipTap, Docs, Segmentation, Migrations). Helps readers quickly understand what is being changed.\n\n**Description Best Practices**:\n- Include the area of change (Relations, Management API, etc.)\n- Describe the change and its impact\n- Be specific, not vague (describe \"a golden retriever\" not just \"a dog\")\n\n**Issue Linking**: Add `(closes #IssueID)` to the title for readability, AND include a closing keyword on its own line in the PR body (e.g., `Fixes #IssueID`) so GitHub actually auto-links and auto-closes the issue on merge. GitHub only parses closing keywords (`closes`, `fixes`, `resolves`) from the PR body or commit messages — the title suffix is cosmetic and does **not** trigger auto-close on its own.\n\n### Commit Messages\n\nFollow Conventional Commits format:\n```\n<type>(<scope>): <description>\n\nTypes: feat, fix, docs, style, refactor, test, chore\nScope: project name (core, web, api, etc.)\n\nExamples:\nfeat(core): add IContentService.GetByIds method\nfix(api): resolve null reference in schema handler\ndocs(web): update routing documentation\n```\n\n### Code Owners\n\nProject ownership is distributed across teams. Check individual project directories for ownership.\n\n---\n\n## 5. Architecture Patterns\n\n### Core Architectural Decisions\n\n1. **Layered Architecture with Dependency Inversion**\n   - Core defines contracts (interfaces)\n   - Infrastructure implements contracts that need Infrastructure-owned machinery\n   - Web/APIs consume implementations via DI\n\n   **Where service implementations live**: Services whose dependencies are satisfiable from Core interfaces alone (repositories, scope, config, other Core services) live in `Umbraco.Core/Services/` — this covers the majority of domain services (`MemberService`, `ContentService`, `MediaService`, `ContentTypeService`, `EntityService`, `AuditService`, `ExternalMemberService`, etc.). Service implementations only live in `Umbraco.Infrastructure/Services/Implement/` when they genuinely need Infrastructure concerns — Examine indexes (`ContentSearchService`, `MediaSearchService`, `IndexedEntitySearchService`), log files (`LogViewerRepository`), packaging internals (`PackagingService`), webhook firing (`WebhookFiringService`), distributed-job coordination (`DistributedJobService`). When adding a new service, default to Core and only move to Infrastructure if a concrete dependency forces it.\n\n2. **Interface-First Design**\n   - All services defined as interfaces in Core\n   - Enables testing, polymorphism, extensibility\n\n3. **Notification Pattern** (not C# events)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"2. Notification System (Event Handling)\"\n\n4. **Composer Pattern** (DI registration)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"3. Composer Pattern (DI Registration)\"\n\n5. **Scoping Pattern** (Unit of Work)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"5. Scoping Pattern (Unit of Work)\"\n\n6. **Attempt Pattern** (operation results)\n   - `Attempt<TResult, TStatus>` instead of exceptions\n   - Strongly-typed operation status enums\n\n### Key Design Patterns Used\n\n- **Repository Pattern** - Data access abstraction\n- **Unit of Work** - Scoping for transactions\n- **Builder Pattern** - `ProblemDetailsBuilder` for API errors\n- **Strategy Pattern** - OpenAPI handlers (schema ID, operation ID)\n- **Options Pattern** - All configuration via `IOptions<T>`\n- **Factory Pattern** - Content type factories\n- **Mediator Pattern** - Notification aggregator\n\n---\n\n## 6. Avoiding Breaking Changes\n\nNo binary breaking changes are allowed within a major version. Three patterns are used:\n\n### 6.1 Obsolete Constructor + StaticServiceProvider\n\nWhen a public class needs new dependencies, obsolete the existing constructor and add a new one. The old constructor delegates to the new one, resolving missing deps via `StaticServiceProvider`.\n\n```csharp\n[Obsolete(\"Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.\")]\npublic MyService(IDependencyA depA)\n    : this(\n        depA,\n        StaticServiceProvider.Instance.GetRequiredService<IDependencyB>())\n{\n}\n\npublic MyService(IDependencyA depA, IDependencyB depB)\n{\n    _depA = depA;\n    _depB = depB;\n}\n```\n\n**Examples**:\n- `ContentCollectionPresentationFactory` - added `FlagProviderCollection`\n- `CacheInstructionService` - added `ILastSyncedManager`, `IRepositoryCacheVersionService`\n- `DocumentPresentationFactory` - added `FlagProviderCollection`\n\n**Rules**:\n- Old constructor marked `[Obsolete(\"... Scheduled for removal in Umbraco {current-major+2}.\")]`\n- Old constructor calls new constructor via `: this(...)`\n- Uses `StaticServiceProvider.Instance.GetRequiredService<T>()` for new params only\n- DI registration must use the NEW constructor (old is for external consumers only)\n\n### 6.2 Obsolete Method + New Overload\n\nWhen a public method signature needs to change, add the new method/overload and obsolete the old. The obsolete method should call the new one with suitable defaults.\n\n```csharp\n[Obsolete(\"Use the overload taking all parameters. Scheduled for removal in Umbraco 19.\")]\npublic void DoThing(string name)\n    => DoThing(name, extraParam: null);\n\npublic void DoThing(string name, string? extraParam)\n{\n    // Real implementation here\n}\n```\n\n**Rules**:\n- Old method marked `[Obsolete]` with removal schedule\n- DRY: old method calls new method, providing defaults for new parameters\n- All internal callers must be updated to use the new method\n- No callers should remain on the obsolete method within the codebase\n\n### 6.3 Default Interface Implementation\n\nWhen adding methods to a public interface, provide a default implementation so existing external implementations don't break.\n\n```csharp\npublic interface IMyService\n{\n    // Existing method\n    void ExistingMethod();\n\n    // New method with default implementation\n    void NewMethod(string param)\n        => ExistingMethod(); // delegate to existing if possible\n}\n```\n\n**Strategies for the default** (in order of preference):\n1. **Use existing interface methods** to satisfy the contract (even if not optimal)\n2. **Return a sensible default** like empty collection, null, etc.\n3. **Throw `NotImplementedException`** if no reasonable default exists\n\n**Example**: `IContentService.SaveBlueprint` - new overload with `IContent? createdFromContent` has a default impl that calls the old method (ignoring the new param).\n\n**Example**: `IDocumentPresentationFactory.CreateCulturePublishScheduleModels` - full default implementation with logic, uses `StaticServiceProvider` for dependency resolution within the interface.\n\n**Rules**:\n- Add `// TODO (V{next-major}): Remove the default implementation when {obsolete method} is removed.` comment\n- Default impl should be functionally correct even if not optimal\n- If using `StaticServiceProvider` in a default impl, note this is temporary\n\n### 6.4 General Rules\n\n- **Removal policy**: Obsoleted members must remain for at least one full major version before removal. If obsoleted in version N, the earliest removal is version N+2. For example, something obsoleted in v17 is scheduled for removal in v19 (giving the whole of v18 as a deprecation period).\n- All `[Obsolete]` attributes must include **\"Scheduled for removal in Umbraco {current+2}\"**\n- Read `version.json` to determine the current major version\n- Suppress `CS0618` warnings where obsolete members must call each other:\n  ```csharp\n  #pragma warning disable CS0618 // Type or member is obsolete\n      => OldMethod(param);\n  #pragma warning restore CS0618 // Type or member is obsolete\n  ```\n- Update ALL internal callers to use the new API - no internal code should use obsolete members\n\n---\n\n## 7. Project-Specific Notes\n\n### Centralized Package Management\n\n**NuGet package versions** are centralized in `Directory.Packages.props`. There are two `Directory.Packages.props` files in the source tree, with multi-level merging enabled so the test file inherits from the root:\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production source code packages — referenced by all `src/**` projects |\n| `tests/Directory.Packages.props` | Test-only packages (NUnit, Moq, Bogus, BenchmarkDotNet, etc.) — adds entries on top of the inherited root file |\n\nWhen updating dependencies, decide which file the package belongs in:\n- A package used only by test projects → `tests/Directory.Packages.props`\n- A package used by any production project (or by both production and tests) → root `Directory.Packages.props`\n\n```xml\n<!-- Individual projects reference WITHOUT version -->\n<PackageReference Include=\"Microsoft.AspNetCore.OpenApi\" />\n\n<!-- Versions defined in Directory.Packages.props -->\n<PackageVersion Include=\"Microsoft.AspNetCore.OpenApi\" Version=\"10.0.0\" />\n```\n\n**Opt-out**: `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` sets `<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>` and specifies versions inline (for `Microsoft.EntityFrameworkCore.Design`, `Microsoft.Build.Tasks.Core`, `Microsoft.ICU.ICU4C.Runtime`, etc.). Update those versions directly in that csproj when bumping. Two further `Directory.Packages.props` files exist under `templates/` for the project/extension templates and have their own version sets — keep `Microsoft.AspNetCore.OpenApi` aligned between the root file and `templates/UmbracoExtension/`.\n\n### Build Configuration\n\n- `Directory.Build.props` - Shared properties (target framework, company, copyright)\n- `.editorconfig` - Code style rules\n- `.globalconfig` - Roslyn analyzer rules\n\n### Persistence Layer - NPoco and EF Core\n\nThe repository contains BOTH (actively supported):\n- **Current**: NPoco-based persistence (`Umbraco.Cms.Persistence.Sqlite`, `Umbraco.Cms.Persistence.SqlServer`) - widely used and fully supported\n- **Future**: EF Core-based persistence (`Umbraco.Cms.Persistence.EFCore.*`) - migration in progress\n\n**Note**: The codebase is actively migrating to EF Core, but NPoco remains the primary persistence layer and is not deprecated. Both are fully supported.\n\n### Authentication: OpenIddict\n\nAll APIs use **OpenIddict** (OAuth 2.0/OpenID Connect):\n- Reference tokens (not JWT) for better security\n- **Secure cookie-based token storage** (v17+) - tokens stored in HTTP-only cookies with `__Host-` prefix\n- Tokens are redacted from client-side responses and passed via secure cookies only (`[redacted]` placeholder)\n- ASP.NET Core Data Protection for token encryption\n- Configured in `Umbraco.Cms.Api.Common`\n- API requests must include credentials (`credentials: include` for fetch)\n\n**Load Balancing Requirement**: All servers must share the same Data Protection key ring.\n\n**Frontend auth pitfalls** — see `src/Umbraco.Web.UI.Client/docs/edge-cases.md` (Auth & Cross-tab section) and `docs/security.md`. Key points:\n- Never call `validateToken()` per API request — it revokes the previous reference token (ID2019 errors)\n- `window.opener` is set for ANY `window.open()` target, not only OAuth popups — scope guards to the pathname too\n- BroadcastChannel does not deliver messages to the sender's own tab\n\n### Content Caching Strategy\n\n**HybridCache** (`Umbraco.PublishedCache.HybridCache`):\n- In-memory cache + distributed cache support\n- Published content only (not draft)\n- Invalidated via notifications and cache refreshers\n\n### API Versioning\n\nAPIs use `Asp.Versioning.Mvc`:\n- Management API: `/umbraco/management/api/v{version}/*`\n- Delivery API: `/umbraco/delivery/api/v{version}/*`\n- OpenAPI docs: `/umbraco/openapi/management.json`, `/umbraco/openapi/delivery.json`\n- Swagger UI: `/umbraco/openapi/`\n\n### Updating `OpenApi.json` (Management API)\n\nWhen a PR changes Management API controllers or models, the `OpenApi.json` file in the Management API project must be updated, along with the generated backoffice client that is derived from it.\n\nWith the Umbraco instance running locally (in a non-Production environment — Swagger isn't mapped in Production):\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\n```\n\nThe first fetches the document from `/umbraco/swagger/management/swagger.json` byte-for-byte into `src/Umbraco.Cms.Api.Management/OpenApi.json`; the second regenerates the hey-api client from that committed file. Both results must be committed together.\n\nThey are two separate commands on purpose. The client generator only ever reads the committed schema — never a running site — so the schema and the client it produced always land in the same commit.\n\nThe `/umb-update-openapi` skill wraps this: it starts and stops a backend for you if one isn't already running, and explains the diff. Reach for it when you want the whole round trip handled.\n\n**Important**: The fetch is byte-for-byte on purpose, so the endpoint stays the source of truth. Don't reformat the result — commit only the substantive changes, not IDE-applied formatting (whitespace, reordering, etc.). Extraneous formatting diffs make PRs harder to review and merge-ups more error-prone.\n\n### Backoffice npm Package\n\nThe backoffice is published to npm as `@umbraco-cms/backoffice`. Runtime dependencies are provided via importmap; npm peerDependencies provide types only. For full details on dependency hoisting, version range logic, and plugin development, see `/src/Umbraco.Web.UI.Client/CLAUDE.md` → \"npm Package Publishing\".\n\n### SQL Server 2100-parameter limit\n\nAny `WHERE IN (@0, @1, ...)` built from a runtime-sized collection risks hitting SQL Server's 2100-parameter ceiling and throwing `SqlException` 8003 in production.\n\nBatch with `IEnumerable<T>.InGroupsOf(Constants.Sql.MaxParameterCount)` or `Database.FetchByGroups(...)` whenever the collection size is driven by user data — not just when it currently fits. Watch for products of two scaling dimensions (documents × languages, properties × versions) and config-tunable batch sizes whose defaults are safe but ceilings aren't.\n\nFull guidance, safe patterns and decision rule: see `/src/Umbraco.Infrastructure/CLAUDE.md` → \"Avoiding the SQL Server 2100-parameter limit\".\n\n### Known Limitations\n\n1. **Circular Dependencies**: Avoided via `Lazy<T>` or event notifications\n2. **Multi-Server**: Requires shared Data Protection key ring and synchronized clocks (NTP)\n3. **Database Support**: SQL Server, SQLite\n\n---\n\n## 8. CI/CD — Claude AI Assistant\n\nTwo GitHub Actions workflows powered by `anthropics/claude-code-action@v1`. Advisory only — does not block merging.\n\n### Workflows\n\n| File | Trigger | Purpose |\n|------|---------|---------|\n| `claude-review.yml` | `pull_request: [opened, ready_for_review]` | Auto-review every non-draft PR using the `umb-review` skill |\n| `claude.yml` | `@claude` comments, issue assign/label | Interactive assistant for PRs and issues |\n\n### Auto-Review (`claude-review.yml`)\n\nRuns the full `.claude/skills/umb-review/SKILL.md` procedure on every newly opened or un-drafted PR. Produces inline comments per finding and one summary comment with a verdict. Skips draft PRs. No turn limit.\n\n### Interactive (`claude.yml`)\n\nResponds to `@claude` mentions on PRs and issues. The trigger phrase is stripped before Claude sees the message, so:\n\n- `@claude review` → light review using `gh pr diff` (not the umb-review skill)\n- `@claude fix ...` → implements a fix on a new branch\n- `@claude help` → answers questions about the codebase\n- `@claude label` → applies labels\n- `@claude` (empty) → defaults to `review` on PRs, `help` on issues\n\nAlso triggers on issue assignment to `claude` or adding the `claude` label. Gated: only runs when `@claude` appears in the comment/issue body. Max 25 turns.\n\n**Allowed Bash tools**: `gh`, `git`, `npm`, `dotnet` (interactive only; auto-review allows `gh` and `git`).\n\n### Labels\n\nBoth workflows apply labels based on content:\n\n**On PRs** (based on changed files):\n\n| Label | Condition |\n|-------|-----------|\n| `area/frontend` | Files under `src/Umbraco.Web.UI.Client/` |\n| `area/backend` | `.cs` files outside the frontend client |\n| `area/test` | Only test files changed |\n| `category/api` | Management or Delivery API files |\n| `category/breaking` | Breaking changes detected |\n| `category/localization` | Localization/language files |\n| `category/test-automation` | Only test files changed |\n| `category/refactor` | Pure refactoring, no new features |\n| `category/performance` | Performance-related changes |\n| `category/ux` | User-facing changes |\n| `category/ui` | UI layer changes |\n\n**On Issues** (based on content): same `area/*` and `category/*` labels, plus `affected/v14` through `affected/v17` and `affected/backoffice`.\n\nLabels are only added, never removed. Claude applies only labels it is confident about.\n\n### Key Implementation Notes\n\n- **Checkout required** — the action internally runs `git fetch origin main` for trusted file restoration. Without `actions/checkout`, it fails with `fatal: not a git repository`.\n- **`id-token: write` permission** — required for OIDC token exchange with the Claude GitHub App.\n- **Trigger phrase stripping** — the action strips `@claude` from comments before passing to Claude. Prompts must reference commands without the prefix (e.g., `review` not `@claude review`).\n- **PR number injection** — the interactive workflow injects the PR/issue number into the prompt via `${{ github.event.issue.number }}` since Claude can't discover it from `gh pr view` when checked out on `main`.\n\n---\n\n## 9. Code Comment Policy\n\n**Default to no comment.** Applies to all code in this repository — C#, TypeScript, Razor, build scripts. Well-named identifiers and small functions carry the meaning; a comment is a fallback for what the code genuinely cannot say — a non-obvious *why*, a subtle invariant the types don't enforce, or a surprising edge case the code handles deliberately. Add XML doc / JSDoc on public members, but keep it concise.\n\n**Write the rule, at the altitude of the code it sits in.** A comment states what must hold going forward, in the vocabulary of the layer it lives in — see §2. Where a comment exists because something once went wrong, the rule is what survives; the incident and the reported scenario belong in the commit message and PR body:\n\n```typescript\n// A resolver may emit several groups of inner values.\n// Pair each draft group with its persisted group by the\n// identifier the resolver supplies, not by call order.\n```\n\n**Keep issue references where they stay actionable.** A tracked issue link (`(#21996)`, `https://...`) is welcome wherever it explains a non-obvious *why* — a guard whose reason isn't clear from the code, a workaround for a defect this code cannot fix (so it can be deleted when the fix lands), or a regression test recording why it exists. Elsewhere the comment stands on its own in general terms.\n\n**Let commit messages and PR descriptions carry provenance.** Which task, PR, or issue produced a change (`Fix for X`, `Used by Y`, `Added for the Z flow`, `See PR #1234`) is recorded in git history, where it stays accurate. Source describes the code as it is now.\n\n### TODOs\n\nAllowed, and can name the specific issue, implementation, or use case it concerns — the one exception to §2, since the comment is deleted once the TODO is done. Keep them short and trackable: `// TODO (V19): remove once obsolete overload is gone` or `// TODO: pagination [NL]`. A TODO should have an author or a version trigger.\n\n---\n\n## 10. Testing Practices\n\nA test for shared code asserts the general rule from §2, not the scenario that reported it, and is named for the rule.\n\n### Tests for a bug fix must fail before the fix\n\nVerify any test you add for a bug fix actually catches the bug: either write the failing test first (TDD), or temporarily revert the production change and confirm the test fails before re-applying. A test that passes both ways proves nothing. Watch for coincidental passes — default seed/sort orders can make a buggy path produce the right answer for the test's specific inputs; construct inputs so the broken and fixed behaviours give visibly different results.\n\nFor integration tests that exercise caching or cache refreshers, see `tests/Umbraco.Tests.Integration/CLAUDE.md` — the harness disables caching by default, which can produce false greens.\n\n---\n\n## 11. Verification Discipline\n\n- **Fresh build before trusting a green.** Never treat `--no-build` or cached/incremental output as proof a change compiles or passes — a stale run can mask a compile error. Rebuild before reporting build or test state. (Integration tests have a related false-green trap — see `tests/Umbraco.Tests.Integration/CLAUDE.md`.)\n- **Grep the branch you think you're on.** A search only supports a claim against the branch actually checked out, so confirm HEAD is where you expect before drawing a conclusion from a grep. Easy to get wrong whenever the tree moves under you — reviewing a PR head, switching worktrees, or mid merge-up/rebase.\n\n---\n\n## Quick Reference\n\n### Essential Commands\n\n```bash\n# Build solution\ndotnet build\n\n# Run all tests\ndotnet test\n\n# Run specific test category\ndotnet test --filter \"Category=Integration\"\n\n# Format code\ndotnet format\n\n# Pack all projects\ndotnet pack -c Release\n```\n\n### Integration Test Database Configuration\n\nIntegration tests are configured in `tests/Umbraco.Tests.Integration/appsettings.Tests.json`.\n\nThe `Tests:Database:DatabaseType` setting controls which database is used:\n- `\"SQLite\"` (default) - No external dependencies\n- `\"LocalDb\"` - Uses SQL Server LocalDB, required for SQL Server-specific tests (e.g., page-level locking, `sys.dm_tran_locks`)\n\nSQL Server-specific tests use `BaseTestDatabase.IsSqlite()` to skip when running on SQLite.\n\n### Key Projects\n\n| Project | Type | Description |\n|---------|------|-------------|\n| **Umbraco.Core** | Library | Interface contracts and domain models |\n| **Umbraco.Infrastructure** | Library | Service implementations and data access |\n| **Umbraco.Web.UI** | Application | Main web application (Razor/MVC) |\n| **Umbraco.Cms.Api.Management** | Library | Management API (backoffice) |\n| **Umbraco.Cms.Api.Delivery** | Library | Delivery API (headless CMS) |\n| **Umbraco.Cms.Api.Common** | Library | Shared API infrastructure |\n| **Umbraco.PublishedCache.HybridCache** | Library | Published content caching |\n| **Umbraco.Examine.Lucene** | Library | Full-text search indexing |\n\n### Important Files\n\n- **Solution**: `umbraco.sln`\n- **Build Config**: `Directory.Build.props`, `Directory.Packages.props`\n- **Code Style**: `.editorconfig`, `.globalconfig`\n- **Documentation**: `/CLAUDE.md`, `/src/Umbraco.Core/CLAUDE.md`, `/src/Umbraco.Cms.Api.Common/CLAUDE.md`\n\n### Project-Specific Documentation\n\nFor detailed information about individual projects, see their CLAUDE.md files:\n- **Core Architecture**: `/src/Umbraco.Core/CLAUDE.md` - Service contracts, notification patterns\n- **API Infrastructure**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md` - OpenAPI, authentication, serialization\n- **Backoffice Frontend**: `/src/Umbraco.Web.UI.Client/CLAUDE.md` - Lit web components, extension system, auth client\n\n**Important**: When working on backoffice client code (anything under `src/Umbraco.Web.UI.Client/`), read `/src/Umbraco.Web.UI.Client/CLAUDE.md` first. It contains action-specific checklists (deprecation, testing, security, etc.) that are not duplicated here.\n\n### Getting Help\n\n- **Official Docs**: https://docs.umbraco.com/\n- **Contributing Guide**: `.github/CONTRIBUTING.md`\n- **Issues**: https://github.com/umbraco/Umbraco-CMS/issues\n- **Community**: https://forum.umbraco.com/\n- **Releases**: https://releases.umbraco.com/\n\n---\n\n**This repository follows a layered architecture with strict dependency rules. The Core defines contracts, Infrastructure implements them, and Web/APIs consume them. Each layer can be understood independently, but dependencies always flow inward toward Core.**\n",".github/copilot-instructions.md":"The full development guide for this repository lives in [CLAUDE.md](../CLAUDE.md). Please read that file for complete instructions on architecture, build steps, testing, branching conventions, and coding patterns.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# Umbraco CMS - Multi-Project Repository\n\nEnterprise-grade CMS built on .NET 10.0. This repository contains 21 production projects organized in a layered architecture with clear separation of concerns.\n\n**Repository**: https://github.com/umbraco/Umbraco-CMS\n**License**: MIT\n**Main Branch**: `main`\n\n---\n\n## 1. Overview\n\n### What This Repository Contains\n\n**21 Production Projects** organized in 3 main categories:\n\n1. **Core Architecture** (Domain & Infrastructure)\n   - `Umbraco.Core` - Interface contracts, domain models, notifications\n   - `Umbraco.Infrastructure` - Service implementations, data access, caching\n\n2. **Web & APIs** (Presentation Layer)\n   - `Umbraco.Web.UI` - Main ASP.NET Core web application\n   - `Umbraco.Web.Common` - Shared web functionality, controllers, middleware\n   - `Umbraco.Cms.Api.Management` - Backoffice Management API (REST)\n   - `Umbraco.Cms.Api.Delivery` - Content Delivery API (headless)\n   - `Umbraco.Cms.Api.Common` - Shared API infrastructure\n\n3. **Specialized Features** (Pluggable Modules)\n   - Persistence: EF Core (modern), NPoco (legacy) for SQL Server & SQLite\n   - Caching: `PublishedCache.HybridCache` (in-memory + distributed)\n   - Search: `Examine.Lucene` (full-text search)\n   - Imaging: `Imaging.ImageSharp` v1 & v2 (image processing)\n   - Other: Static assets, targets, development tools\n\n**6 Test Projects**:\n- `Umbraco.Tests.Common` - Shared test utilities\n- `Umbraco.Tests.UnitTests` - Unit tests\n- `Umbraco.Tests.Integration` - Integration tests\n- `Umbraco.Tests.Benchmarks` - Performance benchmarks\n- `Umbraco.Tests.AcceptanceTest` - E2E tests\n- `Umbraco.Tests.AcceptanceTest.UmbracoProject` - Test instance\n\n### Key Technologies\n\n- **.NET 10.0** - Target framework for all projects\n- **ASP.NET Core** - Web framework\n- **Entity Framework Core** - Modern ORM\n- **OpenIddict** - OAuth 2.0/OpenID Connect authentication\n- **Microsoft.AspNetCore.OpenApi** - OpenAPI document generation\n- **Swashbuckle.AspNetCore.SwaggerUI** - Swagger UI for API documentation\n- **Lucene.NET** - Full-text search via Examine\n- **ImageSharp** - Image processing\n\n---\n\n## 2. General-Purpose by Default\n\nThis repository is a product platform, not an application. Every layer is consumed by code that does not live here — implementors, package developers, and other parts of the CMS. A change is finished when it serves the use case that prompted it *and* the use cases nobody has described yet.\n\n**Design to the contract.** When you change shared code, work out the rule the layer must uphold for *any* implementation of it, and make that rule hold there. Name a concrete implementation freely in the code that owns it — a specific service, package, or project; a shared or generic layer implements only the contract. A specific editor alias, content type alias, or class name appearing in generic code is the signal that a fix has been fitted to one caller — express it instead as a capability the contract exposes.\n\n**Describe the contract.** Comments and public docs use the vocabulary of the layer they sit in. In public XML doc / JSDoc a concrete illustration is welcome where it reads as one possible implementation (\"for example, an editor that emits several groups may…\"), never as the definition of the behaviour.\n\n---\n\n## 3. Repository Structure\n\n```\nUmbraco-CMS/\n├── src/                                    # 21 production projects\n│   ├── Umbraco.Core/                      # Domain contracts (interfaces only)\n│   │   └── CLAUDE.md                      # ⭐ Core architecture guide\n│   ├── Umbraco.Infrastructure/            # Service implementations\n│   ├── Umbraco.Web.Common/                # Web utilities\n│   ├── Umbraco.Web.UI/                    # Main web application\n│   ├── Umbraco.Cms.Api.Management/        # Management API\n│   ├── Umbraco.Cms.Api.Delivery/          # Delivery API (headless)\n│   ├── Umbraco.Cms.Api.Common/            # Shared API infrastructure\n│   │   └── CLAUDE.md                      # ⭐ API patterns guide\n│   ├── Umbraco.PublishedCache.HybridCache/ # Content caching\n│   ├── Umbraco.Examine.Lucene/            # Search indexing\n│   ├── Umbraco.Cms.Persistence.EFCore/    # EF Core data access\n│   ├── Umbraco.Cms.Persistence.EFCore.Sqlite/\n│   ├── Umbraco.Cms.Persistence.EFCore.SqlServer/\n│   ├── Umbraco.Cms.Persistence.Sqlite/    # Legacy SQLite\n│   ├── Umbraco.Cms.Persistence.SqlServer/ # Legacy SQL Server\n│   ├── Umbraco.Cms.Imaging.ImageSharp/    # Image processing v1\n│   ├── Umbraco.Cms.Imaging.ImageSharp2/   # Image processing v2\n│   ├── Umbraco.Cms.StaticAssets/          # Embedded assets\n│   ├── Umbraco.Cms.DevelopmentMode.Backoffice/\n│   ├── Umbraco.Cms.Targets/               # NuGet targets\n│   └── Umbraco.Cms/                       # Meta-package\n│\n├── tests/                                  # 6 test projects\n│   ├── Umbraco.Tests.Common/\n│   ├── Umbraco.Tests.UnitTests/\n│   ├── Umbraco.Tests.Integration/\n│   ├── Umbraco.Tests.Benchmarks/\n│   ├── Umbraco.Tests.AcceptanceTest/\n│   └── Umbraco.Tests.AcceptanceTest.UmbracoProject/\n│\n├── templates/                              # Project templates\n│   └── Umbraco.Templates/\n│\n├── tools/                                  # Build tools\n│   └── Umbraco.JsonSchema/\n│\n├── umbraco.sln                            # Main solution file\n├── Directory.Build.props                  # Shared build configuration\n├── Directory.Packages.props               # Centralized package versions\n├── .editorconfig                          # Code style\n└── .globalconfig                          # Roslyn analyzers\n```\n\n### Architecture Layers\n\n**Dependency Flow** (unidirectional, always flows inward):\n\n```\nWeb.UI → Web.Common → Infrastructure → Core\n                ↓\n          Api.Management → Api.Common → Infrastructure → Core\n                ↓\n          Api.Delivery → Api.Common → Infrastructure → Core\n```\n\n**Key Principle**: Core has NO dependencies (pure contracts). Infrastructure implements Core. Web/APIs depend on Infrastructure.\n\n### Project Dependencies\n\n**Core Layer**:\n- `Umbraco.Core` → No dependencies (only Microsoft.Extensions.*)\n\n**Infrastructure Layer**:\n- `Umbraco.Infrastructure` → `Umbraco.Core`\n- `Umbraco.PublishedCache.*` → `Umbraco.Infrastructure`\n- `Umbraco.Examine.Lucene` → `Umbraco.Infrastructure`\n- `Umbraco.Cms.Persistence.*` → `Umbraco.Infrastructure`\n\n**Web Layer**:\n- `Umbraco.Web.Common` → `Umbraco.Infrastructure` + caching + search\n- `Umbraco.Web.UI` → `Umbraco.Web.Common` + all features\n\n**API Layer**:\n- `Umbraco.Cms.Api.Common` → `Umbraco.Web.Common`\n- `Umbraco.Cms.Api.Management` → `Umbraco.Cms.Api.Common`\n- `Umbraco.Cms.Api.Delivery` → `Umbraco.Cms.Api.Common`\n\n---\n\n## 4. Teamwork & Collaboration\n\n### Branching Strategy\n\n- **Main branch**: `main` (protected)\n- **Branch naming convention**: `v<version>/<type>/<description>`\n\n**Format**: `v{major-version}/{type}/{kebab-case-description}`\n\n**Version**: Read from `version.json` in the repository root. Use the major version number (e.g., `v17` for version 17.x.x).\n\n**Types**:\n| Type | Use Case |\n|------|----------|\n| `feature` | New feature being introduced to the product |\n| `bugfix` | Fix to an existing issue with the product |\n| `qa` | Adding or updating unit, integration, or end-to-end tests |\n| `improvement` | Update to something that already exists but isn't broken (UI finessing, refactoring) |\n| `task` | Update that doesn't directly impact product behavior (dependency updates, build pipeline) |\n\n**Description**: A short, kebab-case description (a few words). This should be prefixed with the GitHub issue number if the update is related to resolving a tracked issue.\n\n**Examples**:\n```\nv17/bugfix/12345-correct-display-of-pending-migrations\nv17/feature/add-webhook-support\nv17/improvement/optimize-content-cache\nv17/qa/add-media-service-tests\nv17/task/update-ef-core-dependency\n```\n\nSee `.github/CONTRIBUTING.md` for full guidelines.\n\n### Pull Request Process\n\n- **PR Template**: `.github/pull_request_template.md`\n- **Required CI Checks**:\n  - All tests pass\n  - Code formatting (dotnet format)\n  - No build warnings\n- **Merge Strategy**: Squash and merge (via GitHub UI)\n- **Reviews**: Required from code owners\n\n#### PR Naming Convention\n\nUse the format: `Area: Description (closes #IssueID)`\n\n**Examples**:\n| Area | Description | Issue |\n|------|-------------|-------|\n| Relations: | Move persistence of relations from repository into notification handlers | (closes #00000) |\n| Management API: | Correct the population of the parent for sibling items when retrieved under a folder | |\n| Docs: | Updated contributing guidelines to welcome contributions on bugfixes | |\n\n**Area**: The feature or aspect affected (e.g., UFM, TipTap, Docs, Segmentation, Migrations). Helps readers quickly understand what is being changed.\n\n**Description Best Practices**:\n- Include the area of change (Relations, Management API, etc.)\n- Describe the change and its impact\n- Be specific, not vague (describe \"a golden retriever\" not just \"a dog\")\n\n**Issue Linking**: Add `(closes #IssueID)` to the title for readability, AND include a closing keyword on its own line in the PR body (e.g., `Fixes #IssueID`) so GitHub actually auto-links and auto-closes the issue on merge. GitHub only parses closing keywords (`closes`, `fixes`, `resolves`) from the PR body or commit messages — the title suffix is cosmetic and does **not** trigger auto-close on its own.\n\n### Commit Messages\n\nFollow Conventional Commits format:\n```\n<type>(<scope>): <description>\n\nTypes: feat, fix, docs, style, refactor, test, chore\nScope: project name (core, web, api, etc.)\n\nExamples:\nfeat(core): add IContentService.GetByIds method\nfix(api): resolve null reference in schema handler\ndocs(web): update routing documentation\n```\n\n### Code Owners\n\nProject ownership is distributed across teams. Check individual project directories for ownership.\n\n---\n\n## 5. Architecture Patterns\n\n### Core Architectural Decisions\n\n1. **Layered Architecture with Dependency Inversion**\n   - Core defines contracts (interfaces)\n   - Infrastructure implements contracts that need Infrastructure-owned machinery\n   - Web/APIs consume implementations via DI\n\n   **Where service implementations live**: Services whose dependencies are satisfiable from Core interfaces alone (repositories, scope, config, other Core services) live in `Umbraco.Core/Services/` — this covers the majority of domain services (`MemberService`, `ContentService`, `MediaService`, `ContentTypeService`, `EntityService`, `AuditService`, `ExternalMemberService`, etc.). Service implementations only live in `Umbraco.Infrastructure/Services/Implement/` when they genuinely need Infrastructure concerns — Examine indexes (`ContentSearchService`, `MediaSearchService`, `IndexedEntitySearchService`), log files (`LogViewerRepository`), packaging internals (`PackagingService`), webhook firing (`WebhookFiringService`), distributed-job coordination (`DistributedJobService`). When adding a new service, default to Core and only move to Infrastructure if a concrete dependency forces it.\n\n2. **Interface-First Design**\n   - All services defined as interfaces in Core\n   - Enables testing, polymorphism, extensibility\n\n3. **Notification Pattern** (not C# events)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"2. Notification System (Event Handling)\"\n\n4. **Composer Pattern** (DI registration)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"3. Composer Pattern (DI Registration)\"\n\n5. **Scoping Pattern** (Unit of Work)\n   - See `/src/Umbraco.Core/CLAUDE.md` → \"5. Scoping Pattern (Unit of Work)\"\n\n6. **Attempt Pattern** (operation results)\n   - `Attempt<TResult, TStatus>` instead of exceptions\n   - Strongly-typed operation status enums\n\n### Key Design Patterns Used\n\n- **Repository Pattern** - Data access abstraction\n- **Unit of Work** - Scoping for transactions\n- **Builder Pattern** - `ProblemDetailsBuilder` for API errors\n- **Strategy Pattern** - OpenAPI handlers (schema ID, operation ID)\n- **Options Pattern** - All configuration via `IOptions<T>`\n- **Factory Pattern** - Content type factories\n- **Mediator Pattern** - Notification aggregator\n\n---\n\n## 6. Avoiding Breaking Changes\n\nNo binary breaking changes are allowed within a major version. Three patterns are used:\n\n### 6.1 Obsolete Constructor + StaticServiceProvider\n\nWhen a public class needs new dependencies, obsolete the existing constructor and add a new one. The old constructor delegates to the new one, resolving missing deps via `StaticServiceProvider`.\n\n```csharp\n[Obsolete(\"Please use the constructor with all parameters. Scheduled for removal in Umbraco 19.\")]\npublic MyService(IDependencyA depA)\n    : this(\n        depA,\n        StaticServiceProvider.Instance.GetRequiredService<IDependencyB>())\n{\n}\n\npublic MyService(IDependencyA depA, IDependencyB depB)\n{\n    _depA = depA;\n    _depB = depB;\n}\n```\n\n**Examples**:\n- `ContentCollectionPresentationFactory` - added `FlagProviderCollection`\n- `CacheInstructionService` - added `ILastSyncedManager`, `IRepositoryCacheVersionService`\n- `DocumentPresentationFactory` - added `FlagProviderCollection`\n\n**Rules**:\n- Old constructor marked `[Obsolete(\"... Scheduled for removal in Umbraco {current-major+2}.\")]`\n- Old constructor calls new constructor via `: this(...)`\n- Uses `StaticServiceProvider.Instance.GetRequiredService<T>()` for new params only\n- DI registration must use the NEW constructor (old is for external consumers only)\n\n### 6.2 Obsolete Method + New Overload\n\nWhen a public method signature needs to change, add the new method/overload and obsolete the old. The obsolete method should call the new one with suitable defaults.\n\n```csharp\n[Obsolete(\"Use the overload taking all parameters. Scheduled for removal in Umbraco 19.\")]\npublic void DoThing(string name)\n    => DoThing(name, extraParam: null);\n\npublic void DoThing(string name, string? extraParam)\n{\n    // Real implementation here\n}\n```\n\n**Rules**:\n- Old method marked `[Obsolete]` with removal schedule\n- DRY: old method calls new method, providing defaults for new parameters\n- All internal callers must be updated to use the new method\n- No callers should remain on the obsolete method within the codebase\n\n### 6.3 Default Interface Implementation\n\nWhen adding methods to a public interface, provide a default implementation so existing external implementations don't break.\n\n```csharp\npublic interface IMyService\n{\n    // Existing method\n    void ExistingMethod();\n\n    // New method with default implementation\n    void NewMethod(string param)\n        => ExistingMethod(); // delegate to existing if possible\n}\n```\n\n**Strategies for the default** (in order of preference):\n1. **Use existing interface methods** to satisfy the contract (even if not optimal)\n2. **Return a sensible default** like empty collection, null, etc.\n3. **Throw `NotImplementedException`** if no reasonable default exists\n\n**Example**: `IContentService.SaveBlueprint` - new overload with `IContent? createdFromContent` has a default impl that calls the old method (ignoring the new param).\n\n**Example**: `IDocumentPresentationFactory.CreateCulturePublishScheduleModels` - full default implementation with logic, uses `StaticServiceProvider` for dependency resolution within the interface.\n\n**Rules**:\n- Add `// TODO (V{next-major}): Remove the default implementation when {obsolete method} is removed.` comment\n- Default impl should be functionally correct even if not optimal\n- If using `StaticServiceProvider` in a default impl, note this is temporary\n\n### 6.4 General Rules\n\n- **Removal policy**: Obsoleted members must remain for at least one full major version before removal. If obsoleted in version N, the earliest removal is version N+2. For example, something obsoleted in v17 is scheduled for removal in v19 (giving the whole of v18 as a deprecation period).\n- All `[Obsolete]` attributes must include **\"Scheduled for removal in Umbraco {current+2}\"**\n- Read `version.json` to determine the current major version\n- Suppress `CS0618` warnings where obsolete members must call each other:\n  ```csharp\n  #pragma warning disable CS0618 // Type or member is obsolete\n      => OldMethod(param);\n  #pragma warning restore CS0618 // Type or member is obsolete\n  ```\n- Update ALL internal callers to use the new API - no internal code should use obsolete members\n\n---\n\n## 7. Project-Specific Notes\n\n### Centralized Package Management\n\n**NuGet package versions** are centralized in `Directory.Packages.props`. There are two `Directory.Packages.props` files in the source tree, with multi-level merging enabled so the test file inherits from the root:\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production source code packages — referenced by all `src/**` projects |\n| `tests/Directory.Packages.props` | Test-only packages (NUnit, Moq, Bogus, BenchmarkDotNet, etc.) — adds entries on top of the inherited root file |\n\nWhen updating dependencies, decide which file the package belongs in:\n- A package used only by test projects → `tests/Directory.Packages.props`\n- A package used by any production project (or by both production and tests) → root `Directory.Packages.props`\n\n```xml\n<!-- Individual projects reference WITHOUT version -->\n<PackageReference Include=\"Microsoft.AspNetCore.OpenApi\" />\n\n<!-- Versions defined in Directory.Packages.props -->\n<PackageVersion Include=\"Microsoft.AspNetCore.OpenApi\" Version=\"10.0.0\" />\n```\n\n**Opt-out**: `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` sets `<ManagePackageVersionsCentrally>false</ManagePackageVersionsCentrally>` and specifies versions inline (for `Microsoft.EntityFrameworkCore.Design`, `Microsoft.Build.Tasks.Core`, `Microsoft.ICU.ICU4C.Runtime`, etc.). Update those versions directly in that csproj when bumping. Two further `Directory.Packages.props` files exist under `templates/` for the project/extension templates and have their own version sets — keep `Microsoft.AspNetCore.OpenApi` aligned between the root file and `templates/UmbracoExtension/`.\n\n### Build Configuration\n\n- `Directory.Build.props` - Shared properties (target framework, company, copyright)\n- `.editorconfig` - Code style rules\n- `.globalconfig` - Roslyn analyzer rules\n\n### Persistence Layer - NPoco and EF Core\n\nThe repository contains BOTH (actively supported):\n- **Current**: NPoco-based persistence (`Umbraco.Cms.Persistence.Sqlite`, `Umbraco.Cms.Persistence.SqlServer`) - widely used and fully supported\n- **Future**: EF Core-based persistence (`Umbraco.Cms.Persistence.EFCore.*`) - migration in progress\n\n**Note**: The codebase is actively migrating to EF Core, but NPoco remains the primary persistence layer and is not deprecated. Both are fully supported.\n\n### Authentication: OpenIddict\n\nAll APIs use **OpenIddict** (OAuth 2.0/OpenID Connect):\n- Reference tokens (not JWT) for better security\n- **Secure cookie-based token storage** (v17+) - tokens stored in HTTP-only cookies with `__Host-` prefix\n- Tokens are redacted from client-side responses and passed via secure cookies only (`[redacted]` placeholder)\n- ASP.NET Core Data Protection for token encryption\n- Configured in `Umbraco.Cms.Api.Common`\n- API requests must include credentials (`credentials: include` for fetch)\n\n**Load Balancing Requirement**: All servers must share the same Data Protection key ring.\n\n**Frontend auth pitfalls** — see `src/Umbraco.Web.UI.Client/docs/edge-cases.md` (Auth & Cross-tab section) and `docs/security.md`. Key points:\n- Never call `validateToken()` per API request — it revokes the previous reference token (ID2019 errors)\n- `window.opener` is set for ANY `window.open()` target, not only OAuth popups — scope guards to the pathname too\n- BroadcastChannel does not deliver messages to the sender's own tab\n\n### Content Caching Strategy\n\n**HybridCache** (`Umbraco.PublishedCache.HybridCache`):\n- In-memory cache + distributed cache support\n- Published content only (not draft)\n- Invalidated via notifications and cache refreshers\n\n### API Versioning\n\nAPIs use `Asp.Versioning.Mvc`:\n- Management API: `/umbraco/management/api/v{version}/*`\n- Delivery API: `/umbraco/delivery/api/v{version}/*`\n- OpenAPI docs: `/umbraco/openapi/management.json`, `/umbraco/openapi/delivery.json`\n- Swagger UI: `/umbraco/openapi/`\n\n### Updating `OpenApi.json` (Management API)\n\nWhen a PR changes Management API controllers or models, the `OpenApi.json` file in the Management API project must be updated, along with the generated backoffice client that is derived from it.\n\nWith the Umbraco instance running locally (in a non-Production environment — Swagger isn't mapped in Production):\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\n```\n\nThe first fetches the document from `/umbraco/swagger/management/swagger.json` byte-for-byte into `src/Umbraco.Cms.Api.Management/OpenApi.json`; the second regenerates the hey-api client from that committed file. Both results must be committed together.\n\nThey are two separate commands on purpose. The client generator only ever reads the committed schema — never a running site — so the schema and the client it produced always land in the same commit.\n\nThe `/umb-update-openapi` skill wraps this: it starts and stops a backend for you if one isn't already running, and explains the diff. Reach for it when you want the whole round trip handled.\n\n**Important**: The fetch is byte-for-byte on purpose, so the endpoint stays the source of truth. Don't reformat the result — commit only the substantive changes, not IDE-applied formatting (whitespace, reordering, etc.). Extraneous formatting diffs make PRs harder to review and merge-ups more error-prone.\n\n### Backoffice npm Package\n\nThe backoffice is published to npm as `@umbraco-cms/backoffice`. Runtime dependencies are provided via importmap; npm peerDependencies provide types only. For full details on dependency hoisting, version range logic, and plugin development, see `/src/Umbraco.Web.UI.Client/CLAUDE.md` → \"npm Package Publishing\".\n\n### SQL Server 2100-parameter limit\n\nAny `WHERE IN (@0, @1, ...)` built from a runtime-sized collection risks hitting SQL Server's 2100-parameter ceiling and throwing `SqlException` 8003 in production.\n\nBatch with `IEnumerable<T>.InGroupsOf(Constants.Sql.MaxParameterCount)` or `Database.FetchByGroups(...)` whenever the collection size is driven by user data — not just when it currently fits. Watch for products of two scaling dimensions (documents × languages, properties × versions) and config-tunable batch sizes whose defaults are safe but ceilings aren't.\n\nFull guidance, safe patterns and decision rule: see `/src/Umbraco.Infrastructure/CLAUDE.md` → \"Avoiding the SQL Server 2100-parameter limit\".\n\n### Known Limitations\n\n1. **Circular Dependencies**: Avoided via `Lazy<T>` or event notifications\n2. **Multi-Server**: Requires shared Data Protection key ring and synchronized clocks (NTP)\n3. **Database Support**: SQL Server, SQLite\n\n---\n\n## 8. CI/CD — Claude AI Assistant\n\nTwo GitHub Actions workflows powered by `anthropics/claude-code-action@v1`. Advisory only — does not block merging.\n\n### Workflows\n\n| File | Trigger | Purpose |\n|------|---------|---------|\n| `claude-review.yml` | `pull_request: [opened, ready_for_review]` | Auto-review every non-draft PR using the `umb-review` skill |\n| `claude.yml` | `@claude` comments, issue assign/label | Interactive assistant for PRs and issues |\n\n### Auto-Review (`claude-review.yml`)\n\nRuns the full `.claude/skills/umb-review/SKILL.md` procedure on every newly opened or un-drafted PR. Produces inline comments per finding and one summary comment with a verdict. Skips draft PRs. No turn limit.\n\n### Interactive (`claude.yml`)\n\nResponds to `@claude` mentions on PRs and issues. The trigger phrase is stripped before Claude sees the message, so:\n\n- `@claude review` → light review using `gh pr diff` (not the umb-review skill)\n- `@claude fix ...` → implements a fix on a new branch\n- `@claude help` → answers questions about the codebase\n- `@claude label` → applies labels\n- `@claude` (empty) → defaults to `review` on PRs, `help` on issues\n\nAlso triggers on issue assignment to `claude` or adding the `claude` label. Gated: only runs when `@claude` appears in the comment/issue body. Max 25 turns.\n\n**Allowed Bash tools**: `gh`, `git`, `npm`, `dotnet` (interactive only; auto-review allows `gh` and `git`).\n\n### Labels\n\nBoth workflows apply labels based on content:\n\n**On PRs** (based on changed files):\n\n| Label | Condition |\n|-------|-----------|\n| `area/frontend` | Files under `src/Umbraco.Web.UI.Client/` |\n| `area/backend` | `.cs` files outside the frontend client |\n| `area/test` | Only test files changed |\n| `category/api` | Management or Delivery API files |\n| `category/breaking` | Breaking changes detected |\n| `category/localization` | Localization/language files |\n| `category/test-automation` | Only test files changed |\n| `category/refactor` | Pure refactoring, no new features |\n| `category/performance` | Performance-related changes |\n| `category/ux` | User-facing changes |\n| `category/ui` | UI layer changes |\n\n**On Issues** (based on content): same `area/*` and `category/*` labels, plus `affected/v14` through `affected/v17` and `affected/backoffice`.\n\nLabels are only added, never removed. Claude applies only labels it is confident about.\n\n### Key Implementation Notes\n\n- **Checkout required** — the action internally runs `git fetch origin main` for trusted file restoration. Without `actions/checkout`, it fails with `fatal: not a git repository`.\n- **`id-token: write` permission** — required for OIDC token exchange with the Claude GitHub App.\n- **Trigger phrase stripping** — the action strips `@claude` from comments before passing to Claude. Prompts must reference commands without the prefix (e.g., `review` not `@claude review`).\n- **PR number injection** — the interactive workflow injects the PR/issue number into the prompt via `${{ github.event.issue.number }}` since Claude can't discover it from `gh pr view` when checked out on `main`.\n\n---\n\n## 9. Code Comment Policy\n\n**Default to no comment.** Applies to all code in this repository — C#, TypeScript, Razor, build scripts. Well-named identifiers and small functions carry the meaning; a comment is a fallback for what the code genuinely cannot say — a non-obvious *why*, a subtle invariant the types don't enforce, or a surprising edge case the code handles deliberately. Add XML doc / JSDoc on public members, but keep it concise.\n\n**Write the rule, at the altitude of the code it sits in.** A comment states what must hold going forward, in the vocabulary of the layer it lives in — see §2. Where a comment exists because something once went wrong, the rule is what survives; the incident and the reported scenario belong in the commit message and PR body:\n\n```typescript\n// A resolver may emit several groups of inner values.\n// Pair each draft group with its persisted group by the\n// identifier the resolver supplies, not by call order.\n```\n\n**Keep issue references where they stay actionable.** A tracked issue link (`(#21996)`, `https://...`) is welcome wherever it explains a non-obvious *why* — a guard whose reason isn't clear from the code, a workaround for a defect this code cannot fix (so it can be deleted when the fix lands), or a regression test recording why it exists. Elsewhere the comment stands on its own in general terms.\n\n**Let commit messages and PR descriptions carry provenance.** Which task, PR, or issue produced a change (`Fix for X`, `Used by Y`, `Added for the Z flow`, `See PR #1234`) is recorded in git history, where it stays accurate. Source describes the code as it is now.\n\n### TODOs\n\nAllowed, and can name the specific issue, implementation, or use case it concerns — the one exception to §2, since the comment is deleted once the TODO is done. Keep them short and trackable: `// TODO (V19): remove once obsolete overload is gone` or `// TODO: pagination [NL]`. A TODO should have an author or a version trigger.\n\n---\n\n## 10. Testing Practices\n\nA test for shared code asserts the general rule from §2, not the scenario that reported it, and is named for the rule.\n\n### Tests for a bug fix must fail before the fix\n\nVerify any test you add for a bug fix actually catches the bug: either write the failing test first (TDD), or temporarily revert the production change and confirm the test fails before re-applying. A test that passes both ways proves nothing. Watch for coincidental passes — default seed/sort orders can make a buggy path produce the right answer for the test's specific inputs; construct inputs so the broken and fixed behaviours give visibly different results.\n\nFor integration tests that exercise caching or cache refreshers, see `tests/Umbraco.Tests.Integration/CLAUDE.md` — the harness disables caching by default, which can produce false greens.\n\n---\n\n## 11. Verification Discipline\n\n- **Fresh build before trusting a green.** Never treat `--no-build` or cached/incremental output as proof a change compiles or passes — a stale run can mask a compile error. Rebuild before reporting build or test state. (Integration tests have a related false-green trap — see `tests/Umbraco.Tests.Integration/CLAUDE.md`.)\n- **Grep the branch you think you're on.** A search only supports a claim against the branch actually checked out, so confirm HEAD is where you expect before drawing a conclusion from a grep. Easy to get wrong whenever the tree moves under you — reviewing a PR head, switching worktrees, or mid merge-up/rebase.\n\n---\n\n## Quick Reference\n\n### Essential Commands\n\n```bash\n# Build solution\ndotnet build\n\n# Run all tests\ndotnet test\n\n# Run specific test category\ndotnet test --filter \"Category=Integration\"\n\n# Format code\ndotnet format\n\n# Pack all projects\ndotnet pack -c Release\n```\n\n### Integration Test Database Configuration\n\nIntegration tests are configured in `tests/Umbraco.Tests.Integration/appsettings.Tests.json`.\n\nThe `Tests:Database:DatabaseType` setting controls which database is used:\n- `\"SQLite\"` (default) - No external dependencies\n- `\"LocalDb\"` - Uses SQL Server LocalDB, required for SQL Server-specific tests (e.g., page-level locking, `sys.dm_tran_locks`)\n\nSQL Server-specific tests use `BaseTestDatabase.IsSqlite()` to skip when running on SQLite.\n\n### Key Projects\n\n| Project | Type | Description |\n|---------|------|-------------|\n| **Umbraco.Core** | Library | Interface contracts and domain models |\n| **Umbraco.Infrastructure** | Library | Service implementations and data access |\n| **Umbraco.Web.UI** | Application | Main web application (Razor/MVC) |\n| **Umbraco.Cms.Api.Management** | Library | Management API (backoffice) |\n| **Umbraco.Cms.Api.Delivery** | Library | Delivery API (headless CMS) |\n| **Umbraco.Cms.Api.Common** | Library | Shared API infrastructure |\n| **Umbraco.PublishedCache.HybridCache** | Library | Published content caching |\n| **Umbraco.Examine.Lucene** | Library | Full-text search indexing |\n\n### Important Files\n\n- **Solution**: `umbraco.sln`\n- **Build Config**: `Directory.Build.props`, `Directory.Packages.props`\n- **Code Style**: `.editorconfig`, `.globalconfig`\n- **Documentation**: `/CLAUDE.md`, `/src/Umbraco.Core/CLAUDE.md`, `/src/Umbraco.Cms.Api.Common/CLAUDE.md`\n\n### Project-Specific Documentation\n\nFor detailed information about individual projects, see their CLAUDE.md files:\n- **Core Architecture**: `/src/Umbraco.Core/CLAUDE.md` - Service contracts, notification patterns\n- **API Infrastructure**: `/src/Umbraco.Cms.Api.Common/CLAUDE.md` - OpenAPI, authentication, serialization\n- **Backoffice Frontend**: `/src/Umbraco.Web.UI.Client/CLAUDE.md` - Lit web components, extension system, auth client\n\n**Important**: When working on backoffice client code (anything under `src/Umbraco.Web.UI.Client/`), read `/src/Umbraco.Web.UI.Client/CLAUDE.md` first. It contains action-specific checklists (deprecation, testing, security, etc.) that are not duplicated here.\n\n### Getting Help\n\n- **Official Docs**: https://docs.umbraco.com/\n- **Contributing Guide**: `.github/CONTRIBUTING.md`\n- **Issues**: https://github.com/umbraco/Umbraco-CMS/issues\n- **Community**: https://forum.umbraco.com/\n- **Releases**: https://releases.umbraco.com/\n\n---\n\n**This repository follows a layered architecture with strict dependency rules. The Core defines contracts, Infrastructure implements them, and Web/APIs consume them. Each layer can be understood independently, but dependencies always flow inward toward Core.**\n","category":"root","tokens":8118},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","title":"copilot-instructions.md","content":"The full development guide for this repository lives in [CLAUDE.md](../CLAUDE.md). Please read that file for complete instructions on architecture, build steps, testing, branching conventions, and coding patterns.\n","category":".github","tokens":54}]}