{"owner":"umbraco","repo":"Umbraco-CMS","hasSkills":true,"totalSkillsCount":69,"totalTokensCount":37658,"categories":["anthropic-skill","claude-rule","copilot-instructions","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/umb-update-server-dependencies-for-minor/SKILL.md",".claude/skills/umb-bump-version/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md",".claude/skills/umb-release-notes/SKILL.md",".claude/skills/umb-review/SKILL.md",".claude/skills/umb-update-openapi/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md","src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md","CLAUDE.md",".github/copilot-instructions.md",".claude/skills/umb-review/evals/evals.json",".claude/skills/umb-review/references/breaking-changes.md",".claude/skills/umb-review/references/coding-preferences.md",".claude/skills/umb-review/references/complexity-assessment.md",".claude/skills/umb-review/references/gh-cli-setup.md",".claude/skills/umb-review/references/impact-analysis.md","src/Umbraco.Web.UI.Client/src/packages/block/package.json","src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json","src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json","src/Umbraco.Web.UI.Client/src/packages/content/package.json","src/Umbraco.Web.UI.Client/src/packages/core/package.json","src/Umbraco.Web.UI.Client/src/packages/data-type/package.json","src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json","src/Umbraco.Web.UI.Client/src/packages/documents/package.json","src/Umbraco.Web.UI.Client/src/packages/elements/package.json","src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json","src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json","src/Umbraco.Web.UI.Client/src/packages/health-check/package.json","src/Umbraco.Web.UI.Client/src/packages/help/package.json","src/Umbraco.Web.UI.Client/src/packages/language/package.json","src/Umbraco.Web.UI.Client/src/packages/library/package.json","src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json","src/Umbraco.Web.UI.Client/src/packages/management-api/package.json","src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json","src/Umbraco.Web.UI.Client/src/packages/media/package.json","src/Umbraco.Web.UI.Client/src/packages/members/package.json","src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json","src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json","src/Umbraco.Web.UI.Client/src/packages/packages/package.json","src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json","src/Umbraco.Web.UI.Client/src/packages/preview/package.json","src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json","src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json","src/Umbraco.Web.UI.Client/src/packages/relations/package.json","src/Umbraco.Web.UI.Client/src/packages/rte/package.json","src/Umbraco.Web.UI.Client/src/packages/segment/package.json","src/Umbraco.Web.UI.Client/src/packages/settings/package.json","src/Umbraco.Web.UI.Client/src/packages/static-file/package.json","src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json","src/Umbraco.Web.UI.Client/src/packages/tags/package.json","src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json","src/Umbraco.Web.UI.Client/src/packages/templating/package.json","src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md","src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json","src/Umbraco.Web.UI.Client/src/packages/translation/package.json","src/Umbraco.Web.UI.Client/src/packages/ufm/package.json","src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json","src/Umbraco.Web.UI.Client/src/packages/user/package.json","src/Umbraco.Web.UI.Client/src/packages/webhook/package.json","src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json","src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md"],"skills":{".claude/skills/umb-update-server-dependencies-for-minor/SKILL.md":"---\nname: umb-update-server-dependencies-for-minor\ndescription: Update all server-side (NuGet) dependencies to their latest minor/patch versions for the current branch's version line, then open a PR. Runs dotnet-outdated, bumps minors and patches only (never majors; never moves a stable package onto a pre-release), syncs the template projects, builds and runs unit tests, commits, and creates a PR. Use when asked to update/bump backend/server/NuGet/.NET dependencies for a release (e.g. \"update server dependencies for 17.6\", \"bump NuGet packages to latest minors\").\nargument-hint: [base-branch] (optional; defaults to the current branch)\n---\n\n# Update Server-Side Dependencies (minor & patch) - Umbraco CMS\n\nBumps every NuGet dependency that is behind to its latest available **minor or patch** version for a single version line (the current branch), then builds, tests, commits and opens a PR.\n\n**Scope: one version line at a time.** This skill operates on the branch that is currently checked out (or the branch given as `$ARGUMENTS`). To update multiple release lines (e.g. `v17/dev` and `main`), run it once per line.\n\n**Guardrails (non-negotiable):**\n- **Never** update a major version. Only minors and patches where a newer one exists.\n- **Never** move a stable package onto a pre-release. Packages already on a pre-release *may* be updated to a newer pre-release — this is what `--pre-release Auto` does.\n- **Never** update packages explicitly held back for compatibility (e.g. marked \"HOLD\"/\"do not bump\" in `Directory.Packages.props`). `TODO` comments there may also mark transitive security pins, which are in-scope to bump if needed (see step 3).\n\n## Prerequisites\n\n- `dotnet-outdated-tool` installed globally: `dotnet tool install --global dotnet-outdated-tool` (verify with `dotnet outdated --version`).\n- `gh` CLI authenticated (`gh auth status`).\n\n## Where versions live\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production packages + `GlobalPackageReference` build tooling + transitive security pins |\n| `tests/Directory.Packages.props` | Test-only packages (inherits the root file) |\n| `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` | Inline versions (opts out of central management) |\n| `templates/UmbracoExtension/Umbraco.Extension.csproj` and its `Directory.Packages.props` | **Not in `umbraco.sln`** — dotnet-outdated will NOT touch these; sync them by hand (step 4) |\n\n## Instructions\n\n### 1. Establish the branch\n\nRead `version.json` and take the major version (e.g. `18` from `18.1.0-rc`) — call it `{major}`.\n\nIf the current branch is a long-lived line (`main`, `v{major}/dev`, a release branch) rather than a task branch, create and switch to a new branch:\n\n```bash\ngit switch -c v{major}/task/update-backend-dependencies\n```\n\n(If `$ARGUMENTS` names a base branch, branch from that instead.) If you are already on a suitable task branch, stay on it. Confirm the working tree is clean before starting.\n\n### 2. Discover what is outdated\n\nRun dotnet-outdated restricted to within the current major. `--version-lock Major` keeps updates inside the current major version; `--pre-release Auto` (the default) offers a pre-release upgrade **only when the package is already on a pre-release** — so a stable package never jumps to a pre-release, while an existing pre-release can move to a newer pre-release:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto umbraco.sln\n```\n\nReview the report. Everything listed is a legitimate minor/patch (or same-track pre-release) candidate. Note anything you intend to skip, and verify afterwards (via `git diff`) that no intentionally pinned/held versions were changed; revert any that were.\n\n### 3. Apply the updates\n\nApply the same filter with `-u`:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto -u umbraco.sln\n```\n\n`dotnet-outdated` restores per-package and may print `Failed to restore project after upgrading!` for the test projects — this is a known quirk with the multi-level `tests/Directory.Packages.props` merge; the version writes still land. Verify with `git diff` afterwards.\n\nThis updates the two `Directory.Packages.props` files, the inline `Umbraco.Web.UI.csproj` versions, and any in-solution project. It also handles `GlobalPackageReference` entries (e.g. `Nerdbank.GitVersioning`).\n\nYou can also update transitive **security pins** if a new minor or patch is available.\n\n### 4. Sync the template projects by hand\n\n`templates/UmbracoExtension/` is not part of `umbraco.sln`, so its versions are not updated automatically. For any package that (a) was bumped in the root `Directory.Packages.props` **and** (b) appears in the template, update it to match:\n\n```bash\ngrep -rnE \"Version=\\\"[0-9]\" templates/UmbracoExtension/\n```\n\nTypically this is `Microsoft.AspNetCore.OpenApi` and/or `Swashbuckle.AspNetCore*` in both `templates/UmbracoExtension/Umbraco.Extension.csproj` and `templates/UmbracoExtension/Directory.Packages.props`. Keep them aligned with the root file.\n\n### 5. Build and run unit tests\n\nBuild the full solution in Release, skipping the frontend/npm steps (these are gated for CI/non-VS builds):\n\n```bash\ndotnet build umbraco.sln -c Release -p:UmbracoBuild=true\n```\n\nThen run the unit test suite:\n\n```bash\ndotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj -c Release -p:UmbracoBuild=true --no-build\n```\n\nBoth build and tests must be green before continuing.\n\nConfirm the bumps did not leave (or introduce) a known-vulnerable transitive version:\n\n```bash\ndotnet list umbraco.sln package --vulnerable --include-transitive\n```\n\nPre-existing, unrelated advisories are out of scope for a routine bump — but if a bump you made is the cause, resolve it (raise the pin, or add an inline reference for `Umbraco.Web.UI`).\n\n### 6. Review and commit\n\nConfirm `git status` shows only the intended package files (the two `Directory.Packages.props`, `Umbraco.Web.UI.csproj`, and any template files) — no stray build output, logs, or the schema placeholder. Stage those files explicitly (do not `git add -A`) and commit:\n\n```\nUpdate NuGet packages to latest minor and patch versions\n\nBump all NuGet dependencies that were behind to their latest available\nminor or patch release, as reported by dotnet-outdated. No major-version\nupdates are included.\n```\n\n### 7. Push and open the PR\n\n```bash\ngit push -u origin <branch>\n```\n\nGenerate the PR body from the actual `git diff` of the package files (do not invent versions). Use tables grouping the changes, and keep the **Testing** section to a single line — CI is the source of truth, do not paste build/test counts.\n\nFill in the template below and **write it to a temp file** (e.g. `/tmp/pr-body.md`), then create the PR against the line's dev branch (`v{major}/dev` for a v-line, `main` for the current major):\n\n```bash\ngh pr create --base <dev-branch> --head <branch> \\\n  --title \"Dependencies: Update NuGet packages to latest minor and patch versions\" \\\n  --body-file /tmp/pr-body.md\n```\n\n**PR body template:**\n\n```markdown\n## Description\n\nRoutine dependency maintenance for the **{version}** release. Updates all NuGet packages that were behind to their latest available **minor or patch** version, as reported by `dotnet-outdated`. No major-version updates are included.\n\n### Production packages (`Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Test packages (`tests/Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Inline / template versions\n\n- `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj`: …\n- `templates/UmbracoExtension`: …\n\n### Deliberately left unchanged\n\n- Any package where only a **major** update is available (out of scope for this PR).\n- Intentionally-held packages carrying a `HOLD`/`do not bump` comment (e.g. `Umbraco.Code`).\n\n## Testing\n\nSolution should build and CI checks pass.\n```\n\n### 8. Report\n\nOutput the PR URL, the branch name, and a short summary of what was bumped (and anything deliberately skipped or any pin that had to be raised).\n\n## Notes\n\n- If `dotnet-outdated` reports nothing outdated, say so and stop — do not create an empty PR.\n- No `OpenApi.json` regeneration is needed for pure dependency bumps (no Management API controllers/models change).\n",".claude/skills/umb-bump-version/SKILL.md":"---\nname: umb-bump-version\ndescription: Bump the Umbraco CMS version across all required files. Use when the user asks to bump, update, or set the version number — e.g., \"bump version to 17.3.4\", \"set version to 18.0.0-rc\", \"update version\". Accepts the target version as an argument.\nargument-hint: <version> (e.g., 17.3.4, 18.0.0-rc)\n---\n\n# Bump Version - Umbraco CMS\n\nUpdates the Umbraco CMS version string across all files that track it.\n\n**Do NOT use AskUserQuestion if a version argument is provided. Only ask if `$ARGUMENTS` is empty or cannot be parsed as a version.**\n\n## Arguments\n\n- `$ARGUMENTS` - Required: the target version string (e.g., `17.3.4`, `18.0.0-rc`)\n\n## Files to Update\n\nThe following 5 files must be updated with the new version:\n\n| # | File | Field |\n|---|------|-------|\n| 1 | `version.json` | `\"version\"` |\n| 2 | `src/Umbraco.Web.UI.Client/package.json` | `\"version\"` |\n| 3 | `src/Umbraco.Web.UI.Client/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n| 4 | `tests/Umbraco.Tests.AcceptanceTest/package.json` | `\"version\"` |\n| 5 | `tests/Umbraco.Tests.AcceptanceTest/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n\n**Note**: For major version bumps (e.g., 17.x to 18.x), `src/Umbraco.Web.UI.Login/package.json` has a caret-ranged dependency on `@umbraco-cms/backoffice` (e.g., `^17.2.0`) that will need manual updating. This skill does not handle that — major bumps involve many other changes beyond version strings.\n\n## Instructions\n\n### 1. Parse and Validate the Version\n\nExtract the version from `$ARGUMENTS`. It must be a valid semver-like string (e.g., `17.3.4`, `18.0.0-rc`, `17.4.0-preview.1`). If no version is provided or it cannot be parsed, ask the user for the target version.\n\n### 2. Read the Current Version\n\nRead `version.json` and extract the current `\"version\"` value. If the current version already equals the target version, report that the version is already set and stop — do not edit, stage, or commit anything.\n\nOtherwise, display both versions:\n\n```\nBumping version: {current} -> {target}\n```\n\n### 3. Update All Files\n\nUpdate each of the 5 files listed above, replacing the old version with the new version. For each file:\n\n- **`version.json`**: Replace the `\"version\"` value.\n- **`package.json` files**: Replace the `\"version\"` value (near the top of the file).\n- **`package-lock.json` files**: Replace BOTH the top-level `\"version\"` value AND the `\"version\"` inside the `\"packages\": { \"\": { ... } }` block. These are always in the first ~10 lines of the file.\n\nUse targeted edits — do NOT rewrite entire files. Be precise to avoid changing version strings in dependency entries.\n\n### 4. Verify\n\nAfter all edits, grep for the target version value across the 5 files to confirm all updates landed correctly:\n\n```bash\ngrep -n \"\\\"version\\\": \\\"{version}\\\"\" version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nExpect exactly 7 matches (one per `package.json` and `version.json`, two per `package-lock.json`).\n\n### 5. Stage and Commit\n\nStage only the 5 changed files:\n\n```bash\ngit add version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nThen commit with the message `Bump version to {version}.` — replacing `{version}` with the target version:\n\n```bash\ngit commit -m \"Bump version to {version}.\"\n```\n\n### 6. Report\n\nOutput a summary:\n\n```\nVersion bumped to {version} in:\n- version.json\n- src/Umbraco.Web.UI.Client/package.json\n- src/Umbraco.Web.UI.Client/package-lock.json\n- tests/Umbraco.Tests.AcceptanceTest/package.json\n- tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n\nChanges staged and committed.\n```\n","src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md":"---\nname: core-add-module\ndescription: Add a new module to the core package (src/packages/core/). Use when adding shared UI framework infrastructure that all packages can use — e.g., a new extension system feature, a new utility, a new shared component pattern, a new picker, or a new workspace primitive. Core modules are imported as @umbraco-cms/backoffice/{module-name}. Also use when the user says things like \"add a module to core\", \"create a new core feature\", or \"add shared infrastructure\".\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Core Module\n\nAdd a new module to the core package (`src/packages/core/`).\n\n## Naming conventions\n\n- **Module names** are **singular** (e.g., `picker`, `sorter`, `entity-flag`, `modal`, `section`)\n\n## What you need from the user\n\n1. **Module name** — What to call it in singular form (kebab-case, e.g., `picker`, `sorter`, `entity-flag`)\n2. **Purpose** — What shared infrastructure this provides\n\n## When to add to core vs. create a new package\n\n- **Core module**: Shared UI framework infrastructure used by multiple packages — extension primitives, pickers, validation, repository base classes, workspace utilities.\n- **New package**: Domain-specific CMS features (documents, media, webhook). Use the `create-package` skill instead.\n\nCore modules should not implement CMS-specific features. They provide the building blocks that CMS feature packages use.\n\n## Files to create\n\n```\nsrc/packages/core/{module-name}/\n├── index.ts                   # Public API exports\n├── manifests.ts               # Extension registrations (if any)\n├── constants.ts               # Module constants\n└── types.ts                   # Module types\n```\n\n## Step 1: Create the module directory and files\n\n### {module-name}/index.ts\n\nThe public API. Only export what other packages need:\n\n```typescript\nexport * from './constants.js';\nexport type * from './types.js';\n```\n\nUse `export type *` for type-only exports.\n\n### {module-name}/manifests.ts\n\nExtension registrations for this module:\n\n```typescript\nexport const manifests: Array<UmbExtensionManifest> = [\n\t// Extension registrations\n];\n```\n\nIf the module has no extensions (pure utility), export an empty array.\n\n### {module-name}/constants.ts\n\n```typescript\n// Aliases, context tokens, entity types\n```\n\n### {module-name}/types.ts\n\n```typescript\n// Type definitions, interfaces, meta types\n```\n\n## Step 2: Wire into core's manifests.ts\n\nAdd the module's manifests to `src/packages/core/manifests.ts`:\n\n```typescript\nimport { manifests as {moduleName}Manifests } from './{module-name}/manifests.js';\n\n// In the manifests array:\n...{moduleName}Manifests,\n```\n\nThe imports are in alphabetical order — insert in the right position.\n\n## Step 3: Add entry point to core's vite.config.ts\n\nAdd the module to the `entry` object in `src/packages/core/vite.config.ts`:\n\n```typescript\n'{module-name}/index': './{module-name}/index.ts',\n```\n\nThe entries are in alphabetical order — insert in the right position. This tells Vite to build the module as a separate entry point.\n\n## Step 4: Register the subpath export\n\nAdd to the `exports` field in the root `package.json` (`src/Umbraco.Web.UI.Client/package.json`):\n\n```json\n\"./{module-name}\": \"./dist-cms/packages/core/{module-name}/index.js\"\n```\n\nThis enables imports like `@umbraco-cms/backoffice/{module-name}`.\n\n## Step 5: Regenerate TypeScript config\n\n```bash\nnpm run generate:tsconfig\n```\n\nThis updates `tsconfig.json` paths so the new module resolves correctly in TypeScript.\n\n## How consumers import\n\nAfter registration, other packages import from the module like:\n\n```typescript\nimport { MyThing } from '@umbraco-cms/backoffice/{module-name}';\nimport type { MyType } from '@umbraco-cms/backoffice/{module-name}';\n```\n\n## Reference: existing core modules to study\n\n- **Simple utility**: `src/packages/core/culture/` — repository + components, minimal surface\n- **Extension primitive**: `src/packages/core/entity-action/` — kinds, base classes, shared element\n- **Infrastructure**: `src/packages/core/repository/` — base classes for the data access pattern\n\n## Checklist\n\n- [ ] Module directory created under `src/packages/core/`\n- [ ] `index.ts` exports only the public API\n- [ ] `manifests.ts` exports extension registrations (or empty array)\n- [ ] `constants.ts` and `types.ts` created\n- [ ] Module manifests imported in `src/packages/core/manifests.ts` (alphabetical order)\n- [ ] Entry point added to `src/packages/core/vite.config.ts` (alphabetical order)\n- [ ] Subpath export added to root `package.json`\n- [ ] `npm run generate:tsconfig` executed\n- [ ] Compiles: `npm run compile`\n",".claude/skills/umb-release-notes/SKILL.md":"---\nname: umb-release-notes\ndescription: Improve a set of auto-generated GitHub release notes for an Umbraco CMS release. Cross-checks the notes against every PR carrying the release label, adds any that are missing, re-files every PR under the most appropriate category, and strips purely-internal entries. Use whenever the user asks to tidy up, improve, complete, or recategorize release notes for a given version, or mentions a release-notes text file plus a version number.\nargument-hint: <version> <path-to-generated-notes-file>\n---\n\n# Umbraco CMS - Improve Release Notes\n\nTakes a file of auto-generated GitHub release notes and produces an improved version that:\n\n1. **Is complete** — every merged PR carrying the `release/<version>` label appears.\n2. **Is well-categorized** — every PR sits under the most appropriate heading.\n3. **Is free of noise** — purely-internal entries of no value to a reader are removed.\n\nThe result is written to a **new** file alongside the input, so the user can diff the two.\n\n**Run autonomously.** Do NOT use `AskUserQuestion` once the required arguments (version and input file path) are available — only ask if one of them is missing from `$ARGUMENTS` and cannot be inferred (see Arguments). Beyond that, make the categorization calls yourself using the rules below; if a handful are genuinely borderline, place them anyway and note the borderline ones in your closing summary so the user can override.\n\n## Arguments\n\n`$ARGUMENTS` contains two values:\n\n1. **Version** — e.g. `17.5.0`, `18.1.0`. The GitHub label to search is `release/<version>` (so version `17.5.0` → label `release/17.5.0`).\n2. **Input file path** — full path to the text file holding the auto-generated notes (e.g. `C:\\Temp\\release-17.5.0-rc.md`).\n\nIf either is missing, ask the user once for the missing value, then proceed.\n\n## Prerequisites\n\nRun `gh auth status`. If it fails, tell the user to authenticate `gh` (e.g. `gh auth login`) and stop — the skill needs the GitHub CLI to query PRs. The repo is always `umbraco/Umbraco-CMS`.\n\n## Procedure\n\n### 1. Read the input notes\n\nRead the input file. Note its structure — it is GitHub's generated format:\n\n- A leading HTML comment (`<!-- Release notes generated ... -->`).\n- A `## What's Changed` heading followed by `### <emoji> <Category>` sub-headings, each with `* <title> by @<author> in <url>` bullets.\n- A trailing `## New Contributors` section and a `**Full Changelog**: ...` line.\n\nExtract the set of PR numbers already present (parse the `/pull/<number>` from each bullet). Preserve each existing bullet's **exact text** (title, author, URL) when you re-emit it — only its category placement may change.\n\n### 2. Fetch every labelled PR\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 \\\n  --json number,title,author,labels,mergedAt \\\n  --jq '.[] | select(.mergedAt != null) | \"\\(.number)\\t\\(.author.login)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"' | sort -n\n```\n\nThis is the authoritative list of what the release *should* contain. Each row gives number, author, labels, title.\n\n**Guard against silent truncation.** `gh pr list` caps at `--limit` without warning, so a large release could drop the overflow and the skill would still look \"complete\". Count the returned rows and compare against the limit:\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 --json number --jq 'length'\n```\n\nIf this equals 1000, the limit was hit — raise `--limit` and re-fetch before continuing. Do **not** proceed on a truncated list.\n\n### 3. Reconcile\n\n- **Missing labelled PRs** (labelled but not in the input file): these must be **added**. Build a bullet as `* <title> by @<author> in https://github.com/umbraco/Umbraco-CMS/pull/<number>`.\n  - **Author handle.** `<author>` in the template is the raw `.author.login` value — the bullet supplies the leading `@`, so do not prepend another. `gh`'s `.author.login` already returns bot accounts with the `[bot]` suffix as part of the login — Dependabot comes back as `dependabot[bot]`, not `dependabot` or `app/dependabot` (the `app/` form only appears in git committer metadata and CODEOWNERS, never in `gh`'s JSON). So the login is already in the right shape; use it verbatim (e.g. `.author.login` of `dependabot[bot]` renders as `@dependabot[bot]`, matching what GitHub's generator wrote for the existing bullets). The only thing to guard against is accidentally stripping or altering the `[bot]` suffix.\n- **PRs in the file but not labelled**: keep them. The generated notes span a commit range (see the `Full Changelog` compare link), so they legitimately include backports / earlier-version PRs that lack the current label. For any of these you need to categorize, fetch its labels with:\n\n  ```bash\n  gh pr view <number> --repo umbraco/Umbraco-CMS --json number,title,labels \\\n    --jq '\"\\(.number)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"'\n  ```\n\nDo **not** invent or alter the `New Contributors` section — carry it over verbatim. You cannot reliably recompute first-time contributors, so leave it as the generator produced it (mention this in the summary).\n\n### 4. Categorize every PR\n\nUse exactly these headings, in this order. Omit any heading that ends up with no entries.\n\n| Heading | What goes here | Primary signal |\n|---|---|---|\n| `### 🙌 Notable Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/notable` |\n| `### 💥 Breaking Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/breaking` |\n| `### 📦 Dependencies` | Dependency bumps | label `dependencies`; or dependabot author |\n| `### 🚀 New Features` | New user- or developer-facing capability | label `type/feature` / `category/feature`; or title introduces/adds a genuinely new capability |\n| `### 🚤 Performance` | Performance improvements | label `category/performance`; or `Performance:` title prefix |\n| `### 🌈 Accessibility Improvements` | A11y improvements (labels, contrast, keyboard) | label `category/accessibility` / `accessibility`; or clear a11y intent (e.g. \"improve contrast\", \"missing labels\") |\n| `### 🐛 Bug Fixes` | Fixes to broken/incorrect behaviour | default for anything describing a fix |\n| `### 🧪 Testing` | Test additions/changes only | label `category/test-automation` / `area/test`; or `E2E`/`QA`/\"acceptance tests\"/\"unit test coverage\"/\"add tests\" titles |\n| `### 🛡️ Code Quality, Documentation and Refactoring` | Refactors, deprecations, API tidy-ups, XML/MD documentation, knowledge-base (`MD`) updates | label `category/refactor`; or titles about refactoring, deprecating, renaming, documenting, constants extraction, MD/CLAUDE.md content |\n| `### 🧑‍💻 Developer Experience` | Things that improve the experience of developers building on or contributing to Umbraco — dev tooling, build/watch ergonomics, test mocks/harnesses, backoffice dev utilities | `Developer Experience` title prefix; dev tooling; mock/harness changes |\n\n**Rules:**\n\n- **Notable and Breaking are off-limits for recategorization** — never move a PR that is *already in the input file* into or out of these sections; they are driven purely by their labels and the generator placed them correctly. This does **not** exempt them from completeness: a PR discovered as missing in step 3 that carries `category/notable` or `category/breaking` must still be **added** under the matching section.\n- Label signals beat title wording, except a `Performance:`/`Developer Experience:` title prefix is decisive for its section.\n- A PR with both `type/feature` and `category/refactor` whose title clearly describes a refactor (e.g. \"swap relative imports\", \"re-export type\") belongs under Code Quality, not New Features.\n- \"Add ... tests\"/\"unit test coverage\" → Testing, even if it also touches docs. If a PR adds XML documentation *and* tests, lead with where the title's emphasis lies (documentation → Code Quality; test coverage → Testing).\n- When a PR is genuinely 50/50, pick the more reader-useful heading and list it in your closing summary as borderline.\n\n### 5. Remove purely-internal noise\n\nDrop entries that have **no value to anyone reading release notes** — pure repository plumbing with no shipped impact. Examples:\n\n- Branch/merge maintenance (\"Fix main branch after merge issue\").\n- CI/pipeline fixes that don't change the product.\n- Reverts of changes that never shipped in a release.\n\n**Keep** anything that ships in the product or genuinely helps developers building on Umbraco — that includes documentation/MD updates, dev tooling, and test mocks (those go to Code Quality or Developer Experience, they are *not* noise). When unsure whether something is noise, keep it and flag it in the summary rather than silently dropping it. List every removal in your closing summary.\n\n### 6. Write the output\n\nWrite to a new file in the **same folder** as the input, named by appending ` - with updates` before the extension:\n\n- Input `C:\\Temp\\release-17.5.0-rc.md` → Output `C:\\Temp\\release-17.5.0-rc - with updates.md`\n\nPreserve the leading HTML comment, the `## What's Changed` heading, the `## New Contributors` section, and the `**Full Changelog**` line exactly. Only the `### <category>` groupings and their bullets change.\n\n### 7. Report\n\nGive a concise summary:\n\n- Count of PRs added (with their numbers), and which categories they landed in.\n- Notable recategorizations (PRs moved out of the catch-all Bug Fixes into Features/Performance/Testing/etc.).\n- Every entry removed, with the one-line reason.\n- Any borderline calls the user may want to override.\n- The output file path.\n\n## Verification\n\nBefore reporting done, confirm:\n\n- Every PR number from step 2 is present in the output (except any you deliberately removed in step 5 — and those must be in the removal list).\n- No PR appears under more than one heading.\n- Notable and Breaking sections are byte-for-byte unchanged from the input.\n- The header comment, New Contributors, and Full Changelog lines are intact.\n",".claude/skills/umb-review/SKILL.md":"---\nname: umb-review\ndescription: Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say \"review\" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.\nargument-hint: <target-branch>\n---\n\n# PR Review - Umbraco CMS\n\nAutomated, non-interactive PR code review. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality.\n\n**Do NOT use AskUserQuestion at any point. This skill runs fully autonomously.**\n\n## Arguments\n\n- `$ARGUMENTS` - Optional: target branch to diff against (auto-detected from PR, falls back to `origin/main`)\n\n## Instructions\n\n### 0. Verify GH CLI is Available\n\nRun `gh auth status`. If it fails, read `references/gh-cli-setup.md` and present the setup instructions to the user. Do not proceed with the review.\n\n### 1. Resolve Target Branch\n\nDetermine the target branch for comparison using this priority order:\n\n1. **Explicit argument**: If `$ARGUMENTS` is provided and non-empty, use it as the target branch\n2. **PR target branch**: If no argument, run `gh pr view --json baseRefName --jq '.baseRefName'` to detect the target branch of the current branch's open PR. If a PR exists, use `origin/{baseRefName}` as the target branch.\n3. **Fallback**: If no argument and no PR found (command fails or returns empty), default to `origin/main`\n\nStore the resolved target branch for use in subsequent steps. Log which resolution method was used (e.g., \"Target branch: `origin/v18/dev` (from PR #1234)\").\n\n### 2. Load Review Standards\n\n#### 2a. Load coding preferences\n\nRead the coding preferences and code review scoring criteria from:\n\n- `references/coding-preferences.md` (relative to this skill file)\n\nParse and internalize all rules, conventions, scoring categories, and severity definitions. These are your review criteria.\n\n#### 2b. Load area-specific documentation\n\nOnce the changed file list is known (after step 3a), determine which areas of the codebase are touched and load the relevant documentation. Execute this sub-step between 3a and 3b. This documentation takes precedence over sibling comparison for architectural and pattern validation.\n\n**Resolution order for each changed file:**\n\n1. **Find the nearest `CLAUDE.md`** — walk up from the changed file's directory toward the repository root. The first `CLAUDE.md` found is the area guide for that file. Read it.\n2. **Read referenced docs** — if the `CLAUDE.md` references documentation files (e.g., a `docs/` directory), use the descriptions in the `CLAUDE.md` to determine which docs are relevant to the type of code being changed, and read those. If unsure, read all referenced docs — the cost of reading is low, the cost of missing a convention is high.\n3. **Follow cross-references in loaded docs** — if a loaded doc references another doc as covering a complementary or related concern, and the changed files touch that concern, read the referenced doc too. Repeat until no new relevant cross-references remain.\n4. **Check for applicable skills** — review the available skills list. If a skill exists for the type of code being changed, read the skill file to understand the expected patterns, structure, and conventions it enforces. Do NOT invoke the skill — just use it as a reference for what the correct implementation should look like.\n\n**Store all loaded documentation** for use in step 4. These docs define the authoritative patterns and conventions that the review evaluates against.\n\n### 3. Gather Changed Files\n\n#### 3a. Collect file list, stats, and diff\n\nRun these git commands (where `{target}` is the resolved target branch):\n\n```bash\ngit diff {target}...HEAD --name-only --diff-filter=d   # changed files (excluding deleted)\ngit diff {target}...HEAD --stat                         # line counts per file\ngit log {target}...HEAD --oneline                       # commit history\ngit diff {target}...HEAD                                # full diff (primary review source)\n```\n\n**If no changes found**: Output \"No changes found between current branch and `{target}`. Nothing to review.\" and stop.\n\n#### 3b. Filter out noise files\n\nFrom the changed file list, classify each file as **noise** or **reviewable**.\n\n**Noise files** (skip entirely — do not read, do not review):\n\n| Pattern                                              | Reason                          |\n| ---------------------------------------------------- | ------------------------------- |\n| `*.gen.ts`, `*.gen.cs`                               | Auto-generated API client code  |\n| `*.generated.cs`, `*.Designer.cs` (in `Migrations/`) | Auto-generated models/snapshots |\n| `*/assets/lang/*.ts` (except `en.ts`)                | Non-English translation files   |\n| `*/mocks/data/*.ts`                                  | Test fixture data               |\n| `*/dist-cms/*`, `*/storybook-static/*`               | Build output                    |\n| `*/TEMP/InMemoryAuto/*`                              | Runtime-generated models        |\n| `package-lock.json`                                  | Dependency lock file            |\n| `appsettings-schema.*.json`                          | Generated JSON schema           |\n\nLog the skip list: \"Skipped {N} noise files: {comma-separated list of filenames}\"\n\n#### 3c. Read reviewable changed files\n\nRead the full file for every reviewable changed file.\n\n#### 3d. Track file counts\n\nKeep track of these numbers for the review output in step 7: total changed files, noise files skipped, and reviewable files read. Also record: distinct production layers touched, distinct project directories, and total lines changed — these feed step 3e.\n\n#### 3e. Assess PR complexity\n\nFollow the procedure in `references/complexity-assessment.md`. Store the triggered dimensions and suggestions for step 7.\n\n#### 3f. Classify PR scope\n\nClassify the PR to determine which review steps are relevant:\n\n| Classification  | Condition                                                                       | Effect                                                                          |\n| --------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |\n| **Gen-only**    | All reviewable files are `gen.ts`                                               | Skip steps 5 and 6; step 4 reviews impact on other code only                    |\n| **Docs-only**   | All reviewable files are `.md`                                                  | Skip steps 5 and 6; step 4 reviews intent and readability only                  |\n| **Test-only**   | All reviewable files are in `tests/`                                            | Skip steps 5 and 6; step 4 reviews intent, code quality, and test coverage only |\n| **Config-only** | All reviewable files are `.csproj`, `.props`, `.json` config, or CI/build files | Skip step 5; step 6 checks dependency version changes only                      |\n| **Standard**    | Anything else                                                                   | No skips — run all steps                                                        |\n\n### 4. Raw Code Review\n\nReview each changed file holistically. Think like a senior developer reading a colleague's PR. Note all findings without worrying about format or severity yet.\n\n#### 4a. Read and reason about each file\n\nFor each changed file, reason about: What does this code do? Is it correct? What's missing — validation, error handling, notifications, cleanup, edge cases? Could this break anything for consumers?\n\n**Unused usings (C# only):** flag a `using` directive only if this diff adds it, or removes the last code that used it — pre-existing unused usings are out of scope. Only flag when confident: a namespace can be used without appearing by name (extension methods, attributes, `<see cref>`, LINQ query syntax, `using static`, aliases, `#if` blocks). Report them as one Suggestion listing `file: namespace` pairs, per root `CLAUDE.md` §11; this is the one stylistic finding that is allowed.\n\n#### 4b. Validate against documentation and patterns\n\nUse a **docs-first** approach: classify the code by what it does, check it against documented conventions, and only fall back to sibling comparison when docs don't cover the pattern.\n\n**Step 1 — Determine the correct approach from documentation, then check whether the PR matches**\n\nA PR is a proposed solution, not the source of truth. This step has two parts that must happen in order — do not start part B until part A is complete.\n\n**Part A — Before validating/judging the implementation**, determine what the correct approach is for each new class or file based on what it does. Use the documentation loaded in step 2b to identify the expected base classes, patterns, and conventions. Write down the expected approach. Classify based on what the code does, not based on what neighboring files look like.\n\n**Part B — Now compare the PR's implementation** against the expected approach from Part A. If it deviates from the documented approach, flag it. If the documentation specifies reference examples, read those examples to verify the implementation matches.\n\n**Pattern match is the leading finding.** If the documentation defines a pattern that fits what the code does, the first and most important finding is whether the code follows that pattern.\n\n**Step 2 — Fall back to sibling comparison**\n\nIf the documentation does not cover the specific pattern, or for cross-cutting concerns not addressed in docs, fall back to sibling comparison:\n\n1. **New method on existing class/interface**: Grep for the most similar existing method on the same class using `-A 80` to capture the full method body (e.g., `UpdateCurrentUserAsync` → grep for `UpdateAsync` in the same file with `-A 80`). Compare line by line for missing cross-cutting concerns: notifications/events, validation, scoping, authorization, error handling, audit logging.\n2. **New TS class**: Grep for siblings by base class (`extends {BaseClass}`) or by interface (`implements {Interface}`) or by name suffix (e.g., `CurrentUserController` → grep for `UserController`). Compare for missing concerns.\n3. **New CS class**: Grep for siblings by base class (`class {ClassName} : {BaseClass}`) or by interface (`class {ClassName} : {Interface}`) or by name suffix (e.g., `ManagementApiComposer` → grep for `ApiComposer`). Compare for missing concerns.\n\n**Important:** Sibling comparison validates cross-cutting concerns, but it must not override documented conventions. If a sibling deviates from documented patterns, that sibling is wrong — do not copy its deviation.\n\nStore your raw findings — they feed into step 7.\n\n### 5. Impact Analysis\n\n**Skip this step if PR scope is docs-only, test-only, or config-only.**\n\nFollow the procedure in `references/impact-analysis.md`.\n\n### 6. Breaking Changes Check\n\n**Skip this step if PR scope is docs-only or test-only. If config-only, only check for dependency version changes that could break consumers.**\n\nFollow the procedure in `references/breaking-changes.md`.\n\n### 7. Consolidate and Output Review\n\nMerge findings from step 4 (raw review), step 5 (impact analysis), and step 6 (breaking changes). For each finding, assign severity (Critical/Important/Suggestion) and verify it relates to changed code — not pre-existing issues. Before outputting, drop any finding about whitespace, blank lines, formatting, or comment wording — but keep findings about comments or code pitched at the wrong altitude (see the review output guidelines below). Then present the review in this exact format:\n\n```markdown\n## PR Review\n\n**Target:** `{target_branch}` · **Based on commit:** `{head_sha}`\n[If any skipped files, append: · **Skipped:** {skipped} files out of {total} total]\n[If step 3f classification is not \"Standard\", append: · **Classified as:** {classification}]\n\n[1–2 sentences: what this PR accomplishes , keep it as short as possible, only highlight the primary essence.]\n\n- **Modified public API:** {changed existing interfaces/types/classes/methods}\n  [Omit bullet if none]\n- **Affected implementations (outside this PR):** {interfaces/types/classes/methods using modified public API}\n  [Omit bullet if none]\n- **Breaking changes:** {violations with specifics}\n  [Omit bullet if none]\n- **Other changes:** {changes not listed above that an Umbraco user, plugin developer, or API consumer would notice — e.g., behavior changes, default value changes, error message changes, new configuration options, removed functionality. Exclude internal renames, formatting, and private implementation details.}\n  [Omit bullet if none]\n\n[If step 3e triggered any dimensions, insert this block. Omit entirely if nothing triggered:]\n\n> [!NOTE]\n> **Complexity advisory** — This PR may benefit from splitting.\n>\n> - **{Dimension}:** {Explanation and concrete split suggestion from step 3e}\n>   [one bullet per triggered dimension]\n>\n> _This is an observation, not a blocker. The full review follows below._\n\n---\n\n### Critical\n\n[Must fix before merge — security vulnerabilities, data loss, broken functionality, breaking changes without proper patterns]\n\n- **`{file}:{line}`**: {problem} → {fix}\n\n[Omit section if none]\n\n### Important\n\n[Should fix — performance issues, missing tests, architectural violations, pattern misuse]\n\n- **`{file}:{line}`**: {observation} → {suggestion}\n\n[Omit section if none]\n\n### Suggestions\n\n[Nice to have — readability, minor refactoring, alternative approaches]\n\n- **`{file}:{line}`**: {detail}\n\n[Omit section if none]\n\n---\n\n[One of:]\n\n## Approved\n\nThis looks good to be merged as-is, but please do a manual sanity check and testing before merging.\n\n## Approved with Suggestions for improvement\n\nGood to go, but please carefully consider the importance of the suggestions.\n\n## Request Changes\n\nCritical and important issues must be addressed first.\n\n## Needs re-work\n\nThis is in such a bad state that the feedback of this review is not sufficient to guide improvements, the PR cannot be approved.\n```\n\n**Guidelines for the review output:**\n\n— When reporting information, be extremely concise and sacrifice grammar for sake of concision.\n\n- Only review code that was changed in the diff — pre-existing issues are out of scope. Focus on what compilers and linters cannot catch: behavioral side-effects (e.g., a changed default alters runtime behavior for consumers), architectural violations (e.g., a new dependency breaks layering), breaking changes for external consumers of the public API, and security implications. Leave type errors, missing imports, and broken references to CI.\n- Be specific — always reference file and line number\n- Explain WHY something is an issue, not just WHAT, but avoid stating the obvious.\n- For complex matters, provide concrete fix suggestions, including code snippets when helpful\n- Keep it constructive — the goal is to help, not gatekeep\n- Don't repeat the same finding for every occurrence — mention it once and note \"same pattern in {other files}\"\n- Focus on substantive issues only. Do NOT flag purely cosmetic or stylistic concerns. Specifically, never flag: code formatting or whitespace, comment grammar or wording, redundant-but-harmless syntax (e.g., optional chaining after a truthiness check), code duplication that doesn't cause bugs, or HTML template cosmetics. The only exception is when a stylistic issue has a concrete impact on performance or rendering. Note: missing JSDoc/documentation on public or exported APIs is a substantive finding (per coding preferences), not a cosmetic one — flag it as a Suggestion. A comment, doc, or branch inside a shared/generic layer that names a specific editor alias, entity type, or manifest type instead of describing the contract (root `CLAUDE.md` §2, General-Purpose by Default) is likewise substantive, not stylistic — flag it as a Suggestion.\n- For breaking changes, reference the specific pattern from the CLAUDE.md that should be applied\n- Do not suggest changes that would themselves introduce breaking changes. If a suggestion would alter public API surface (e.g., changing return types, renaming public members), it is not appropriate for a PR targeting `main` within a major version. Only suggest non-breaking alternatives.\n",".claude/skills/umb-update-openapi/SKILL.md":"---\nname: umb-update-openapi\ndescription: Regenerate the Management API OpenApi.json for Umbraco CMS from a running backend, and optionally regenerate the backoffice hey-api client so its generated types match. Proactively offer to run this at the end of any work that adds, removes, or changes Management API controllers, endpoints, routes, or their request/response/view models — OpenApi.json and the generated backend-api client go stale the moment the API surface changes, so suggest syncing rather than waiting to be asked. Also use it whenever the user asks to update/refresh/regenerate OpenApi.json or the openapi document, says the generated backend-api / hey-api client is stale or out of date, or has just added an endpoint and needs the client types — including phrasings like \"sync the OpenAPI spec\", \"update the API client\", \"the openapi doc is out of date\", or \"regenerate the server API\", even if OpenApi.json isn't named explicitly. Does NOT apply to Delivery API changes or to internal service/repository edits that don't alter the Management API surface.\n---\n\n# Update Management API OpenApi.json (Umbraco CMS)\n\nRefresh `src/Umbraco.Cms.Api.Management/OpenApi.json` **byte-for-byte** from a running backend's OpenAPI document, then optionally regenerate the backoffice hey-api client so its generated types match.\n\nThis replaces the old manual routine (run the app, open OpenAPI UI, copy JSON, paste, fix formatting). The point of automating it is that a byte-for-byte fetch from the canonical endpoint is deterministic — there's no editor reformatting to strip out, and repeated runs produce identical output.\n\n**Run from the repository root** (the directory containing `umbraco.sln`). Bash's working directory persists between calls, but `cd` can trigger a permission prompt — prefer paths relative to the repo root over `cd`.\n\n## Key facts\n\n- **OpenAPI endpoint:** `/umbraco/openapi/management.json`, served over HTTPS with a dev cert. OpenApi is only mapped when the environment is **not Production** — a backend running in Production returns 404 here even though it's healthy.\n- **Port:** default **`44339`** (the `launchSettings.json` https profile). Assume it unless the user says otherwise.\n- **Target file:** `src/Umbraco.Cms.Api.Management/OpenApi.json` — git-tracked, so git is the safety net for a bad fetch.\n- **The mechanical work is a script, not this document.** `npm run generate:openapi` fetches, validates and writes the spec; `npm run generate:server-api` then regenerates the client **from the committed file**. Prefer these over hand-rolled `curl`.\n- **Keep those two commands separate — never chain them into one.** The client generator reads the committed `OpenApi.json` and only that. Pointing it at a live server instead has been tried and removed (b8c2a3366fd): it broke base-url handling, and worse, it produced a client with no matching committed schema. Schema and client belong in the same commit.\n- **This skill exists for the parts a script can't do:** knowing _when_ to run it, managing the backend lifecycle, and reading the diff.\n\n## Procedure\n\n### 1. Confirm the target exists\n\nVerify `src/Umbraco.Cms.Api.Management/OpenApi.json` is present. If it isn't, you're likely not at the repo root — stop and say so rather than writing a file in the wrong place.\n\n### 2. Make sure a backend is running on 44339\n\n> **The fetched document reflects the _running_ build, not the working tree.** If Management API code changed since the server started, a still-running instance serves a stale spec — you'll get a byte-for-byte-valid file that's silently missing your latest endpoints. If the user changed the API, make sure the instance was (re)built/restarted after those changes; if in doubt, restart it.\n>\n> **Not every endpoint appears in the document.** Controllers marked `[ApiExplorerSettings(IgnoreApi = true)]` are excluded by design — notably the back-office _security_ endpoints (`BackOfficeController`: login, token, sign-out, authorize, keep-alive). So the absence of, say, a new `keep-alive` endpoint is expected, not a sign of a stale build. Don't use \"is my new endpoint in the doc?\" as a freshness check unless you've confirmed that controller is actually exposed to the API explorer.\n\nProbe for a live document (short timeout, accept the dev cert):\n\n```bash\nCODE=$(curl -sk --max-time 5 -o /dev/null -w \"%{http_code}\" \"https://localhost:44339/umbraco/openapi/management.json\")\n```\n\n- **`200`** → a backend is already up. Reuse it; do **not** stop it afterward (the user had it running).\n- **`000`** → nothing is listening on 44339. Start one yourself, in the background, on 44339, and remember that _you_ started it so you can stop it in step 5. No `--no-build` here on purpose, so a cold repo still works.\n- **anything else** → something is already listening on 44339 but isn't serving the expected OpenAPI document. Don't start another instance on the same port; surface the HTTP status and stop. A `404` from an otherwise healthy Umbraco is the common one — that's a backend running in Production, where OpenApi isn't mapped. Say so, and let the user decide whether to restart it in Development rather than restarting it for them.\n\nFor the `000` case, start the backend with:\n\n```bash\ndotnet run --project src/Umbraco.Web.UI --no-launch-profile -- \\\n  --environment Development --urls https://localhost:44339\n```\n\nRun that with `run_in_background: true` — that also puts the process under the harness's control so step 5 can stop it cleanly. First-run startup (build + boot) can take a couple of minutes — the fetch in step 3 waits for it.\n\n### 3. Fetch byte-for-byte straight into the file\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi -- --wait\n```\n\nThe script writes the raw response body straight into the target — that's the byte-for-byte copy — and only after it parses as JSON, so a failed run leaves `OpenApi.json` untouched rather than restoring it after the fact. `--wait` keeps retrying while nothing is listening yet, so a backend you started in step 2 is picked up the moment it's up (no `sleep` loops); against a server that was already running it returns straight away.\n\nPass a URL as the first argument if the user is on a non-default port.\n\nA non-zero exit is a failed run — report the script's own message rather than retrying. The usual causes are a backend in Production (OpenApi unmapped), a non-`Run` runtime level mid-install/upgrade, or a different app on the port.\n\n### 4. Show the diff\n\nSurface exactly what changed so the user can sanity-check it:\n\n```bash\ngit diff --stat -- src/Umbraco.Cms.Api.Management/OpenApi.json\n```\n\n- **No diff** → the spec is already up to date; say so. **This does not mean the generated client is in sync** — a previous spec change may never have been followed by a regen, so step 6 still applies.\n- **A focused diff** → summarize the changed schemas/paths at a high level.\n- **A whole-file reformat** → flag it. Byte-for-byte is intentional, but a run that rewrites the _entire_ file means the server's serialization differs from what was committed. That's the canonical format going forward and is fine — just make sure the user knows it's a formatting shift, not hundreds of real API changes.\n\n### 5. Stop the backend if you started it\n\nOnly if **you** started it in step 2 (it wasn't already running) — if the probe returned `200`, skip this step and leave the user's server alone.\n\nThe instance you launched in step 2 ran with `run_in_background: true`, so the harness owns it as a tracked background task. Stop _that task_ directly, using the background shell's own stop — not a port scan. This is fully cross-platform (no `lsof`, which isn't installed on Windows) and can only ever stop the process you started, never an unrelated one that happens to be bound to 44339.\n\n### 6. Regenerate the backoffice client to check it's in sync\n\nThe generated hey-api client is downstream of `OpenApi.json`, but it can drift **independently** of it: a previous spec change that was never followed by a regen leaves a stale client even when step 4 shows no spec diff. So don't gate this on step 4 — regenerating is the only way to know the committed client actually matches the current spec. The generator is deterministic and git-recoverable, so running it against an already-synced client is a no-op (empty diff).\n\nGet the user's agreement before running it — it rewrites generated sources and can be noisy. **Ask once, not twice.** If you got here from a proactive offer (you suggested the sync yourself at the end of some Management API work), the client regen belongs in that same offer — \"shall I sync `OpenApi.json` and the generated client?\" — and a yes covers this step. Only ask separately if you're mid-run and it genuinely hasn't come up yet:\n\n> \"Want me to regenerate the backoffice hey-api client (`npm run generate:server-api`) to confirm it's in sync with `OpenApi.json`? It's a no-op if it already matches.\"\n\nIf yes:\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\ngit diff --stat -- src/Umbraco.Web.UI.Client/src/packages/core/backend-api/\n```\n\nRead the resulting diff honestly:\n\n- **No diff** → the client is in sync; done.\n- **A diff even though step 4 showed no spec change** → the client had drifted (a prior spec update wasn't regenerated). Surface it — this is exactly the case worth catching, and the regen you just ran fixes it.\n- **A diff matching this run's spec changes** → expected; summarize it.\n\nIf the user declines, note the client's sync status is now unverified against `OpenApi.json`.\n\n## Notes\n\n- **Don't reformat the fetched JSON.** The user asked for byte-for-byte; the endpoint is the source of truth. If diffs ever look noisy from formatting alone, that's a decision to make with the user (e.g. a normalization pass) — not something to do silently.\n- **JSON-valid does not mean fresh.** A well-formed response from a pre-change build is still adopted (see step 2). When correctness matters more than speed, restart the backend so it reflects current source before fetching.\n- This skill talks to a live server and, optionally, mutates generated sources — it is not read-only, but it is safe to re-run and git-recoverable. It's idempotent when the spec hasn't changed.\n- If the port isn't reachable and starting the backend fails (build errors, DB not configured), surface the `dotnet run` output rather than retrying blindly — the fix is in the app, not the skill.\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md":"---\nname: general-add-value-summary\ndescription: Add a value summary extension so a feature's typed value can be displayed compactly in collection views (e.g., table columns). Use when a feature or property editor already has a value type and needs a renderer for collection table cells. Two variants — simple (element only, no API call) and resolver (batch API resolution needed). Prerequisite: a value type constant must already exist — use the general-add-value-type skill first if it doesn't.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Summary\n\nAdd a `valueSummary` extension so a feature's values are rendered in collection views.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Summary](../../../docs/value-summary.md)** — extension type, base classes, resolver contract, rules\n\n## Prerequisite\n\nA value type constant (`UMB_{FEATURE}_VALUE_TYPE`) must already exist and be exported from the package `index.ts`. If it doesn't exist yet, use the **`general-add-value-type`** skill first.\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type constant** — the existing constant to bind to (e.g., `UMB_COLOR_PICKER_VALUE_TYPE`)\n4. **Resolver needed?** — Yes if rendering requires an API call (resolving IDs to names). No if the raw value can be formatted directly.\n\n**If resolver is needed, also ask:**\n5. **Resolved type** — the TypeScript type after resolution (e.g., `UmbUserGroupItemModel`)\n6. **Repository to use** — which item/detail repository resolves the values\n\n---\n\n## Option A: Simple summary (no resolver)\n\nUse when the raw value can be formatted or rendered without an API call — dates, booleans, strings, color hex codes, numeric ranges, etc.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n└── {feature}-value-summary.element.ts\n```\n\n### Step 1: Create the summary element\n\nFile: `{package-path}/value-summary/{feature}-value-summary.element.ts`\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbValueSummaryElementBase } from '@umbraco-cms/backoffice/value-summary';\n\n@customElement('umb-{feature}-value-summary')\nexport class Umb{Feature}ValueSummaryElement extends UmbValueSummaryElementBase<{TValue}> {\n\toverride render() {\n\t\tif (!this._value) return html``;\n\t\treturn html`<span>${this._value}</span>`;\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryElement as element };\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'umb-{feature}-value-summary': Umb{Feature}ValueSummaryElement;\n\t}\n}\n```\n\nAdjust `render()` for the value type:\n- Dates → `this.localize.dateTime(value)`\n- Booleans → `<umb-icon>`\n- References → tags or comma-separated names\n\n### Step 2: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t},\n];\n```\n\n### Step 3: Wire manifests into parent\n\n```typescript\nimport { manifests as valueSummaryManifests } from './value-summary/manifests.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t...valueSummaryManifests,\n\t// ... other manifests\n];\n```\n\n### Simple summary checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase` and exports as `element`\n- [ ] Manifest uses `kind: 'default'`, correct `forValueType`, lazy `element` import\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Option B: Summary with resolver (batch API resolution)\n\nUse when displaying the value requires fetching data — e.g., resolving reference uniques to display names. The resolver receives all values from the entire column in one batch call.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n├── {feature}-value-summary.element.ts\n└── {feature}-value-summary.resolver.ts\n```\n\n### Step 1: Create the summary element\n\nSame pattern as Option A, Step 1. Pass `{TResolved}` as the generic instead of `{TValue}` — the element receives the resolved value in `this._value`, already correctly typed.\n\n### Step 2: Create the resolver\n\nFile: `{package-path}/value-summary/{feature}-value-summary.resolver.ts`\n\n```typescript\nimport type { UmbValueSummaryResolver } from '@umbraco-cms/backoffice/value-summary';\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\nimport { Umb{Entity}ItemRepository } from '../repository/item/{entity}-item.repository.js';\n\nexport class Umb{Feature}ValueSummaryResolver\n\textends UmbControllerBase\n\timplements UmbValueSummaryResolver<{TValue}, {TResolved}>\n{\n\t#repo = new Umb{Entity}ItemRepository(this);\n\n\tasync resolveValues(values: ReadonlyArray<{TValue}>) {\n\t\t// Deduplicate all IDs needed across the entire batch\n\t\tconst allUniques = [...new Set(values.flatMap((v) => /* extract IDs from v */))];\n\n\t\tconst { data: items, asObservable } = await this.#repo.requestItems(allUniques);\n\t\tif (!items) return { data: values.map(() => undefined as unknown as {TResolved}) };\n\n\t\t// Positional mapping — output must align with input\n\t\treturn {\n\t\t\tdata: values.map((v) => /* find resolved item(s) for v */),\n\t\t\tasObservable: asObservable\n\t\t\t\t? () => asObservable().pipe(map((latest) => values.map((v) => /* remap */)))\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryResolver as valueResolver };\n```\n\nCritical rules:\n- `data` must have the **same length** as `values` and be **positionally aligned**.\n- Export the class as `valueResolver` (named export) — the loader checks for this name.\n- Provide `asObservable` only when the resolved data can change after initial load.\n\n### Step 3: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t\tvalueResolver: () => import('./{feature}-value-summary.resolver.js'),\n\t},\n];\n```\n\n### Step 4: Wire manifests into parent\n\nSame as Option A, Step 3.\n\n### Resolver checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase`, renders resolved type, exports as `element`\n- [ ] Resolver extends `UmbControllerBase`, implements `UmbValueSummaryResolver<{TValue}, {TResolved}>`\n- [ ] `resolveValues()` returns `data` positionally aligned with `values` (same length, same order)\n- [ ] Resolver exported as `valueResolver` (named export)\n- [ ] Manifest includes `valueResolver: () => import(...)` lazy loader\n- [ ] `asObservable` included only if resolved data can change reactively\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Collection wiring\n\n**Property editors**: no extra step needed. The document collection reads `editorAlias` from the server and matches it to your `forValueType`.\n\n**Generic table collection kind**: set `valueType` in the collection manifest column:\n\n```typescript\n{ field: 'myField', label: 'My Field', valueType: UMB_{FEATURE}_VALUE_TYPE }\n```\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md":"---\nname: general-add-localization\ndescription: Add localization keys and use them in elements or controllers. Use when adding user-facing text that should be translatable — labels, descriptions, error messages, button text, status text, or any string shown in the backoffice UI.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Localization\n\nAdd translatable text to the Umbraco backoffice.\n\n## What you need from the user\n\n1. **The text to localize** — What the user sees (e.g., \"Create item\", \"Status\")\n2. **Which group it belongs to** — Feature area (e.g., `actions`, `general`, `content`, `user`)\n3. **Where it's used** — Element template, controller logic, or standalone component\n\n## Step 1: Add the key to the English dictionary\n\n**File:** `src/assets/lang/en.ts`\n\nThis file exports a `UmbLocalizationDictionary` — a nested object where top-level keys are groups and nested keys are the terms.\n\n```typescript\n// src/assets/lang/en.ts\nexport default {\n\t// ... existing groups\n\tmyFeature: {\n\t\tmyLabel: 'My Label',\n\t\tmyDescription: 'A description of the feature',\n\t\tcreateFor: (name: string) => (name ? `Create item for ${name}` : 'Create'),\n\t},\n} satisfies UmbLocalizationDictionary;\n```\n\n### Key naming rules\n\n- **Group**: camelCase feature name (e.g., `actions`, `general`, `content`, `media`, `user`)\n- **Term**: camelCase descriptive name (e.g., `assignDomain`, `auditTrail`, `browse`)\n- **Full key** used in code: `group_termName` (underscore separator)\n\n#### Choosing a group\n\nA group covers a **shared UX area**, not just the one component you're editing. Don't pick a group unilaterally — confirm it with the user:\n\n1. **If you already have a good idea of the scope** (from the surrounding code, the feature being worked on, etc.), propose it: \"This looks like it belongs to the `{scope}` scope — should that be the localization group?\"\n2. **If you don't**, ask directly: \"What is the common group name for this localization?\"\n3. **Either way, check for an existing match first.** Search the groups already in `src/assets/lang/en.ts` for one that already covers this UX area, and if you find one, ask whether it should be reused instead of creating a new group: \"`{existingGroup}` already covers this — should I use that instead?\"\n\nExamples already in this codebase:\n\n- `blockEditor` — Block List, Block Grid, and Block RTE share the same block-configuration UX\n- `contentTypeEditor` — Document Type, Media Type, and Member Type share the same editing UX\n- `codeEditor` — anything embedding the code editor\n\nOnly create a new group once the user confirms no existing one fits.\n\n#### Choosing a term\n\nA term names the **situation**, not the wording — two situations with identical English text today should still get separate terms, since the copy can diverge later.\n\nBuild it from two parts:\n\n1. **Subject**: `CreateBlock`, `ConfirmDelete`, `AddGroup`.\n2. **Presentation role**: `Title`, `Description`, `Action`, `Label`, `Notice`, `Message`, `ValidationMessage`, `Headline`, etc.\n\nCombined (subject + role suffix): `createAction`, `confirmDeleteTitle`, `addGroupDescription`.\n\nExample — three terms for one dialog in the `blockEditor` group, same subject (`confirmDeleteBlockGroup`) with different roles:\n\n```typescript\nconfirmDeleteBlockGroupTitle: 'Delete group?',\nconfirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',\nconfirmDeleteBlockGroupNotice: 'The content of these Blocks will still be present, editing of this content will no longer be available and will be shown as unsupported content.',\n```\n\nGrouping by subject keeps every piece of the dialog together, even though the wording shares nothing.\n\n### Value types\n\n| Type               | Use when                 | Example                                           |\n| ------------------ | ------------------------ | ------------------------------------------------- |\n| `string`           | Static text              | `myLabel: 'My Label'`                             |\n| `(args) => string` | Text with dynamic values | `createFor: (name: string) => \\`Create ${name}\\`` |\n\n## Step 2: Use the localized text\n\n### In element templates — `this.localize.term()`\n\nAvailable on any class extending `UmbLitElement`. The `localize` property is provided automatically.\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-element')\nexport class UmbMyElement extends UmbLitElement {\n\toverride render() {\n\t\treturn html`<uui-button label=${this.localize.term('myFeature_myLabel')}></uui-button>`;\n\t}\n}\n```\n\n### In templates — `<umb-localize>` element\n\nFor inline localized text in HTML templates:\n\n```html\n<umb-localize key=\"myFeature_myLabel\"></umb-localize>\n\n<!-- With fallback text (shown if key is missing) -->\n<umb-localize key=\"myFeature_myLabel\">Fallback text</umb-localize>\n```\n\n### In controllers or non-element classes — `UmbLocalizationController`\n\n```typescript\nimport { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';\n\nexport class UmbMyManager extends UmbControllerBase {\n\treadonly #localization = new UmbLocalizationController(this);\n\n\tsomeMethod() {\n\t\tconst label = this.#localization.term('myFeature_myLabel');\n\t}\n}\n```\n\n## Checklist\n\n- [ ] Key added to `src/assets/lang/en.ts` in the correct group\n- [ ] Key follows `group_termName` convention (camelCase, underscore separator)\n- [ ] Used `this.localize.term()` in elements or `UmbLocalizationController` in non-elements\n- [ ] No hardcoded user-facing strings remain\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md":"---\nname: general-add-value-type\ndescription: Add a new value type — a named, typed string constant that extends UmbValueTypeMap. Use when a feature needs to declare what type of value it holds so it can be referenced in collection columns or value summary manifests. Also use when a property editor needs to expose its value type for use in collection views.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Type\n\nAdd a new value type constant to `UmbValueTypeMap`.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Type](../../../docs/value-type.md)** — full reference, naming convention, rules\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type key** — the string alias:\n   - For a property editor: use the schema alias (e.g., `'Umbraco.ColorPicker'`)\n   - For a domain type: use `Umb.ValueType.{Entity}.{Shape}` (e.g., `'Umb.ValueType.UserGroup.References'`)\n4. **TypeScript type** — the type of the raw value (e.g., `string`, `boolean`, `UmbReferenceByUnique[]`)\n\n---\n\n## Step 1: Create the constant file\n\nFile: `{package-path}/value-type/constants.ts`\n\n```typescript\nexport const UMB_{FEATURE}_VALUE_TYPE = '{value-type-key}' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_{FEATURE}_VALUE_TYPE]: {TValue};\n\t}\n}\n```\n\nRules:\n- The string value is always `as const`.\n- The `declare global` block is in the **same file** as the constant.\n- One constant per file — do not group multiple value types.\n\n### Property editor example\n\n```typescript\nimport type { UmbColorPickerPropertyEditorValue } from '../types.js';\n\nexport const UMB_COLOR_PICKER_VALUE_TYPE = 'Umbraco.ColorPicker' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_COLOR_PICKER_VALUE_TYPE]: UmbColorPickerPropertyEditorValue;\n\t}\n}\n```\n\n### Domain type example\n\n```typescript\nimport type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';\n\nexport const UMB_USER_GROUP_REFERENCES_VALUE_TYPE = 'Umb.ValueType.UserGroup.References' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_USER_GROUP_REFERENCES_VALUE_TYPE]: UmbReferenceByUnique[];\n\t}\n}\n```\n\n---\n\n## Step 2: Export from the package index\n\nAdd to the package `index.ts`:\n\n```typescript\nexport * from './value-type/constants.js';\n```\n\n---\n\n## Checklist\n\n- [ ] Constant declared with `as const`\n- [ ] `declare global` block extends `UmbValueTypeMap` in the same file\n- [ ] Key follows naming convention (schema alias for property editors, `Umb.ValueType.{Entity}.{Shape}` for domain types)\n- [ ] TypeScript type is the correct raw value type\n- [ ] One constant per file\n- [ ] Constant exported from package `index.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md":"---\nname: general-create-condition\ndescription: Create a new extension condition that controls when extensions are active. Use when you need to conditionally show or hide extensions based on application state (e.g., current section, user permissions, entity state, workspace context). Conditions are registered as extensions and referenced in manifest condition arrays.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Condition\n\nCreate a new extension condition that controls when extensions are active.\n\n## What you need from the user\n\n1. **What it checks** — What context or state determines the condition (e.g., current section, user role, entity state)\n2. **Config properties** — What parameters consumers provide (e.g., `match`, `oneOf`)\n3. **Context dependency** — Which context token provides the data to evaluate\n\n## Files to create\n\nPlace in a `conditions/` subdirectory of the relevant feature:\n\n```\nmy-feature/\n└── conditions/\n    └── my-condition/\n        ├── constants.ts                     # Condition alias constant\n        ├── types.ts                         # Config type + global declaration\n        ├── my-condition.condition.ts         # Condition controller\n        └── manifests.ts                     # Condition manifest\n```\n\n## Step 1: Define the alias constant\n\n```typescript\n// constants.ts\nexport const UMB_MY_CONDITION_ALIAS = 'Umb.Condition.MyCondition';\n```\n\n## Step 2: Define the config type\n\n```typescript\n// types.ts\nimport type { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MyConditionConfig extends UmbConditionConfigBase<typeof UMB_MY_CONDITION_ALIAS> {\n\t/**\n\t * The value to match against.\n\t * @example \"Umb.Section.Content\"\n\t */\n\tmatch?: string;\n\n\t/**\n\t * One or more values to match against (any match passes).\n\t * @example [\"Umb.Section.Content\", \"Umb.Section.Media\"]\n\t */\n\toneOf?: Array<string>;\n}\n\ndeclare global {\n\tinterface UmbExtensionConditionConfigMap {\n\t\tUmbMyConditionConfig: MyConditionConfig;\n\t}\n}\n```\n\n### Key rules\n\n- Extend `UmbConditionConfigBase<typeof ALIAS_CONSTANT>` — this types the `alias` property\n- The `declare global` block on `UmbExtensionConditionConfigMap` makes the config type-safe when used in extension manifests\n- Use a unique key in the map\n- Add JSDoc with `@example` — these feed into JSON schema generation\n\n### Common config patterns\n\n| Pattern | Use when |\n|---------|----------|\n| `match: string` | Single value match |\n| `oneOf: Array<string>` | Any of multiple values |\n| `allOf: Array<string>` | All values must match |\n| `noneOf: Array<string>` | None of the values match |\n| No config properties | Condition checks a boolean state (e.g., \"is trashed\", \"is admin\") |\n\n## Step 3: Implement the condition controller\n\n```typescript\n// my-condition.condition.ts\nimport type { MyConditionConfig } from './types.js';\nimport { UMB_MY_CONTEXT } from '../../my-context.token.js';\nimport type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';\nimport type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';\nimport { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';\n\nexport class UmbMyCondition\n\textends UmbConditionBase<MyConditionConfig>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<MyConditionConfig>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_MY_CONTEXT, (context) => {\n\t\t\tthis.observe(\n\t\t\t\tcontext.someObservable,\n\t\t\t\t(value) => {\n\t\t\t\t\tthis.permitted = this.#check(value);\n\t\t\t\t},\n\t\t\t\t'_observeConditionValue',\n\t\t\t);\n\t\t});\n\t}\n\n\t#check(value: string | undefined): boolean {\n\t\tif (!value) return false;\n\n\t\tif (this.config.match) {\n\t\t\treturn value === this.config.match;\n\t\t}\n\n\t\tif (this.config.oneOf) {\n\t\t\treturn this.config.oneOf.includes(value);\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\nexport { UmbMyCondition as api };\n```\n\n### Key rules\n\n- Extend `UmbConditionBase<ConfigType>` — provides `permitted` property and `config` access\n- Implement `UmbExtensionCondition`\n- Constructor signature: `(host: UmbControllerHost, args: UmbConditionControllerArguments<ConfigType>)`\n- Set `this.permitted = true/false` — the base class notifies the extension system on change\n- Use `consumeContext()` + `observe()` for reactive conditions that update as state changes\n- Export as `api` (named export) so the class can be imported by the manifest\n- Use an explicit observer alias so re-evaluation replaces the previous subscription\n\n### For simple boolean conditions (no config)\n\n```typescript\nexport class UmbIsSomethingCondition\n\textends UmbConditionBase<UmbConditionConfigBase>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_SOME_CONTEXT, (context) => {\n\t\t\tthis.observe(context.isSomething, (value) => {\n\t\t\t\tthis.permitted = value === true;\n\t\t\t});\n\t\t});\n\t}\n}\n```\n\n## Step 4: Register the condition manifest\n\nConditions must load fast — always import the class directly (no lazy `() => import()`).\n\n```typescript\n// manifests.ts\nimport { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport { UmbMyCondition } from './my-condition.condition.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'condition',\n\t\tname: 'My Condition',\n\t\talias: UMB_MY_CONDITION_ALIAS,\n\t\tapi: UmbMyCondition,\n\t},\n];\n```\n\nAdd to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## How consumers use the condition\n\nIn any extension manifest that supports conditions:\n\n```typescript\n{\n\ttype: 'workspaceAction',\n\talias: 'My.WorkspaceAction',\n\tname: 'My Action',\n\tconditions: [\n\t\t{\n\t\t\talias: 'Umb.Condition.MyCondition',\n\t\t\tmatch: 'some-value',\n\t\t},\n\t],\n}\n```\n\nAll conditions in the array must be `permitted` for the extension to be active. They are AND-ed together.\n\n## Checklist\n\n- [ ] Alias constant exported from `constants.ts`\n- [ ] Config type extends `UmbConditionConfigBase<typeof ALIAS>` with the alias constant\n- [ ] Config type declared on global `UmbExtensionConditionConfigMap`\n- [ ] Condition class extends `UmbConditionBase<ConfigType>` and implements `UmbExtensionCondition`\n- [ ] Constructor calls `super(host, args)` and sets up context observation\n- [ ] `this.permitted` is set reactively (via `observe()`) — not just once in the constructor\n- [ ] Condition manifest registered with `type: 'condition'`\n- [ ] Manifests bubbled up to the package-level `manifests.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md":"---\nname: general-create-extension-type\ndescription: Create a new extension type for the Umbraco backoffice extension registry. Use when adding a new type of extension that can be registered via manifests (e.g., a new dashboard type, a new sidebar app type, a new toolbar extension type). Any package can define extension types.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Extension Type\n\nCreate a new extension type for the Umbraco backoffice extension registry.\n\n## What you need from the user\n\n1. **Type name** — The `type` string used in manifests (e.g., `'dashboard'`, `'searchProvider'`)\n2. **Purpose** — What this extension type does (determines which base interface to extend)\n3. **Meta shape** — What configuration the extension needs from consumers\n4. **Whether it needs conditions** — Should extensions of this type support conditional loading?\n5. **Whether it has an element, API, or both** — Does it render UI, provide logic, or both?\n\n## Files to create\n\nAll files go in the relevant feature directory (e.g., `src/packages/my-package/my-feature/`).\n\n```\nmy-feature/\n├── my-feature.extension.ts     # Type definition + global declaration\n├── my-feature-element.interface.ts  # Element interface (if element-based)\n└── types.ts                    # Meta interface (if needed)\n```\n\n## Step 1: Choose the base manifest interface\n\n| Your extension... | Extend from | Import |\n|---|---|---|\n| Renders a custom element | `ManifestElement<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Provides an API class (no UI) | `ManifestApi<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Has both element and API | `ManifestElementAndApi<E, A>` | `@umbraco-cms/backoffice/extension-api` |\n| Needs conditional loading | Also extend `ManifestWithDynamicConditions<UmbExtensionConditionConfig>` | `@umbraco-cms/backoffice/extension-api` |\n\n## Step 2: Define the manifest interface\n\n```typescript\n// my-feature.extension.ts\nimport type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MetaMyFeature {\n\tlabel?: string;\n\t// ... properties consumers configure via meta\n}\n\nexport interface ManifestMyFeature\n\textends ManifestElement<UmbMyFeatureElement>,\n\t\tManifestWithDynamicConditions<UmbExtensionConditionConfig> {\n\ttype: 'myFeature';\n\tmeta: MetaMyFeature;\n}\n\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature;\n\t}\n}\n```\n\n### Key rules\n\n- The `type` property must be a **string literal type** (not `string`)\n- The key in `UmbExtensionManifestMap` must be unique across the entire codebase\n- The `declare global` block is what makes the registry type-aware — without it, the type won't be recognized\n\n## Step 3: Define the element/API interface (if needed)\n\n```typescript\n// my-feature-element.interface.ts\nimport type { UmbElement } from '@umbraco-cms/backoffice/element-api';\n\nexport interface UmbMyFeatureElement extends UmbElement {\n\t// Properties/methods that implementations must provide\n}\n```\n\n## Step 4: If the type supports kinds\n\nAdd a union type for the base manifest plus kind-specific variants:\n\n```typescript\nexport interface ManifestMyFeatureButtonKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'button';\n\tmeta: MetaMyFeatureButtonKind;\n}\n\nexport interface MetaMyFeatureButtonKind extends MetaMyFeature {\n\ticon: string;\n}\n\n// Union all variants in the global declaration\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureButtonKind;\n\t}\n}\n```\n\n## Step 5: Export from the package barrel\n\nAdd the type export to the package's `index.ts`:\n\n```typescript\nexport type * from './my-feature/my-feature.extension.js';\n```\n\nUse `export type *` since these are type-only exports.\n\n## Step 6: Register manifests that use this type\n\nVerify the type works by creating a test manifest:\n\n```typescript\nconst manifest: UmbExtensionManifest = {\n\ttype: 'myFeature',\n\talias: 'Umb.MyFeature.Test',\n\tname: 'Test My Feature',\n\telement: () => import('./my-feature-test.element.js'),\n\tmeta: {\n\t\tlabel: 'Test',\n\t},\n};\n```\n\nTypeScript should provide autocomplete for `type` and validate `meta` against `MetaMyFeature`.\n\n## Checklist\n\n- [ ] Manifest interface extends the correct base (`ManifestElement`, `ManifestApi`, or `ManifestElementAndApi`)\n- [ ] `type` property is a string literal, not `string`\n- [ ] `declare global` block adds to `UmbExtensionManifestMap` with a unique key\n- [ ] Meta interface is defined separately and exported\n- [ ] Element/API interface is defined if the type renders UI or provides logic\n- [ ] If kinds are supported: kind-specific interfaces extend the base with literal `kind` property\n- [ ] Type is exported from the package's `index.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md":"---\nname: general-create-kind\ndescription: Create a new extension kind — a reusable default implementation for an extension type. Use when multiple extensions of the same type share the same element/API implementation and only differ in configuration. Kinds reduce boilerplate by providing pre-built defaults that extensions customize via meta.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Kind\n\nCreate a new extension kind — a reusable default implementation for an extension type.\n\n## What you need from the user\n\n1. **Which extension type** — The `type` this kind applies to (e.g., `'entityAction'`, `'section'`)\n2. **Kind name** — The `kind` string (e.g., `'delete'`, `'default'`, `'button'`)\n3. **What it provides** — Default element, API, or both\n4. **Meta defaults** — What meta values the kind pre-fills for consumers\n\n## When to create a kind\n\nCreate a kind when **multiple extensions of the same type share the same element/API implementation** and only differ in meta configuration. If the implementation is unique, a kind adds unnecessary indirection.\n\n## Files to create\n\n```\nmy-feature/\n├── kinds/\n│   └── my-kind/\n│       ├── my-kind.kind.ts              # Kind manifest definition\n│       ├── my-kind.element.ts           # Default element (if providing UI)\n│       ├── my-kind.api.ts               # Default API class (if providing logic)\n│       └── manifests.ts                 # Exports the kind manifest\n```\n\n## Step 1: Define the kind manifest\n\n```typescript\n// my-kind.kind.ts\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const UMB_MY_FEATURE_MY_KIND_MANIFEST: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.MyKind',\n\tmatchType: 'myFeature',\n\tmatchKind: 'myKind',\n\tmanifest: {\n\t\ttype: 'myFeature',\n\t\tkind: 'myKind',\n\t\telement: () => import('./my-kind.element.js'),\n\t\tapi: () => import('./my-kind.api.js'),\n\t\tweight: 1000,\n\t\tmeta: {\n\t\t\ticon: '',\n\t\t\tlabel: '',\n\t\t\t// ... defaults that consumers override\n\t\t},\n\t},\n};\n\nexport const manifest = UMB_MY_FEATURE_MY_KIND_MANIFEST;\n```\n\n### Key fields\n\n- **`type`**: Always `'kind'`\n- **`alias`**: Unique identifier. Convention: `Umb.Kind.{ExtensionType}.{KindName}`\n- **`matchType`**: The extension type this kind applies to\n- **`matchKind`**: The kind name that extensions reference\n- **`manifest`**: Partial manifest with defaults — any property here becomes the base for extensions using this kind\n\n## Step 2: Create the default element (if providing UI)\n\n```typescript\n// my-kind.element.ts\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-kind-element')\nexport class UmbMyKindElement extends UmbLitElement {\n\t// This element is shared by all extensions using this kind.\n\t// Use manifest meta for customization — not hardcoded values.\n\n\toverride render() {\n\t\treturn html`<!-- default rendering -->`;\n\t}\n}\n\nexport { UmbMyKindElement as element };\n```\n\n**Important:** Export as `element` (named export) — the lazy `import()` in the kind manifest resolves this name.\n\n## Step 3: Create the default API (if providing logic)\n\n```typescript\n// my-kind.api.ts\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\n\nexport class UmbMyKindApi extends UmbControllerBase {\n\t// Shared logic for all extensions using this kind.\n\t// Access extension-specific config via the manifest meta.\n}\n\nexport { UmbMyKindApi as api };\n```\n\n## Step 4: Register the kind in manifests\n\n```typescript\n// manifests.ts\nimport { manifest as myKindManifest } from './my-kind.kind.js';\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [myKindManifest];\n```\n\nAdd this to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## Step 5: Add kind-specific type to the extension type (if not already done)\n\nIn the extension type definition file, add a variant interface for this kind:\n\n```typescript\nexport interface ManifestMyFeatureMyKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'myKind';\n\tmeta: MetaMyFeatureMyKind;\n}\n\nexport interface MetaMyFeatureMyKind extends MetaMyFeature {\n\ticon: string;\n\tlabel: string;\n}\n\n// Update the global declaration to include the kind variant\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureMyKind;\n\t}\n}\n```\n\n## How consumers use the kind\n\nExtensions reference the kind and only specify what differs:\n\n```typescript\n{\n\ttype: 'myFeature',\n\tkind: 'myKind',\n\talias: 'My.Package.MyFeature.Specific',\n\tname: 'My Specific Feature',\n\tmeta: {\n\t\ticon: 'icon-heart',\n\t\tlabel: 'My Label',\n\t},\n}\n```\n\nThe registry merges the kind defaults with the extension manifest:\n- Extension properties **override** kind properties\n- `meta` is **shallow-merged** (extension meta extends kind meta, doesn't replace it)\n\n## Extending an existing kind\n\nA kind can build on another kind by spreading its manifest:\n\n```typescript\nimport { UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST } from '../default/default.kind.js';\n\nexport const manifest: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.Special',\n\tmatchType: 'myFeature',\n\tmatchKind: 'special',\n\tmanifest: {\n\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest,\n\t\ttype: 'myFeature',\n\t\tkind: 'special',\n\t\tapi: () => import('./special.api.js'),\n\t\tmeta: {\n\t\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest.meta,\n\t\t\t// additional meta defaults\n\t\t},\n\t},\n};\n```\n\n## Checklist\n\n- [ ] Kind manifest has `type: 'kind'` with unique `alias`\n- [ ] `matchType` matches the target extension type exactly\n- [ ] `matchKind` matches the `kind` string extensions will use\n- [ ] Default element exported as `element`, default API exported as `api` (named exports for lazy import)\n- [ ] Kind-specific manifest interface added to the extension type's `declare global` block\n- [ ] Kind registered in `manifests.ts` with type `Array<UmbExtensionManifest | UmbExtensionManifestKind>`\n- [ ] Meta defaults use empty strings or sensible fallbacks (not undefined)\n- [ ] Compiles: `npm run compile`\n- [ ] Blast radius considered: changing this kind affects all extensions that use it\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md":"# general-create-package\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md":"# general-create-repository\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md":"# general-create-workspace\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md":"# general-deprecate-api\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/CLAUDE.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.github/copilot-instructions.md)",".claude/skills/umb-review/evals/evals.json":"# Evals Skill\n\nPath: `.claude/skills/umb-review/evals/evals.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/evals/evals.json)",".claude/skills/umb-review/references/breaking-changes.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/breaking-changes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/breaking-changes.md)",".claude/skills/umb-review/references/coding-preferences.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/coding-preferences.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/coding-preferences.md)",".claude/skills/umb-review/references/complexity-assessment.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/complexity-assessment.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/complexity-assessment.md)",".claude/skills/umb-review/references/gh-cli-setup.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/gh-cli-setup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/gh-cli-setup.md)",".claude/skills/umb-review/references/impact-analysis.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/impact-analysis.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/impact-analysis.md)","src/Umbraco.Web.UI.Client/src/packages/block/package.json":"# Block Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/block/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/block/package.json)","src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json":"# Clipboard Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json)","src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json":"# Code-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json)","src/Umbraco.Web.UI.Client/src/packages/content/package.json":"# Content Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/content/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/content/package.json)","src/Umbraco.Web.UI.Client/src/packages/core/package.json":"# Core Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/package.json)","src/Umbraco.Web.UI.Client/src/packages/data-type/package.json":"# Data-type Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/data-type/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/data-type/package.json)","src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json":"# Dictionary Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json)","src/Umbraco.Web.UI.Client/src/packages/documents/package.json":"# Documents Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/documents/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/documents/package.json)","src/Umbraco.Web.UI.Client/src/packages/elements/package.json":"# Elements Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/elements/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/elements/package.json)","src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json":"# Embedded-media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json)","src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json":"# Extension-insights Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json)","src/Umbraco.Web.UI.Client/src/packages/health-check/package.json":"# Health-check Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/health-check/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/health-check/package.json)","src/Umbraco.Web.UI.Client/src/packages/help/package.json":"# Help Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/help/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/help/package.json)","src/Umbraco.Web.UI.Client/src/packages/language/package.json":"# Language Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/language/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/language/package.json)","src/Umbraco.Web.UI.Client/src/packages/library/package.json":"# Library Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/library/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/library/package.json)","src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json":"# Log-viewer Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json)","src/Umbraco.Web.UI.Client/src/packages/management-api/package.json":"# Management-api Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/management-api/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json)","src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json":"# Markdown-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json)","src/Umbraco.Web.UI.Client/src/packages/media/package.json":"# Media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/media/package.json)","src/Umbraco.Web.UI.Client/src/packages/members/package.json":"# Members Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/members/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/members/package.json)","src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json":"# Models-builder Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json)","src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json":"# Multi-url-picker Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json)","src/Umbraco.Web.UI.Client/src/packages/packages/package.json":"# Packages Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/packages/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/packages/package.json)","src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json":"# Performance-profiling Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json)","src/Umbraco.Web.UI.Client/src/packages/preview/package.json":"# Preview Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/preview/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/preview/package.json)","src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json":"# Property-editors Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json)","src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json":"# Publish-cache Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json)","src/Umbraco.Web.UI.Client/src/packages/relations/package.json":"# Relations Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/relations/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/relations/package.json)","src/Umbraco.Web.UI.Client/src/packages/rte/package.json":"# Rte Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/rte/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/rte/package.json)","src/Umbraco.Web.UI.Client/src/packages/segment/package.json":"# Segment Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/segment/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/segment/package.json)","src/Umbraco.Web.UI.Client/src/packages/settings/package.json":"# Settings Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/settings/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/settings/package.json)","src/Umbraco.Web.UI.Client/src/packages/static-file/package.json":"# Static-file Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/static-file/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/static-file/package.json)","src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json":"# Sysinfo Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json)","src/Umbraco.Web.UI.Client/src/packages/tags/package.json":"# Tags Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tags/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tags/package.json)","src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json":"# Telemetry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json)","src/Umbraco.Web.UI.Client/src/packages/templating/package.json":"# Templating Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/templating/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/templating/package.json)","src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md)","src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json)","src/Umbraco.Web.UI.Client/src/packages/translation/package.json":"# Translation Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/translation/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/translation/package.json)","src/Umbraco.Web.UI.Client/src/packages/ufm/package.json":"# Ufm Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/ufm/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/ufm/package.json)","src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json":"# Umbraco-news Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json)","src/Umbraco.Web.UI.Client/src/packages/user/package.json":"# User Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/user/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/user/package.json)","src/Umbraco.Web.UI.Client/src/packages/webhook/package.json":"# Webhook Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/webhook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/webhook/package.json)","src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json":"# Icon-registry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json)","src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md":"# validation Documentation\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md)"},"files":{".claude/skills/umb-update-server-dependencies-for-minor/SKILL.md":"---\nname: umb-update-server-dependencies-for-minor\ndescription: Update all server-side (NuGet) dependencies to their latest minor/patch versions for the current branch's version line, then open a PR. Runs dotnet-outdated, bumps minors and patches only (never majors; never moves a stable package onto a pre-release), syncs the template projects, builds and runs unit tests, commits, and creates a PR. Use when asked to update/bump backend/server/NuGet/.NET dependencies for a release (e.g. \"update server dependencies for 17.6\", \"bump NuGet packages to latest minors\").\nargument-hint: [base-branch] (optional; defaults to the current branch)\n---\n\n# Update Server-Side Dependencies (minor & patch) - Umbraco CMS\n\nBumps every NuGet dependency that is behind to its latest available **minor or patch** version for a single version line (the current branch), then builds, tests, commits and opens a PR.\n\n**Scope: one version line at a time.** This skill operates on the branch that is currently checked out (or the branch given as `$ARGUMENTS`). To update multiple release lines (e.g. `v17/dev` and `main`), run it once per line.\n\n**Guardrails (non-negotiable):**\n- **Never** update a major version. Only minors and patches where a newer one exists.\n- **Never** move a stable package onto a pre-release. Packages already on a pre-release *may* be updated to a newer pre-release — this is what `--pre-release Auto` does.\n- **Never** update packages explicitly held back for compatibility (e.g. marked \"HOLD\"/\"do not bump\" in `Directory.Packages.props`). `TODO` comments there may also mark transitive security pins, which are in-scope to bump if needed (see step 3).\n\n## Prerequisites\n\n- `dotnet-outdated-tool` installed globally: `dotnet tool install --global dotnet-outdated-tool` (verify with `dotnet outdated --version`).\n- `gh` CLI authenticated (`gh auth status`).\n\n## Where versions live\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production packages + `GlobalPackageReference` build tooling + transitive security pins |\n| `tests/Directory.Packages.props` | Test-only packages (inherits the root file) |\n| `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` | Inline versions (opts out of central management) |\n| `templates/UmbracoExtension/Umbraco.Extension.csproj` and its `Directory.Packages.props` | **Not in `umbraco.sln`** — dotnet-outdated will NOT touch these; sync them by hand (step 4) |\n\n## Instructions\n\n### 1. Establish the branch\n\nRead `version.json` and take the major version (e.g. `18` from `18.1.0-rc`) — call it `{major}`.\n\nIf the current branch is a long-lived line (`main`, `v{major}/dev`, a release branch) rather than a task branch, create and switch to a new branch:\n\n```bash\ngit switch -c v{major}/task/update-backend-dependencies\n```\n\n(If `$ARGUMENTS` names a base branch, branch from that instead.) If you are already on a suitable task branch, stay on it. Confirm the working tree is clean before starting.\n\n### 2. Discover what is outdated\n\nRun dotnet-outdated restricted to within the current major. `--version-lock Major` keeps updates inside the current major version; `--pre-release Auto` (the default) offers a pre-release upgrade **only when the package is already on a pre-release** — so a stable package never jumps to a pre-release, while an existing pre-release can move to a newer pre-release:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto umbraco.sln\n```\n\nReview the report. Everything listed is a legitimate minor/patch (or same-track pre-release) candidate. Note anything you intend to skip, and verify afterwards (via `git diff`) that no intentionally pinned/held versions were changed; revert any that were.\n\n### 3. Apply the updates\n\nApply the same filter with `-u`:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto -u umbraco.sln\n```\n\n`dotnet-outdated` restores per-package and may print `Failed to restore project after upgrading!` for the test projects — this is a known quirk with the multi-level `tests/Directory.Packages.props` merge; the version writes still land. Verify with `git diff` afterwards.\n\nThis updates the two `Directory.Packages.props` files, the inline `Umbraco.Web.UI.csproj` versions, and any in-solution project. It also handles `GlobalPackageReference` entries (e.g. `Nerdbank.GitVersioning`).\n\nYou can also update transitive **security pins** if a new minor or patch is available.\n\n### 4. Sync the template projects by hand\n\n`templates/UmbracoExtension/` is not part of `umbraco.sln`, so its versions are not updated automatically. For any package that (a) was bumped in the root `Directory.Packages.props` **and** (b) appears in the template, update it to match:\n\n```bash\ngrep -rnE \"Version=\\\"[0-9]\" templates/UmbracoExtension/\n```\n\nTypically this is `Microsoft.AspNetCore.OpenApi` and/or `Swashbuckle.AspNetCore*` in both `templates/UmbracoExtension/Umbraco.Extension.csproj` and `templates/UmbracoExtension/Directory.Packages.props`. Keep them aligned with the root file.\n\n### 5. Build and run unit tests\n\nBuild the full solution in Release, skipping the frontend/npm steps (these are gated for CI/non-VS builds):\n\n```bash\ndotnet build umbraco.sln -c Release -p:UmbracoBuild=true\n```\n\nThen run the unit test suite:\n\n```bash\ndotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj -c Release -p:UmbracoBuild=true --no-build\n```\n\nBoth build and tests must be green before continuing.\n\nConfirm the bumps did not leave (or introduce) a known-vulnerable transitive version:\n\n```bash\ndotnet list umbraco.sln package --vulnerable --include-transitive\n```\n\nPre-existing, unrelated advisories are out of scope for a routine bump — but if a bump you made is the cause, resolve it (raise the pin, or add an inline reference for `Umbraco.Web.UI`).\n\n### 6. Review and commit\n\nConfirm `git status` shows only the intended package files (the two `Directory.Packages.props`, `Umbraco.Web.UI.csproj`, and any template files) — no stray build output, logs, or the schema placeholder. Stage those files explicitly (do not `git add -A`) and commit:\n\n```\nUpdate NuGet packages to latest minor and patch versions\n\nBump all NuGet dependencies that were behind to their latest available\nminor or patch release, as reported by dotnet-outdated. No major-version\nupdates are included.\n```\n\n### 7. Push and open the PR\n\n```bash\ngit push -u origin <branch>\n```\n\nGenerate the PR body from the actual `git diff` of the package files (do not invent versions). Use tables grouping the changes, and keep the **Testing** section to a single line — CI is the source of truth, do not paste build/test counts.\n\nFill in the template below and **write it to a temp file** (e.g. `/tmp/pr-body.md`), then create the PR against the line's dev branch (`v{major}/dev` for a v-line, `main` for the current major):\n\n```bash\ngh pr create --base <dev-branch> --head <branch> \\\n  --title \"Dependencies: Update NuGet packages to latest minor and patch versions\" \\\n  --body-file /tmp/pr-body.md\n```\n\n**PR body template:**\n\n```markdown\n## Description\n\nRoutine dependency maintenance for the **{version}** release. Updates all NuGet packages that were behind to their latest available **minor or patch** version, as reported by `dotnet-outdated`. No major-version updates are included.\n\n### Production packages (`Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Test packages (`tests/Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Inline / template versions\n\n- `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj`: …\n- `templates/UmbracoExtension`: …\n\n### Deliberately left unchanged\n\n- Any package where only a **major** update is available (out of scope for this PR).\n- Intentionally-held packages carrying a `HOLD`/`do not bump` comment (e.g. `Umbraco.Code`).\n\n## Testing\n\nSolution should build and CI checks pass.\n```\n\n### 8. Report\n\nOutput the PR URL, the branch name, and a short summary of what was bumped (and anything deliberately skipped or any pin that had to be raised).\n\n## Notes\n\n- If `dotnet-outdated` reports nothing outdated, say so and stop — do not create an empty PR.\n- No `OpenApi.json` regeneration is needed for pure dependency bumps (no Management API controllers/models change).\n",".claude/skills/umb-bump-version/SKILL.md":"---\nname: umb-bump-version\ndescription: Bump the Umbraco CMS version across all required files. Use when the user asks to bump, update, or set the version number — e.g., \"bump version to 17.3.4\", \"set version to 18.0.0-rc\", \"update version\". Accepts the target version as an argument.\nargument-hint: <version> (e.g., 17.3.4, 18.0.0-rc)\n---\n\n# Bump Version - Umbraco CMS\n\nUpdates the Umbraco CMS version string across all files that track it.\n\n**Do NOT use AskUserQuestion if a version argument is provided. Only ask if `$ARGUMENTS` is empty or cannot be parsed as a version.**\n\n## Arguments\n\n- `$ARGUMENTS` - Required: the target version string (e.g., `17.3.4`, `18.0.0-rc`)\n\n## Files to Update\n\nThe following 5 files must be updated with the new version:\n\n| # | File | Field |\n|---|------|-------|\n| 1 | `version.json` | `\"version\"` |\n| 2 | `src/Umbraco.Web.UI.Client/package.json` | `\"version\"` |\n| 3 | `src/Umbraco.Web.UI.Client/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n| 4 | `tests/Umbraco.Tests.AcceptanceTest/package.json` | `\"version\"` |\n| 5 | `tests/Umbraco.Tests.AcceptanceTest/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n\n**Note**: For major version bumps (e.g., 17.x to 18.x), `src/Umbraco.Web.UI.Login/package.json` has a caret-ranged dependency on `@umbraco-cms/backoffice` (e.g., `^17.2.0`) that will need manual updating. This skill does not handle that — major bumps involve many other changes beyond version strings.\n\n## Instructions\n\n### 1. Parse and Validate the Version\n\nExtract the version from `$ARGUMENTS`. It must be a valid semver-like string (e.g., `17.3.4`, `18.0.0-rc`, `17.4.0-preview.1`). If no version is provided or it cannot be parsed, ask the user for the target version.\n\n### 2. Read the Current Version\n\nRead `version.json` and extract the current `\"version\"` value. If the current version already equals the target version, report that the version is already set and stop — do not edit, stage, or commit anything.\n\nOtherwise, display both versions:\n\n```\nBumping version: {current} -> {target}\n```\n\n### 3. Update All Files\n\nUpdate each of the 5 files listed above, replacing the old version with the new version. For each file:\n\n- **`version.json`**: Replace the `\"version\"` value.\n- **`package.json` files**: Replace the `\"version\"` value (near the top of the file).\n- **`package-lock.json` files**: Replace BOTH the top-level `\"version\"` value AND the `\"version\"` inside the `\"packages\": { \"\": { ... } }` block. These are always in the first ~10 lines of the file.\n\nUse targeted edits — do NOT rewrite entire files. Be precise to avoid changing version strings in dependency entries.\n\n### 4. Verify\n\nAfter all edits, grep for the target version value across the 5 files to confirm all updates landed correctly:\n\n```bash\ngrep -n \"\\\"version\\\": \\\"{version}\\\"\" version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nExpect exactly 7 matches (one per `package.json` and `version.json`, two per `package-lock.json`).\n\n### 5. Stage and Commit\n\nStage only the 5 changed files:\n\n```bash\ngit add version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nThen commit with the message `Bump version to {version}.` — replacing `{version}` with the target version:\n\n```bash\ngit commit -m \"Bump version to {version}.\"\n```\n\n### 6. Report\n\nOutput a summary:\n\n```\nVersion bumped to {version} in:\n- version.json\n- src/Umbraco.Web.UI.Client/package.json\n- src/Umbraco.Web.UI.Client/package-lock.json\n- tests/Umbraco.Tests.AcceptanceTest/package.json\n- tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n\nChanges staged and committed.\n```\n","src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md":"---\nname: core-add-module\ndescription: Add a new module to the core package (src/packages/core/). Use when adding shared UI framework infrastructure that all packages can use — e.g., a new extension system feature, a new utility, a new shared component pattern, a new picker, or a new workspace primitive. Core modules are imported as @umbraco-cms/backoffice/{module-name}. Also use when the user says things like \"add a module to core\", \"create a new core feature\", or \"add shared infrastructure\".\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Core Module\n\nAdd a new module to the core package (`src/packages/core/`).\n\n## Naming conventions\n\n- **Module names** are **singular** (e.g., `picker`, `sorter`, `entity-flag`, `modal`, `section`)\n\n## What you need from the user\n\n1. **Module name** — What to call it in singular form (kebab-case, e.g., `picker`, `sorter`, `entity-flag`)\n2. **Purpose** — What shared infrastructure this provides\n\n## When to add to core vs. create a new package\n\n- **Core module**: Shared UI framework infrastructure used by multiple packages — extension primitives, pickers, validation, repository base classes, workspace utilities.\n- **New package**: Domain-specific CMS features (documents, media, webhook). Use the `create-package` skill instead.\n\nCore modules should not implement CMS-specific features. They provide the building blocks that CMS feature packages use.\n\n## Files to create\n\n```\nsrc/packages/core/{module-name}/\n├── index.ts                   # Public API exports\n├── manifests.ts               # Extension registrations (if any)\n├── constants.ts               # Module constants\n└── types.ts                   # Module types\n```\n\n## Step 1: Create the module directory and files\n\n### {module-name}/index.ts\n\nThe public API. Only export what other packages need:\n\n```typescript\nexport * from './constants.js';\nexport type * from './types.js';\n```\n\nUse `export type *` for type-only exports.\n\n### {module-name}/manifests.ts\n\nExtension registrations for this module:\n\n```typescript\nexport const manifests: Array<UmbExtensionManifest> = [\n\t// Extension registrations\n];\n```\n\nIf the module has no extensions (pure utility), export an empty array.\n\n### {module-name}/constants.ts\n\n```typescript\n// Aliases, context tokens, entity types\n```\n\n### {module-name}/types.ts\n\n```typescript\n// Type definitions, interfaces, meta types\n```\n\n## Step 2: Wire into core's manifests.ts\n\nAdd the module's manifests to `src/packages/core/manifests.ts`:\n\n```typescript\nimport { manifests as {moduleName}Manifests } from './{module-name}/manifests.js';\n\n// In the manifests array:\n...{moduleName}Manifests,\n```\n\nThe imports are in alphabetical order — insert in the right position.\n\n## Step 3: Add entry point to core's vite.config.ts\n\nAdd the module to the `entry` object in `src/packages/core/vite.config.ts`:\n\n```typescript\n'{module-name}/index': './{module-name}/index.ts',\n```\n\nThe entries are in alphabetical order — insert in the right position. This tells Vite to build the module as a separate entry point.\n\n## Step 4: Register the subpath export\n\nAdd to the `exports` field in the root `package.json` (`src/Umbraco.Web.UI.Client/package.json`):\n\n```json\n\"./{module-name}\": \"./dist-cms/packages/core/{module-name}/index.js\"\n```\n\nThis enables imports like `@umbraco-cms/backoffice/{module-name}`.\n\n## Step 5: Regenerate TypeScript config\n\n```bash\nnpm run generate:tsconfig\n```\n\nThis updates `tsconfig.json` paths so the new module resolves correctly in TypeScript.\n\n## How consumers import\n\nAfter registration, other packages import from the module like:\n\n```typescript\nimport { MyThing } from '@umbraco-cms/backoffice/{module-name}';\nimport type { MyType } from '@umbraco-cms/backoffice/{module-name}';\n```\n\n## Reference: existing core modules to study\n\n- **Simple utility**: `src/packages/core/culture/` — repository + components, minimal surface\n- **Extension primitive**: `src/packages/core/entity-action/` — kinds, base classes, shared element\n- **Infrastructure**: `src/packages/core/repository/` — base classes for the data access pattern\n\n## Checklist\n\n- [ ] Module directory created under `src/packages/core/`\n- [ ] `index.ts` exports only the public API\n- [ ] `manifests.ts` exports extension registrations (or empty array)\n- [ ] `constants.ts` and `types.ts` created\n- [ ] Module manifests imported in `src/packages/core/manifests.ts` (alphabetical order)\n- [ ] Entry point added to `src/packages/core/vite.config.ts` (alphabetical order)\n- [ ] Subpath export added to root `package.json`\n- [ ] `npm run generate:tsconfig` executed\n- [ ] Compiles: `npm run compile`\n",".claude/skills/umb-release-notes/SKILL.md":"---\nname: umb-release-notes\ndescription: Improve a set of auto-generated GitHub release notes for an Umbraco CMS release. Cross-checks the notes against every PR carrying the release label, adds any that are missing, re-files every PR under the most appropriate category, and strips purely-internal entries. Use whenever the user asks to tidy up, improve, complete, or recategorize release notes for a given version, or mentions a release-notes text file plus a version number.\nargument-hint: <version> <path-to-generated-notes-file>\n---\n\n# Umbraco CMS - Improve Release Notes\n\nTakes a file of auto-generated GitHub release notes and produces an improved version that:\n\n1. **Is complete** — every merged PR carrying the `release/<version>` label appears.\n2. **Is well-categorized** — every PR sits under the most appropriate heading.\n3. **Is free of noise** — purely-internal entries of no value to a reader are removed.\n\nThe result is written to a **new** file alongside the input, so the user can diff the two.\n\n**Run autonomously.** Do NOT use `AskUserQuestion` once the required arguments (version and input file path) are available — only ask if one of them is missing from `$ARGUMENTS` and cannot be inferred (see Arguments). Beyond that, make the categorization calls yourself using the rules below; if a handful are genuinely borderline, place them anyway and note the borderline ones in your closing summary so the user can override.\n\n## Arguments\n\n`$ARGUMENTS` contains two values:\n\n1. **Version** — e.g. `17.5.0`, `18.1.0`. The GitHub label to search is `release/<version>` (so version `17.5.0` → label `release/17.5.0`).\n2. **Input file path** — full path to the text file holding the auto-generated notes (e.g. `C:\\Temp\\release-17.5.0-rc.md`).\n\nIf either is missing, ask the user once for the missing value, then proceed.\n\n## Prerequisites\n\nRun `gh auth status`. If it fails, tell the user to authenticate `gh` (e.g. `gh auth login`) and stop — the skill needs the GitHub CLI to query PRs. The repo is always `umbraco/Umbraco-CMS`.\n\n## Procedure\n\n### 1. Read the input notes\n\nRead the input file. Note its structure — it is GitHub's generated format:\n\n- A leading HTML comment (`<!-- Release notes generated ... -->`).\n- A `## What's Changed` heading followed by `### <emoji> <Category>` sub-headings, each with `* <title> by @<author> in <url>` bullets.\n- A trailing `## New Contributors` section and a `**Full Changelog**: ...` line.\n\nExtract the set of PR numbers already present (parse the `/pull/<number>` from each bullet). Preserve each existing bullet's **exact text** (title, author, URL) when you re-emit it — only its category placement may change.\n\n### 2. Fetch every labelled PR\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 \\\n  --json number,title,author,labels,mergedAt \\\n  --jq '.[] | select(.mergedAt != null) | \"\\(.number)\\t\\(.author.login)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"' | sort -n\n```\n\nThis is the authoritative list of what the release *should* contain. Each row gives number, author, labels, title.\n\n**Guard against silent truncation.** `gh pr list` caps at `--limit` without warning, so a large release could drop the overflow and the skill would still look \"complete\". Count the returned rows and compare against the limit:\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 --json number --jq 'length'\n```\n\nIf this equals 1000, the limit was hit — raise `--limit` and re-fetch before continuing. Do **not** proceed on a truncated list.\n\n### 3. Reconcile\n\n- **Missing labelled PRs** (labelled but not in the input file): these must be **added**. Build a bullet as `* <title> by @<author> in https://github.com/umbraco/Umbraco-CMS/pull/<number>`.\n  - **Author handle.** `<author>` in the template is the raw `.author.login` value — the bullet supplies the leading `@`, so do not prepend another. `gh`'s `.author.login` already returns bot accounts with the `[bot]` suffix as part of the login — Dependabot comes back as `dependabot[bot]`, not `dependabot` or `app/dependabot` (the `app/` form only appears in git committer metadata and CODEOWNERS, never in `gh`'s JSON). So the login is already in the right shape; use it verbatim (e.g. `.author.login` of `dependabot[bot]` renders as `@dependabot[bot]`, matching what GitHub's generator wrote for the existing bullets). The only thing to guard against is accidentally stripping or altering the `[bot]` suffix.\n- **PRs in the file but not labelled**: keep them. The generated notes span a commit range (see the `Full Changelog` compare link), so they legitimately include backports / earlier-version PRs that lack the current label. For any of these you need to categorize, fetch its labels with:\n\n  ```bash\n  gh pr view <number> --repo umbraco/Umbraco-CMS --json number,title,labels \\\n    --jq '\"\\(.number)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"'\n  ```\n\nDo **not** invent or alter the `New Contributors` section — carry it over verbatim. You cannot reliably recompute first-time contributors, so leave it as the generator produced it (mention this in the summary).\n\n### 4. Categorize every PR\n\nUse exactly these headings, in this order. Omit any heading that ends up with no entries.\n\n| Heading | What goes here | Primary signal |\n|---|---|---|\n| `### 🙌 Notable Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/notable` |\n| `### 💥 Breaking Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/breaking` |\n| `### 📦 Dependencies` | Dependency bumps | label `dependencies`; or dependabot author |\n| `### 🚀 New Features` | New user- or developer-facing capability | label `type/feature` / `category/feature`; or title introduces/adds a genuinely new capability |\n| `### 🚤 Performance` | Performance improvements | label `category/performance`; or `Performance:` title prefix |\n| `### 🌈 Accessibility Improvements` | A11y improvements (labels, contrast, keyboard) | label `category/accessibility` / `accessibility`; or clear a11y intent (e.g. \"improve contrast\", \"missing labels\") |\n| `### 🐛 Bug Fixes` | Fixes to broken/incorrect behaviour | default for anything describing a fix |\n| `### 🧪 Testing` | Test additions/changes only | label `category/test-automation` / `area/test`; or `E2E`/`QA`/\"acceptance tests\"/\"unit test coverage\"/\"add tests\" titles |\n| `### 🛡️ Code Quality, Documentation and Refactoring` | Refactors, deprecations, API tidy-ups, XML/MD documentation, knowledge-base (`MD`) updates | label `category/refactor`; or titles about refactoring, deprecating, renaming, documenting, constants extraction, MD/CLAUDE.md content |\n| `### 🧑‍💻 Developer Experience` | Things that improve the experience of developers building on or contributing to Umbraco — dev tooling, build/watch ergonomics, test mocks/harnesses, backoffice dev utilities | `Developer Experience` title prefix; dev tooling; mock/harness changes |\n\n**Rules:**\n\n- **Notable and Breaking are off-limits for recategorization** — never move a PR that is *already in the input file* into or out of these sections; they are driven purely by their labels and the generator placed them correctly. This does **not** exempt them from completeness: a PR discovered as missing in step 3 that carries `category/notable` or `category/breaking` must still be **added** under the matching section.\n- Label signals beat title wording, except a `Performance:`/`Developer Experience:` title prefix is decisive for its section.\n- A PR with both `type/feature` and `category/refactor` whose title clearly describes a refactor (e.g. \"swap relative imports\", \"re-export type\") belongs under Code Quality, not New Features.\n- \"Add ... tests\"/\"unit test coverage\" → Testing, even if it also touches docs. If a PR adds XML documentation *and* tests, lead with where the title's emphasis lies (documentation → Code Quality; test coverage → Testing).\n- When a PR is genuinely 50/50, pick the more reader-useful heading and list it in your closing summary as borderline.\n\n### 5. Remove purely-internal noise\n\nDrop entries that have **no value to anyone reading release notes** — pure repository plumbing with no shipped impact. Examples:\n\n- Branch/merge maintenance (\"Fix main branch after merge issue\").\n- CI/pipeline fixes that don't change the product.\n- Reverts of changes that never shipped in a release.\n\n**Keep** anything that ships in the product or genuinely helps developers building on Umbraco — that includes documentation/MD updates, dev tooling, and test mocks (those go to Code Quality or Developer Experience, they are *not* noise). When unsure whether something is noise, keep it and flag it in the summary rather than silently dropping it. List every removal in your closing summary.\n\n### 6. Write the output\n\nWrite to a new file in the **same folder** as the input, named by appending ` - with updates` before the extension:\n\n- Input `C:\\Temp\\release-17.5.0-rc.md` → Output `C:\\Temp\\release-17.5.0-rc - with updates.md`\n\nPreserve the leading HTML comment, the `## What's Changed` heading, the `## New Contributors` section, and the `**Full Changelog**` line exactly. Only the `### <category>` groupings and their bullets change.\n\n### 7. Report\n\nGive a concise summary:\n\n- Count of PRs added (with their numbers), and which categories they landed in.\n- Notable recategorizations (PRs moved out of the catch-all Bug Fixes into Features/Performance/Testing/etc.).\n- Every entry removed, with the one-line reason.\n- Any borderline calls the user may want to override.\n- The output file path.\n\n## Verification\n\nBefore reporting done, confirm:\n\n- Every PR number from step 2 is present in the output (except any you deliberately removed in step 5 — and those must be in the removal list).\n- No PR appears under more than one heading.\n- Notable and Breaking sections are byte-for-byte unchanged from the input.\n- The header comment, New Contributors, and Full Changelog lines are intact.\n",".claude/skills/umb-review/SKILL.md":"---\nname: umb-review\ndescription: Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say \"review\" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.\nargument-hint: <target-branch>\n---\n\n# PR Review - Umbraco CMS\n\nAutomated, non-interactive PR code review. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality.\n\n**Do NOT use AskUserQuestion at any point. This skill runs fully autonomously.**\n\n## Arguments\n\n- `$ARGUMENTS` - Optional: target branch to diff against (auto-detected from PR, falls back to `origin/main`)\n\n## Instructions\n\n### 0. Verify GH CLI is Available\n\nRun `gh auth status`. If it fails, read `references/gh-cli-setup.md` and present the setup instructions to the user. Do not proceed with the review.\n\n### 1. Resolve Target Branch\n\nDetermine the target branch for comparison using this priority order:\n\n1. **Explicit argument**: If `$ARGUMENTS` is provided and non-empty, use it as the target branch\n2. **PR target branch**: If no argument, run `gh pr view --json baseRefName --jq '.baseRefName'` to detect the target branch of the current branch's open PR. If a PR exists, use `origin/{baseRefName}` as the target branch.\n3. **Fallback**: If no argument and no PR found (command fails or returns empty), default to `origin/main`\n\nStore the resolved target branch for use in subsequent steps. Log which resolution method was used (e.g., \"Target branch: `origin/v18/dev` (from PR #1234)\").\n\n### 2. Load Review Standards\n\n#### 2a. Load coding preferences\n\nRead the coding preferences and code review scoring criteria from:\n\n- `references/coding-preferences.md` (relative to this skill file)\n\nParse and internalize all rules, conventions, scoring categories, and severity definitions. These are your review criteria.\n\n#### 2b. Load area-specific documentation\n\nOnce the changed file list is known (after step 3a), determine which areas of the codebase are touched and load the relevant documentation. Execute this sub-step between 3a and 3b. This documentation takes precedence over sibling comparison for architectural and pattern validation.\n\n**Resolution order for each changed file:**\n\n1. **Find the nearest `CLAUDE.md`** — walk up from the changed file's directory toward the repository root. The first `CLAUDE.md` found is the area guide for that file. Read it.\n2. **Read referenced docs** — if the `CLAUDE.md` references documentation files (e.g., a `docs/` directory), use the descriptions in the `CLAUDE.md` to determine which docs are relevant to the type of code being changed, and read those. If unsure, read all referenced docs — the cost of reading is low, the cost of missing a convention is high.\n3. **Follow cross-references in loaded docs** — if a loaded doc references another doc as covering a complementary or related concern, and the changed files touch that concern, read the referenced doc too. Repeat until no new relevant cross-references remain.\n4. **Check for applicable skills** — review the available skills list. If a skill exists for the type of code being changed, read the skill file to understand the expected patterns, structure, and conventions it enforces. Do NOT invoke the skill — just use it as a reference for what the correct implementation should look like.\n\n**Store all loaded documentation** for use in step 4. These docs define the authoritative patterns and conventions that the review evaluates against.\n\n### 3. Gather Changed Files\n\n#### 3a. Collect file list, stats, and diff\n\nRun these git commands (where `{target}` is the resolved target branch):\n\n```bash\ngit diff {target}...HEAD --name-only --diff-filter=d   # changed files (excluding deleted)\ngit diff {target}...HEAD --stat                         # line counts per file\ngit log {target}...HEAD --oneline                       # commit history\ngit diff {target}...HEAD                                # full diff (primary review source)\n```\n\n**If no changes found**: Output \"No changes found between current branch and `{target}`. Nothing to review.\" and stop.\n\n#### 3b. Filter out noise files\n\nFrom the changed file list, classify each file as **noise** or **reviewable**.\n\n**Noise files** (skip entirely — do not read, do not review):\n\n| Pattern                                              | Reason                          |\n| ---------------------------------------------------- | ------------------------------- |\n| `*.gen.ts`, `*.gen.cs`                               | Auto-generated API client code  |\n| `*.generated.cs`, `*.Designer.cs` (in `Migrations/`) | Auto-generated models/snapshots |\n| `*/assets/lang/*.ts` (except `en.ts`)                | Non-English translation files   |\n| `*/mocks/data/*.ts`                                  | Test fixture data               |\n| `*/dist-cms/*`, `*/storybook-static/*`               | Build output                    |\n| `*/TEMP/InMemoryAuto/*`                              | Runtime-generated models        |\n| `package-lock.json`                                  | Dependency lock file            |\n| `appsettings-schema.*.json`                          | Generated JSON schema           |\n\nLog the skip list: \"Skipped {N} noise files: {comma-separated list of filenames}\"\n\n#### 3c. Read reviewable changed files\n\nRead the full file for every reviewable changed file.\n\n#### 3d. Track file counts\n\nKeep track of these numbers for the review output in step 7: total changed files, noise files skipped, and reviewable files read. Also record: distinct production layers touched, distinct project directories, and total lines changed — these feed step 3e.\n\n#### 3e. Assess PR complexity\n\nFollow the procedure in `references/complexity-assessment.md`. Store the triggered dimensions and suggestions for step 7.\n\n#### 3f. Classify PR scope\n\nClassify the PR to determine which review steps are relevant:\n\n| Classification  | Condition                                                                       | Effect                                                                          |\n| --------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |\n| **Gen-only**    | All reviewable files are `gen.ts`                                               | Skip steps 5 and 6; step 4 reviews impact on other code only                    |\n| **Docs-only**   | All reviewable files are `.md`                                                  | Skip steps 5 and 6; step 4 reviews intent and readability only                  |\n| **Test-only**   | All reviewable files are in `tests/`                                            | Skip steps 5 and 6; step 4 reviews intent, code quality, and test coverage only |\n| **Config-only** | All reviewable files are `.csproj`, `.props`, `.json` config, or CI/build files | Skip step 5; step 6 checks dependency version changes only                      |\n| **Standard**    | Anything else                                                                   | No skips — run all steps                                                        |\n\n### 4. Raw Code Review\n\nReview each changed file holistically. Think like a senior developer reading a colleague's PR. Note all findings without worrying about format or severity yet.\n\n#### 4a. Read and reason about each file\n\nFor each changed file, reason about: What does this code do? Is it correct? What's missing — validation, error handling, notifications, cleanup, edge cases? Could this break anything for consumers?\n\n**Unused usings (C# only):** flag a `using` directive only if this diff adds it, or removes the last code that used it — pre-existing unused usings are out of scope. Only flag when confident: a namespace can be used without appearing by name (extension methods, attributes, `<see cref>`, LINQ query syntax, `using static`, aliases, `#if` blocks). Report them as one Suggestion listing `file: namespace` pairs, per root `CLAUDE.md` §11; this is the one stylistic finding that is allowed.\n\n#### 4b. Validate against documentation and patterns\n\nUse a **docs-first** approach: classify the code by what it does, check it against documented conventions, and only fall back to sibling comparison when docs don't cover the pattern.\n\n**Step 1 — Determine the correct approach from documentation, then check whether the PR matches**\n\nA PR is a proposed solution, not the source of truth. This step has two parts that must happen in order — do not start part B until part A is complete.\n\n**Part A — Before validating/judging the implementation**, determine what the correct approach is for each new class or file based on what it does. Use the documentation loaded in step 2b to identify the expected base classes, patterns, and conventions. Write down the expected approach. Classify based on what the code does, not based on what neighboring files look like.\n\n**Part B — Now compare the PR's implementation** against the expected approach from Part A. If it deviates from the documented approach, flag it. If the documentation specifies reference examples, read those examples to verify the implementation matches.\n\n**Pattern match is the leading finding.** If the documentation defines a pattern that fits what the code does, the first and most important finding is whether the code follows that pattern.\n\n**Step 2 — Fall back to sibling comparison**\n\nIf the documentation does not cover the specific pattern, or for cross-cutting concerns not addressed in docs, fall back to sibling comparison:\n\n1. **New method on existing class/interface**: Grep for the most similar existing method on the same class using `-A 80` to capture the full method body (e.g., `UpdateCurrentUserAsync` → grep for `UpdateAsync` in the same file with `-A 80`). Compare line by line for missing cross-cutting concerns: notifications/events, validation, scoping, authorization, error handling, audit logging.\n2. **New TS class**: Grep for siblings by base class (`extends {BaseClass}`) or by interface (`implements {Interface}`) or by name suffix (e.g., `CurrentUserController` → grep for `UserController`). Compare for missing concerns.\n3. **New CS class**: Grep for siblings by base class (`class {ClassName} : {BaseClass}`) or by interface (`class {ClassName} : {Interface}`) or by name suffix (e.g., `ManagementApiComposer` → grep for `ApiComposer`). Compare for missing concerns.\n\n**Important:** Sibling comparison validates cross-cutting concerns, but it must not override documented conventions. If a sibling deviates from documented patterns, that sibling is wrong — do not copy its deviation.\n\nStore your raw findings — they feed into step 7.\n\n### 5. Impact Analysis\n\n**Skip this step if PR scope is docs-only, test-only, or config-only.**\n\nFollow the procedure in `references/impact-analysis.md`.\n\n### 6. Breaking Changes Check\n\n**Skip this step if PR scope is docs-only or test-only. If config-only, only check for dependency version changes that could break consumers.**\n\nFollow the procedure in `references/breaking-changes.md`.\n\n### 7. Consolidate and Output Review\n\nMerge findings from step 4 (raw review), step 5 (impact analysis), and step 6 (breaking changes). For each finding, assign severity (Critical/Important/Suggestion) and verify it relates to changed code — not pre-existing issues. Before outputting, drop any finding about whitespace, blank lines, formatting, or comment wording — but keep findings about comments or code pitched at the wrong altitude (see the review output guidelines below). Then present the review in this exact format:\n\n```markdown\n## PR Review\n\n**Target:** `{target_branch}` · **Based on commit:** `{head_sha}`\n[If any skipped files, append: · **Skipped:** {skipped} files out of {total} total]\n[If step 3f classification is not \"Standard\", append: · **Classified as:** {classification}]\n\n[1–2 sentences: what this PR accomplishes , keep it as short as possible, only highlight the primary essence.]\n\n- **Modified public API:** {changed existing interfaces/types/classes/methods}\n  [Omit bullet if none]\n- **Affected implementations (outside this PR):** {interfaces/types/classes/methods using modified public API}\n  [Omit bullet if none]\n- **Breaking changes:** {violations with specifics}\n  [Omit bullet if none]\n- **Other changes:** {changes not listed above that an Umbraco user, plugin developer, or API consumer would notice — e.g., behavior changes, default value changes, error message changes, new configuration options, removed functionality. Exclude internal renames, formatting, and private implementation details.}\n  [Omit bullet if none]\n\n[If step 3e triggered any dimensions, insert this block. Omit entirely if nothing triggered:]\n\n> [!NOTE]\n> **Complexity advisory** — This PR may benefit from splitting.\n>\n> - **{Dimension}:** {Explanation and concrete split suggestion from step 3e}\n>   [one bullet per triggered dimension]\n>\n> _This is an observation, not a blocker. The full review follows below._\n\n---\n\n### Critical\n\n[Must fix before merge — security vulnerabilities, data loss, broken functionality, breaking changes without proper patterns]\n\n- **`{file}:{line}`**: {problem} → {fix}\n\n[Omit section if none]\n\n### Important\n\n[Should fix — performance issues, missing tests, architectural violations, pattern misuse]\n\n- **`{file}:{line}`**: {observation} → {suggestion}\n\n[Omit section if none]\n\n### Suggestions\n\n[Nice to have — readability, minor refactoring, alternative approaches]\n\n- **`{file}:{line}`**: {detail}\n\n[Omit section if none]\n\n---\n\n[One of:]\n\n## Approved\n\nThis looks good to be merged as-is, but please do a manual sanity check and testing before merging.\n\n## Approved with Suggestions for improvement\n\nGood to go, but please carefully consider the importance of the suggestions.\n\n## Request Changes\n\nCritical and important issues must be addressed first.\n\n## Needs re-work\n\nThis is in such a bad state that the feedback of this review is not sufficient to guide improvements, the PR cannot be approved.\n```\n\n**Guidelines for the review output:**\n\n— When reporting information, be extremely concise and sacrifice grammar for sake of concision.\n\n- Only review code that was changed in the diff — pre-existing issues are out of scope. Focus on what compilers and linters cannot catch: behavioral side-effects (e.g., a changed default alters runtime behavior for consumers), architectural violations (e.g., a new dependency breaks layering), breaking changes for external consumers of the public API, and security implications. Leave type errors, missing imports, and broken references to CI.\n- Be specific — always reference file and line number\n- Explain WHY something is an issue, not just WHAT, but avoid stating the obvious.\n- For complex matters, provide concrete fix suggestions, including code snippets when helpful\n- Keep it constructive — the goal is to help, not gatekeep\n- Don't repeat the same finding for every occurrence — mention it once and note \"same pattern in {other files}\"\n- Focus on substantive issues only. Do NOT flag purely cosmetic or stylistic concerns. Specifically, never flag: code formatting or whitespace, comment grammar or wording, redundant-but-harmless syntax (e.g., optional chaining after a truthiness check), code duplication that doesn't cause bugs, or HTML template cosmetics. The only exception is when a stylistic issue has a concrete impact on performance or rendering. Note: missing JSDoc/documentation on public or exported APIs is a substantive finding (per coding preferences), not a cosmetic one — flag it as a Suggestion. A comment, doc, or branch inside a shared/generic layer that names a specific editor alias, entity type, or manifest type instead of describing the contract (root `CLAUDE.md` §2, General-Purpose by Default) is likewise substantive, not stylistic — flag it as a Suggestion.\n- For breaking changes, reference the specific pattern from the CLAUDE.md that should be applied\n- Do not suggest changes that would themselves introduce breaking changes. If a suggestion would alter public API surface (e.g., changing return types, renaming public members), it is not appropriate for a PR targeting `main` within a major version. Only suggest non-breaking alternatives.\n",".claude/skills/umb-update-openapi/SKILL.md":"---\nname: umb-update-openapi\ndescription: Regenerate the Management API OpenApi.json for Umbraco CMS from a running backend, and optionally regenerate the backoffice hey-api client so its generated types match. Proactively offer to run this at the end of any work that adds, removes, or changes Management API controllers, endpoints, routes, or their request/response/view models — OpenApi.json and the generated backend-api client go stale the moment the API surface changes, so suggest syncing rather than waiting to be asked. Also use it whenever the user asks to update/refresh/regenerate OpenApi.json or the openapi document, says the generated backend-api / hey-api client is stale or out of date, or has just added an endpoint and needs the client types — including phrasings like \"sync the OpenAPI spec\", \"update the API client\", \"the openapi doc is out of date\", or \"regenerate the server API\", even if OpenApi.json isn't named explicitly. Does NOT apply to Delivery API changes or to internal service/repository edits that don't alter the Management API surface.\n---\n\n# Update Management API OpenApi.json (Umbraco CMS)\n\nRefresh `src/Umbraco.Cms.Api.Management/OpenApi.json` **byte-for-byte** from a running backend's OpenAPI document, then optionally regenerate the backoffice hey-api client so its generated types match.\n\nThis replaces the old manual routine (run the app, open OpenAPI UI, copy JSON, paste, fix formatting). The point of automating it is that a byte-for-byte fetch from the canonical endpoint is deterministic — there's no editor reformatting to strip out, and repeated runs produce identical output.\n\n**Run from the repository root** (the directory containing `umbraco.sln`). Bash's working directory persists between calls, but `cd` can trigger a permission prompt — prefer paths relative to the repo root over `cd`.\n\n## Key facts\n\n- **OpenAPI endpoint:** `/umbraco/openapi/management.json`, served over HTTPS with a dev cert. OpenApi is only mapped when the environment is **not Production** — a backend running in Production returns 404 here even though it's healthy.\n- **Port:** default **`44339`** (the `launchSettings.json` https profile). Assume it unless the user says otherwise.\n- **Target file:** `src/Umbraco.Cms.Api.Management/OpenApi.json` — git-tracked, so git is the safety net for a bad fetch.\n- **The mechanical work is a script, not this document.** `npm run generate:openapi` fetches, validates and writes the spec; `npm run generate:server-api` then regenerates the client **from the committed file**. Prefer these over hand-rolled `curl`.\n- **Keep those two commands separate — never chain them into one.** The client generator reads the committed `OpenApi.json` and only that. Pointing it at a live server instead has been tried and removed (b8c2a3366fd): it broke base-url handling, and worse, it produced a client with no matching committed schema. Schema and client belong in the same commit.\n- **This skill exists for the parts a script can't do:** knowing _when_ to run it, managing the backend lifecycle, and reading the diff.\n\n## Procedure\n\n### 1. Confirm the target exists\n\nVerify `src/Umbraco.Cms.Api.Management/OpenApi.json` is present. If it isn't, you're likely not at the repo root — stop and say so rather than writing a file in the wrong place.\n\n### 2. Make sure a backend is running on 44339\n\n> **The fetched document reflects the _running_ build, not the working tree.** If Management API code changed since the server started, a still-running instance serves a stale spec — you'll get a byte-for-byte-valid file that's silently missing your latest endpoints. If the user changed the API, make sure the instance was (re)built/restarted after those changes; if in doubt, restart it.\n>\n> **Not every endpoint appears in the document.** Controllers marked `[ApiExplorerSettings(IgnoreApi = true)]` are excluded by design — notably the back-office _security_ endpoints (`BackOfficeController`: login, token, sign-out, authorize, keep-alive). So the absence of, say, a new `keep-alive` endpoint is expected, not a sign of a stale build. Don't use \"is my new endpoint in the doc?\" as a freshness check unless you've confirmed that controller is actually exposed to the API explorer.\n\nProbe for a live document (short timeout, accept the dev cert):\n\n```bash\nCODE=$(curl -sk --max-time 5 -o /dev/null -w \"%{http_code}\" \"https://localhost:44339/umbraco/openapi/management.json\")\n```\n\n- **`200`** → a backend is already up. Reuse it; do **not** stop it afterward (the user had it running).\n- **`000`** → nothing is listening on 44339. Start one yourself, in the background, on 44339, and remember that _you_ started it so you can stop it in step 5. No `--no-build` here on purpose, so a cold repo still works.\n- **anything else** → something is already listening on 44339 but isn't serving the expected OpenAPI document. Don't start another instance on the same port; surface the HTTP status and stop. A `404` from an otherwise healthy Umbraco is the common one — that's a backend running in Production, where OpenApi isn't mapped. Say so, and let the user decide whether to restart it in Development rather than restarting it for them.\n\nFor the `000` case, start the backend with:\n\n```bash\ndotnet run --project src/Umbraco.Web.UI --no-launch-profile -- \\\n  --environment Development --urls https://localhost:44339\n```\n\nRun that with `run_in_background: true` — that also puts the process under the harness's control so step 5 can stop it cleanly. First-run startup (build + boot) can take a couple of minutes — the fetch in step 3 waits for it.\n\n### 3. Fetch byte-for-byte straight into the file\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi -- --wait\n```\n\nThe script writes the raw response body straight into the target — that's the byte-for-byte copy — and only after it parses as JSON, so a failed run leaves `OpenApi.json` untouched rather than restoring it after the fact. `--wait` keeps retrying while nothing is listening yet, so a backend you started in step 2 is picked up the moment it's up (no `sleep` loops); against a server that was already running it returns straight away.\n\nPass a URL as the first argument if the user is on a non-default port.\n\nA non-zero exit is a failed run — report the script's own message rather than retrying. The usual causes are a backend in Production (OpenApi unmapped), a non-`Run` runtime level mid-install/upgrade, or a different app on the port.\n\n### 4. Show the diff\n\nSurface exactly what changed so the user can sanity-check it:\n\n```bash\ngit diff --stat -- src/Umbraco.Cms.Api.Management/OpenApi.json\n```\n\n- **No diff** → the spec is already up to date; say so. **This does not mean the generated client is in sync** — a previous spec change may never have been followed by a regen, so step 6 still applies.\n- **A focused diff** → summarize the changed schemas/paths at a high level.\n- **A whole-file reformat** → flag it. Byte-for-byte is intentional, but a run that rewrites the _entire_ file means the server's serialization differs from what was committed. That's the canonical format going forward and is fine — just make sure the user knows it's a formatting shift, not hundreds of real API changes.\n\n### 5. Stop the backend if you started it\n\nOnly if **you** started it in step 2 (it wasn't already running) — if the probe returned `200`, skip this step and leave the user's server alone.\n\nThe instance you launched in step 2 ran with `run_in_background: true`, so the harness owns it as a tracked background task. Stop _that task_ directly, using the background shell's own stop — not a port scan. This is fully cross-platform (no `lsof`, which isn't installed on Windows) and can only ever stop the process you started, never an unrelated one that happens to be bound to 44339.\n\n### 6. Regenerate the backoffice client to check it's in sync\n\nThe generated hey-api client is downstream of `OpenApi.json`, but it can drift **independently** of it: a previous spec change that was never followed by a regen leaves a stale client even when step 4 shows no spec diff. So don't gate this on step 4 — regenerating is the only way to know the committed client actually matches the current spec. The generator is deterministic and git-recoverable, so running it against an already-synced client is a no-op (empty diff).\n\nGet the user's agreement before running it — it rewrites generated sources and can be noisy. **Ask once, not twice.** If you got here from a proactive offer (you suggested the sync yourself at the end of some Management API work), the client regen belongs in that same offer — \"shall I sync `OpenApi.json` and the generated client?\" — and a yes covers this step. Only ask separately if you're mid-run and it genuinely hasn't come up yet:\n\n> \"Want me to regenerate the backoffice hey-api client (`npm run generate:server-api`) to confirm it's in sync with `OpenApi.json`? It's a no-op if it already matches.\"\n\nIf yes:\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\ngit diff --stat -- src/Umbraco.Web.UI.Client/src/packages/core/backend-api/\n```\n\nRead the resulting diff honestly:\n\n- **No diff** → the client is in sync; done.\n- **A diff even though step 4 showed no spec change** → the client had drifted (a prior spec update wasn't regenerated). Surface it — this is exactly the case worth catching, and the regen you just ran fixes it.\n- **A diff matching this run's spec changes** → expected; summarize it.\n\nIf the user declines, note the client's sync status is now unverified against `OpenApi.json`.\n\n## Notes\n\n- **Don't reformat the fetched JSON.** The user asked for byte-for-byte; the endpoint is the source of truth. If diffs ever look noisy from formatting alone, that's a decision to make with the user (e.g. a normalization pass) — not something to do silently.\n- **JSON-valid does not mean fresh.** A well-formed response from a pre-change build is still adopted (see step 2). When correctness matters more than speed, restart the backend so it reflects current source before fetching.\n- This skill talks to a live server and, optionally, mutates generated sources — it is not read-only, but it is safe to re-run and git-recoverable. It's idempotent when the spec hasn't changed.\n- If the port isn't reachable and starting the backend fails (build errors, DB not configured), surface the `dotnet run` output rather than retrying blindly — the fix is in the app, not the skill.\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md":"---\nname: general-add-value-summary\ndescription: Add a value summary extension so a feature's typed value can be displayed compactly in collection views (e.g., table columns). Use when a feature or property editor already has a value type and needs a renderer for collection table cells. Two variants — simple (element only, no API call) and resolver (batch API resolution needed). Prerequisite: a value type constant must already exist — use the general-add-value-type skill first if it doesn't.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Summary\n\nAdd a `valueSummary` extension so a feature's values are rendered in collection views.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Summary](../../../docs/value-summary.md)** — extension type, base classes, resolver contract, rules\n\n## Prerequisite\n\nA value type constant (`UMB_{FEATURE}_VALUE_TYPE`) must already exist and be exported from the package `index.ts`. If it doesn't exist yet, use the **`general-add-value-type`** skill first.\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type constant** — the existing constant to bind to (e.g., `UMB_COLOR_PICKER_VALUE_TYPE`)\n4. **Resolver needed?** — Yes if rendering requires an API call (resolving IDs to names). No if the raw value can be formatted directly.\n\n**If resolver is needed, also ask:**\n5. **Resolved type** — the TypeScript type after resolution (e.g., `UmbUserGroupItemModel`)\n6. **Repository to use** — which item/detail repository resolves the values\n\n---\n\n## Option A: Simple summary (no resolver)\n\nUse when the raw value can be formatted or rendered without an API call — dates, booleans, strings, color hex codes, numeric ranges, etc.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n└── {feature}-value-summary.element.ts\n```\n\n### Step 1: Create the summary element\n\nFile: `{package-path}/value-summary/{feature}-value-summary.element.ts`\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbValueSummaryElementBase } from '@umbraco-cms/backoffice/value-summary';\n\n@customElement('umb-{feature}-value-summary')\nexport class Umb{Feature}ValueSummaryElement extends UmbValueSummaryElementBase<{TValue}> {\n\toverride render() {\n\t\tif (!this._value) return html``;\n\t\treturn html`<span>${this._value}</span>`;\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryElement as element };\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'umb-{feature}-value-summary': Umb{Feature}ValueSummaryElement;\n\t}\n}\n```\n\nAdjust `render()` for the value type:\n- Dates → `this.localize.dateTime(value)`\n- Booleans → `<umb-icon>`\n- References → tags or comma-separated names\n\n### Step 2: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t},\n];\n```\n\n### Step 3: Wire manifests into parent\n\n```typescript\nimport { manifests as valueSummaryManifests } from './value-summary/manifests.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t...valueSummaryManifests,\n\t// ... other manifests\n];\n```\n\n### Simple summary checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase` and exports as `element`\n- [ ] Manifest uses `kind: 'default'`, correct `forValueType`, lazy `element` import\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Option B: Summary with resolver (batch API resolution)\n\nUse when displaying the value requires fetching data — e.g., resolving reference uniques to display names. The resolver receives all values from the entire column in one batch call.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n├── {feature}-value-summary.element.ts\n└── {feature}-value-summary.resolver.ts\n```\n\n### Step 1: Create the summary element\n\nSame pattern as Option A, Step 1. Pass `{TResolved}` as the generic instead of `{TValue}` — the element receives the resolved value in `this._value`, already correctly typed.\n\n### Step 2: Create the resolver\n\nFile: `{package-path}/value-summary/{feature}-value-summary.resolver.ts`\n\n```typescript\nimport type { UmbValueSummaryResolver } from '@umbraco-cms/backoffice/value-summary';\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\nimport { Umb{Entity}ItemRepository } from '../repository/item/{entity}-item.repository.js';\n\nexport class Umb{Feature}ValueSummaryResolver\n\textends UmbControllerBase\n\timplements UmbValueSummaryResolver<{TValue}, {TResolved}>\n{\n\t#repo = new Umb{Entity}ItemRepository(this);\n\n\tasync resolveValues(values: ReadonlyArray<{TValue}>) {\n\t\t// Deduplicate all IDs needed across the entire batch\n\t\tconst allUniques = [...new Set(values.flatMap((v) => /* extract IDs from v */))];\n\n\t\tconst { data: items, asObservable } = await this.#repo.requestItems(allUniques);\n\t\tif (!items) return { data: values.map(() => undefined as unknown as {TResolved}) };\n\n\t\t// Positional mapping — output must align with input\n\t\treturn {\n\t\t\tdata: values.map((v) => /* find resolved item(s) for v */),\n\t\t\tasObservable: asObservable\n\t\t\t\t? () => asObservable().pipe(map((latest) => values.map((v) => /* remap */)))\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryResolver as valueResolver };\n```\n\nCritical rules:\n- `data` must have the **same length** as `values` and be **positionally aligned**.\n- Export the class as `valueResolver` (named export) — the loader checks for this name.\n- Provide `asObservable` only when the resolved data can change after initial load.\n\n### Step 3: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t\tvalueResolver: () => import('./{feature}-value-summary.resolver.js'),\n\t},\n];\n```\n\n### Step 4: Wire manifests into parent\n\nSame as Option A, Step 3.\n\n### Resolver checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase`, renders resolved type, exports as `element`\n- [ ] Resolver extends `UmbControllerBase`, implements `UmbValueSummaryResolver<{TValue}, {TResolved}>`\n- [ ] `resolveValues()` returns `data` positionally aligned with `values` (same length, same order)\n- [ ] Resolver exported as `valueResolver` (named export)\n- [ ] Manifest includes `valueResolver: () => import(...)` lazy loader\n- [ ] `asObservable` included only if resolved data can change reactively\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Collection wiring\n\n**Property editors**: no extra step needed. The document collection reads `editorAlias` from the server and matches it to your `forValueType`.\n\n**Generic table collection kind**: set `valueType` in the collection manifest column:\n\n```typescript\n{ field: 'myField', label: 'My Field', valueType: UMB_{FEATURE}_VALUE_TYPE }\n```\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md":"---\nname: general-add-localization\ndescription: Add localization keys and use them in elements or controllers. Use when adding user-facing text that should be translatable — labels, descriptions, error messages, button text, status text, or any string shown in the backoffice UI.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Localization\n\nAdd translatable text to the Umbraco backoffice.\n\n## What you need from the user\n\n1. **The text to localize** — What the user sees (e.g., \"Create item\", \"Status\")\n2. **Which group it belongs to** — Feature area (e.g., `actions`, `general`, `content`, `user`)\n3. **Where it's used** — Element template, controller logic, or standalone component\n\n## Step 1: Add the key to the English dictionary\n\n**File:** `src/assets/lang/en.ts`\n\nThis file exports a `UmbLocalizationDictionary` — a nested object where top-level keys are groups and nested keys are the terms.\n\n```typescript\n// src/assets/lang/en.ts\nexport default {\n\t// ... existing groups\n\tmyFeature: {\n\t\tmyLabel: 'My Label',\n\t\tmyDescription: 'A description of the feature',\n\t\tcreateFor: (name: string) => (name ? `Create item for ${name}` : 'Create'),\n\t},\n} satisfies UmbLocalizationDictionary;\n```\n\n### Key naming rules\n\n- **Group**: camelCase feature name (e.g., `actions`, `general`, `content`, `media`, `user`)\n- **Term**: camelCase descriptive name (e.g., `assignDomain`, `auditTrail`, `browse`)\n- **Full key** used in code: `group_termName` (underscore separator)\n\n#### Choosing a group\n\nA group covers a **shared UX area**, not just the one component you're editing. Don't pick a group unilaterally — confirm it with the user:\n\n1. **If you already have a good idea of the scope** (from the surrounding code, the feature being worked on, etc.), propose it: \"This looks like it belongs to the `{scope}` scope — should that be the localization group?\"\n2. **If you don't**, ask directly: \"What is the common group name for this localization?\"\n3. **Either way, check for an existing match first.** Search the groups already in `src/assets/lang/en.ts` for one that already covers this UX area, and if you find one, ask whether it should be reused instead of creating a new group: \"`{existingGroup}` already covers this — should I use that instead?\"\n\nExamples already in this codebase:\n\n- `blockEditor` — Block List, Block Grid, and Block RTE share the same block-configuration UX\n- `contentTypeEditor` — Document Type, Media Type, and Member Type share the same editing UX\n- `codeEditor` — anything embedding the code editor\n\nOnly create a new group once the user confirms no existing one fits.\n\n#### Choosing a term\n\nA term names the **situation**, not the wording — two situations with identical English text today should still get separate terms, since the copy can diverge later.\n\nBuild it from two parts:\n\n1. **Subject**: `CreateBlock`, `ConfirmDelete`, `AddGroup`.\n2. **Presentation role**: `Title`, `Description`, `Action`, `Label`, `Notice`, `Message`, `ValidationMessage`, `Headline`, etc.\n\nCombined (subject + role suffix): `createAction`, `confirmDeleteTitle`, `addGroupDescription`.\n\nExample — three terms for one dialog in the `blockEditor` group, same subject (`confirmDeleteBlockGroup`) with different roles:\n\n```typescript\nconfirmDeleteBlockGroupTitle: 'Delete group?',\nconfirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',\nconfirmDeleteBlockGroupNotice: 'The content of these Blocks will still be present, editing of this content will no longer be available and will be shown as unsupported content.',\n```\n\nGrouping by subject keeps every piece of the dialog together, even though the wording shares nothing.\n\n### Value types\n\n| Type               | Use when                 | Example                                           |\n| ------------------ | ------------------------ | ------------------------------------------------- |\n| `string`           | Static text              | `myLabel: 'My Label'`                             |\n| `(args) => string` | Text with dynamic values | `createFor: (name: string) => \\`Create ${name}\\`` |\n\n## Step 2: Use the localized text\n\n### In element templates — `this.localize.term()`\n\nAvailable on any class extending `UmbLitElement`. The `localize` property is provided automatically.\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-element')\nexport class UmbMyElement extends UmbLitElement {\n\toverride render() {\n\t\treturn html`<uui-button label=${this.localize.term('myFeature_myLabel')}></uui-button>`;\n\t}\n}\n```\n\n### In templates — `<umb-localize>` element\n\nFor inline localized text in HTML templates:\n\n```html\n<umb-localize key=\"myFeature_myLabel\"></umb-localize>\n\n<!-- With fallback text (shown if key is missing) -->\n<umb-localize key=\"myFeature_myLabel\">Fallback text</umb-localize>\n```\n\n### In controllers or non-element classes — `UmbLocalizationController`\n\n```typescript\nimport { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';\n\nexport class UmbMyManager extends UmbControllerBase {\n\treadonly #localization = new UmbLocalizationController(this);\n\n\tsomeMethod() {\n\t\tconst label = this.#localization.term('myFeature_myLabel');\n\t}\n}\n```\n\n## Checklist\n\n- [ ] Key added to `src/assets/lang/en.ts` in the correct group\n- [ ] Key follows `group_termName` convention (camelCase, underscore separator)\n- [ ] Used `this.localize.term()` in elements or `UmbLocalizationController` in non-elements\n- [ ] No hardcoded user-facing strings remain\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md":"---\nname: general-add-value-type\ndescription: Add a new value type — a named, typed string constant that extends UmbValueTypeMap. Use when a feature needs to declare what type of value it holds so it can be referenced in collection columns or value summary manifests. Also use when a property editor needs to expose its value type for use in collection views.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Type\n\nAdd a new value type constant to `UmbValueTypeMap`.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Type](../../../docs/value-type.md)** — full reference, naming convention, rules\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type key** — the string alias:\n   - For a property editor: use the schema alias (e.g., `'Umbraco.ColorPicker'`)\n   - For a domain type: use `Umb.ValueType.{Entity}.{Shape}` (e.g., `'Umb.ValueType.UserGroup.References'`)\n4. **TypeScript type** — the type of the raw value (e.g., `string`, `boolean`, `UmbReferenceByUnique[]`)\n\n---\n\n## Step 1: Create the constant file\n\nFile: `{package-path}/value-type/constants.ts`\n\n```typescript\nexport const UMB_{FEATURE}_VALUE_TYPE = '{value-type-key}' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_{FEATURE}_VALUE_TYPE]: {TValue};\n\t}\n}\n```\n\nRules:\n- The string value is always `as const`.\n- The `declare global` block is in the **same file** as the constant.\n- One constant per file — do not group multiple value types.\n\n### Property editor example\n\n```typescript\nimport type { UmbColorPickerPropertyEditorValue } from '../types.js';\n\nexport const UMB_COLOR_PICKER_VALUE_TYPE = 'Umbraco.ColorPicker' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_COLOR_PICKER_VALUE_TYPE]: UmbColorPickerPropertyEditorValue;\n\t}\n}\n```\n\n### Domain type example\n\n```typescript\nimport type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';\n\nexport const UMB_USER_GROUP_REFERENCES_VALUE_TYPE = 'Umb.ValueType.UserGroup.References' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_USER_GROUP_REFERENCES_VALUE_TYPE]: UmbReferenceByUnique[];\n\t}\n}\n```\n\n---\n\n## Step 2: Export from the package index\n\nAdd to the package `index.ts`:\n\n```typescript\nexport * from './value-type/constants.js';\n```\n\n---\n\n## Checklist\n\n- [ ] Constant declared with `as const`\n- [ ] `declare global` block extends `UmbValueTypeMap` in the same file\n- [ ] Key follows naming convention (schema alias for property editors, `Umb.ValueType.{Entity}.{Shape}` for domain types)\n- [ ] TypeScript type is the correct raw value type\n- [ ] One constant per file\n- [ ] Constant exported from package `index.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md":"---\nname: general-create-condition\ndescription: Create a new extension condition that controls when extensions are active. Use when you need to conditionally show or hide extensions based on application state (e.g., current section, user permissions, entity state, workspace context). Conditions are registered as extensions and referenced in manifest condition arrays.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Condition\n\nCreate a new extension condition that controls when extensions are active.\n\n## What you need from the user\n\n1. **What it checks** — What context or state determines the condition (e.g., current section, user role, entity state)\n2. **Config properties** — What parameters consumers provide (e.g., `match`, `oneOf`)\n3. **Context dependency** — Which context token provides the data to evaluate\n\n## Files to create\n\nPlace in a `conditions/` subdirectory of the relevant feature:\n\n```\nmy-feature/\n└── conditions/\n    └── my-condition/\n        ├── constants.ts                     # Condition alias constant\n        ├── types.ts                         # Config type + global declaration\n        ├── my-condition.condition.ts         # Condition controller\n        └── manifests.ts                     # Condition manifest\n```\n\n## Step 1: Define the alias constant\n\n```typescript\n// constants.ts\nexport const UMB_MY_CONDITION_ALIAS = 'Umb.Condition.MyCondition';\n```\n\n## Step 2: Define the config type\n\n```typescript\n// types.ts\nimport type { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MyConditionConfig extends UmbConditionConfigBase<typeof UMB_MY_CONDITION_ALIAS> {\n\t/**\n\t * The value to match against.\n\t * @example \"Umb.Section.Content\"\n\t */\n\tmatch?: string;\n\n\t/**\n\t * One or more values to match against (any match passes).\n\t * @example [\"Umb.Section.Content\", \"Umb.Section.Media\"]\n\t */\n\toneOf?: Array<string>;\n}\n\ndeclare global {\n\tinterface UmbExtensionConditionConfigMap {\n\t\tUmbMyConditionConfig: MyConditionConfig;\n\t}\n}\n```\n\n### Key rules\n\n- Extend `UmbConditionConfigBase<typeof ALIAS_CONSTANT>` — this types the `alias` property\n- The `declare global` block on `UmbExtensionConditionConfigMap` makes the config type-safe when used in extension manifests\n- Use a unique key in the map\n- Add JSDoc with `@example` — these feed into JSON schema generation\n\n### Common config patterns\n\n| Pattern | Use when |\n|---------|----------|\n| `match: string` | Single value match |\n| `oneOf: Array<string>` | Any of multiple values |\n| `allOf: Array<string>` | All values must match |\n| `noneOf: Array<string>` | None of the values match |\n| No config properties | Condition checks a boolean state (e.g., \"is trashed\", \"is admin\") |\n\n## Step 3: Implement the condition controller\n\n```typescript\n// my-condition.condition.ts\nimport type { MyConditionConfig } from './types.js';\nimport { UMB_MY_CONTEXT } from '../../my-context.token.js';\nimport type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';\nimport type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';\nimport { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';\n\nexport class UmbMyCondition\n\textends UmbConditionBase<MyConditionConfig>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<MyConditionConfig>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_MY_CONTEXT, (context) => {\n\t\t\tthis.observe(\n\t\t\t\tcontext.someObservable,\n\t\t\t\t(value) => {\n\t\t\t\t\tthis.permitted = this.#check(value);\n\t\t\t\t},\n\t\t\t\t'_observeConditionValue',\n\t\t\t);\n\t\t});\n\t}\n\n\t#check(value: string | undefined): boolean {\n\t\tif (!value) return false;\n\n\t\tif (this.config.match) {\n\t\t\treturn value === this.config.match;\n\t\t}\n\n\t\tif (this.config.oneOf) {\n\t\t\treturn this.config.oneOf.includes(value);\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\nexport { UmbMyCondition as api };\n```\n\n### Key rules\n\n- Extend `UmbConditionBase<ConfigType>` — provides `permitted` property and `config` access\n- Implement `UmbExtensionCondition`\n- Constructor signature: `(host: UmbControllerHost, args: UmbConditionControllerArguments<ConfigType>)`\n- Set `this.permitted = true/false` — the base class notifies the extension system on change\n- Use `consumeContext()` + `observe()` for reactive conditions that update as state changes\n- Export as `api` (named export) so the class can be imported by the manifest\n- Use an explicit observer alias so re-evaluation replaces the previous subscription\n\n### For simple boolean conditions (no config)\n\n```typescript\nexport class UmbIsSomethingCondition\n\textends UmbConditionBase<UmbConditionConfigBase>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_SOME_CONTEXT, (context) => {\n\t\t\tthis.observe(context.isSomething, (value) => {\n\t\t\t\tthis.permitted = value === true;\n\t\t\t});\n\t\t});\n\t}\n}\n```\n\n## Step 4: Register the condition manifest\n\nConditions must load fast — always import the class directly (no lazy `() => import()`).\n\n```typescript\n// manifests.ts\nimport { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport { UmbMyCondition } from './my-condition.condition.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'condition',\n\t\tname: 'My Condition',\n\t\talias: UMB_MY_CONDITION_ALIAS,\n\t\tapi: UmbMyCondition,\n\t},\n];\n```\n\nAdd to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## How consumers use the condition\n\nIn any extension manifest that supports conditions:\n\n```typescript\n{\n\ttype: 'workspaceAction',\n\talias: 'My.WorkspaceAction',\n\tname: 'My Action',\n\tconditions: [\n\t\t{\n\t\t\talias: 'Umb.Condition.MyCondition',\n\t\t\tmatch: 'some-value',\n\t\t},\n\t],\n}\n```\n\nAll conditions in the array must be `permitted` for the extension to be active. They are AND-ed together.\n\n## Checklist\n\n- [ ] Alias constant exported from `constants.ts`\n- [ ] Config type extends `UmbConditionConfigBase<typeof ALIAS>` with the alias constant\n- [ ] Config type declared on global `UmbExtensionConditionConfigMap`\n- [ ] Condition class extends `UmbConditionBase<ConfigType>` and implements `UmbExtensionCondition`\n- [ ] Constructor calls `super(host, args)` and sets up context observation\n- [ ] `this.permitted` is set reactively (via `observe()`) — not just once in the constructor\n- [ ] Condition manifest registered with `type: 'condition'`\n- [ ] Manifests bubbled up to the package-level `manifests.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md":"---\nname: general-create-extension-type\ndescription: Create a new extension type for the Umbraco backoffice extension registry. Use when adding a new type of extension that can be registered via manifests (e.g., a new dashboard type, a new sidebar app type, a new toolbar extension type). Any package can define extension types.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Extension Type\n\nCreate a new extension type for the Umbraco backoffice extension registry.\n\n## What you need from the user\n\n1. **Type name** — The `type` string used in manifests (e.g., `'dashboard'`, `'searchProvider'`)\n2. **Purpose** — What this extension type does (determines which base interface to extend)\n3. **Meta shape** — What configuration the extension needs from consumers\n4. **Whether it needs conditions** — Should extensions of this type support conditional loading?\n5. **Whether it has an element, API, or both** — Does it render UI, provide logic, or both?\n\n## Files to create\n\nAll files go in the relevant feature directory (e.g., `src/packages/my-package/my-feature/`).\n\n```\nmy-feature/\n├── my-feature.extension.ts     # Type definition + global declaration\n├── my-feature-element.interface.ts  # Element interface (if element-based)\n└── types.ts                    # Meta interface (if needed)\n```\n\n## Step 1: Choose the base manifest interface\n\n| Your extension... | Extend from | Import |\n|---|---|---|\n| Renders a custom element | `ManifestElement<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Provides an API class (no UI) | `ManifestApi<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Has both element and API | `ManifestElementAndApi<E, A>` | `@umbraco-cms/backoffice/extension-api` |\n| Needs conditional loading | Also extend `ManifestWithDynamicConditions<UmbExtensionConditionConfig>` | `@umbraco-cms/backoffice/extension-api` |\n\n## Step 2: Define the manifest interface\n\n```typescript\n// my-feature.extension.ts\nimport type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MetaMyFeature {\n\tlabel?: string;\n\t// ... properties consumers configure via meta\n}\n\nexport interface ManifestMyFeature\n\textends ManifestElement<UmbMyFeatureElement>,\n\t\tManifestWithDynamicConditions<UmbExtensionConditionConfig> {\n\ttype: 'myFeature';\n\tmeta: MetaMyFeature;\n}\n\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature;\n\t}\n}\n```\n\n### Key rules\n\n- The `type` property must be a **string literal type** (not `string`)\n- The key in `UmbExtensionManifestMap` must be unique across the entire codebase\n- The `declare global` block is what makes the registry type-aware — without it, the type won't be recognized\n\n## Step 3: Define the element/API interface (if needed)\n\n```typescript\n// my-feature-element.interface.ts\nimport type { UmbElement } from '@umbraco-cms/backoffice/element-api';\n\nexport interface UmbMyFeatureElement extends UmbElement {\n\t// Properties/methods that implementations must provide\n}\n```\n\n## Step 4: If the type supports kinds\n\nAdd a union type for the base manifest plus kind-specific variants:\n\n```typescript\nexport interface ManifestMyFeatureButtonKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'button';\n\tmeta: MetaMyFeatureButtonKind;\n}\n\nexport interface MetaMyFeatureButtonKind extends MetaMyFeature {\n\ticon: string;\n}\n\n// Union all variants in the global declaration\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureButtonKind;\n\t}\n}\n```\n\n## Step 5: Export from the package barrel\n\nAdd the type export to the package's `index.ts`:\n\n```typescript\nexport type * from './my-feature/my-feature.extension.js';\n```\n\nUse `export type *` since these are type-only exports.\n\n## Step 6: Register manifests that use this type\n\nVerify the type works by creating a test manifest:\n\n```typescript\nconst manifest: UmbExtensionManifest = {\n\ttype: 'myFeature',\n\talias: 'Umb.MyFeature.Test',\n\tname: 'Test My Feature',\n\telement: () => import('./my-feature-test.element.js'),\n\tmeta: {\n\t\tlabel: 'Test',\n\t},\n};\n```\n\nTypeScript should provide autocomplete for `type` and validate `meta` against `MetaMyFeature`.\n\n## Checklist\n\n- [ ] Manifest interface extends the correct base (`ManifestElement`, `ManifestApi`, or `ManifestElementAndApi`)\n- [ ] `type` property is a string literal, not `string`\n- [ ] `declare global` block adds to `UmbExtensionManifestMap` with a unique key\n- [ ] Meta interface is defined separately and exported\n- [ ] Element/API interface is defined if the type renders UI or provides logic\n- [ ] If kinds are supported: kind-specific interfaces extend the base with literal `kind` property\n- [ ] Type is exported from the package's `index.ts`\n- [ ] Compiles: `npm run compile`\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md":"---\nname: general-create-kind\ndescription: Create a new extension kind — a reusable default implementation for an extension type. Use when multiple extensions of the same type share the same element/API implementation and only differ in configuration. Kinds reduce boilerplate by providing pre-built defaults that extensions customize via meta.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Kind\n\nCreate a new extension kind — a reusable default implementation for an extension type.\n\n## What you need from the user\n\n1. **Which extension type** — The `type` this kind applies to (e.g., `'entityAction'`, `'section'`)\n2. **Kind name** — The `kind` string (e.g., `'delete'`, `'default'`, `'button'`)\n3. **What it provides** — Default element, API, or both\n4. **Meta defaults** — What meta values the kind pre-fills for consumers\n\n## When to create a kind\n\nCreate a kind when **multiple extensions of the same type share the same element/API implementation** and only differ in meta configuration. If the implementation is unique, a kind adds unnecessary indirection.\n\n## Files to create\n\n```\nmy-feature/\n├── kinds/\n│   └── my-kind/\n│       ├── my-kind.kind.ts              # Kind manifest definition\n│       ├── my-kind.element.ts           # Default element (if providing UI)\n│       ├── my-kind.api.ts               # Default API class (if providing logic)\n│       └── manifests.ts                 # Exports the kind manifest\n```\n\n## Step 1: Define the kind manifest\n\n```typescript\n// my-kind.kind.ts\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const UMB_MY_FEATURE_MY_KIND_MANIFEST: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.MyKind',\n\tmatchType: 'myFeature',\n\tmatchKind: 'myKind',\n\tmanifest: {\n\t\ttype: 'myFeature',\n\t\tkind: 'myKind',\n\t\telement: () => import('./my-kind.element.js'),\n\t\tapi: () => import('./my-kind.api.js'),\n\t\tweight: 1000,\n\t\tmeta: {\n\t\t\ticon: '',\n\t\t\tlabel: '',\n\t\t\t// ... defaults that consumers override\n\t\t},\n\t},\n};\n\nexport const manifest = UMB_MY_FEATURE_MY_KIND_MANIFEST;\n```\n\n### Key fields\n\n- **`type`**: Always `'kind'`\n- **`alias`**: Unique identifier. Convention: `Umb.Kind.{ExtensionType}.{KindName}`\n- **`matchType`**: The extension type this kind applies to\n- **`matchKind`**: The kind name that extensions reference\n- **`manifest`**: Partial manifest with defaults — any property here becomes the base for extensions using this kind\n\n## Step 2: Create the default element (if providing UI)\n\n```typescript\n// my-kind.element.ts\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-kind-element')\nexport class UmbMyKindElement extends UmbLitElement {\n\t// This element is shared by all extensions using this kind.\n\t// Use manifest meta for customization — not hardcoded values.\n\n\toverride render() {\n\t\treturn html`<!-- default rendering -->`;\n\t}\n}\n\nexport { UmbMyKindElement as element };\n```\n\n**Important:** Export as `element` (named export) — the lazy `import()` in the kind manifest resolves this name.\n\n## Step 3: Create the default API (if providing logic)\n\n```typescript\n// my-kind.api.ts\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\n\nexport class UmbMyKindApi extends UmbControllerBase {\n\t// Shared logic for all extensions using this kind.\n\t// Access extension-specific config via the manifest meta.\n}\n\nexport { UmbMyKindApi as api };\n```\n\n## Step 4: Register the kind in manifests\n\n```typescript\n// manifests.ts\nimport { manifest as myKindManifest } from './my-kind.kind.js';\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [myKindManifest];\n```\n\nAdd this to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## Step 5: Add kind-specific type to the extension type (if not already done)\n\nIn the extension type definition file, add a variant interface for this kind:\n\n```typescript\nexport interface ManifestMyFeatureMyKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'myKind';\n\tmeta: MetaMyFeatureMyKind;\n}\n\nexport interface MetaMyFeatureMyKind extends MetaMyFeature {\n\ticon: string;\n\tlabel: string;\n}\n\n// Update the global declaration to include the kind variant\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureMyKind;\n\t}\n}\n```\n\n## How consumers use the kind\n\nExtensions reference the kind and only specify what differs:\n\n```typescript\n{\n\ttype: 'myFeature',\n\tkind: 'myKind',\n\talias: 'My.Package.MyFeature.Specific',\n\tname: 'My Specific Feature',\n\tmeta: {\n\t\ticon: 'icon-heart',\n\t\tlabel: 'My Label',\n\t},\n}\n```\n\nThe registry merges the kind defaults with the extension manifest:\n- Extension properties **override** kind properties\n- `meta` is **shallow-merged** (extension meta extends kind meta, doesn't replace it)\n\n## Extending an existing kind\n\nA kind can build on another kind by spreading its manifest:\n\n```typescript\nimport { UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST } from '../default/default.kind.js';\n\nexport const manifest: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.Special',\n\tmatchType: 'myFeature',\n\tmatchKind: 'special',\n\tmanifest: {\n\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest,\n\t\ttype: 'myFeature',\n\t\tkind: 'special',\n\t\tapi: () => import('./special.api.js'),\n\t\tmeta: {\n\t\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest.meta,\n\t\t\t// additional meta defaults\n\t\t},\n\t},\n};\n```\n\n## Checklist\n\n- [ ] Kind manifest has `type: 'kind'` with unique `alias`\n- [ ] `matchType` matches the target extension type exactly\n- [ ] `matchKind` matches the `kind` string extensions will use\n- [ ] Default element exported as `element`, default API exported as `api` (named exports for lazy import)\n- [ ] Kind-specific manifest interface added to the extension type's `declare global` block\n- [ ] Kind registered in `manifests.ts` with type `Array<UmbExtensionManifest | UmbExtensionManifestKind>`\n- [ ] Meta defaults use empty strings or sensible fallbacks (not undefined)\n- [ ] Compiles: `npm run compile`\n- [ ] Blast radius considered: changing this kind affects all extensions that use it\n","src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md":"# general-create-package\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md":"# general-create-repository\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md":"# general-create-workspace\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md)","src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md":"# general-deprecate-api\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md)","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/CLAUDE.md)",".github/copilot-instructions.md":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.github/copilot-instructions.md)",".claude/skills/umb-review/evals/evals.json":"# Evals Skill\n\nPath: `.claude/skills/umb-review/evals/evals.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/evals/evals.json)",".claude/skills/umb-review/references/breaking-changes.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/breaking-changes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/breaking-changes.md)",".claude/skills/umb-review/references/coding-preferences.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/coding-preferences.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/coding-preferences.md)",".claude/skills/umb-review/references/complexity-assessment.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/complexity-assessment.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/complexity-assessment.md)",".claude/skills/umb-review/references/gh-cli-setup.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/gh-cli-setup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/gh-cli-setup.md)",".claude/skills/umb-review/references/impact-analysis.md":"# References Skill\n\nPath: `.claude/skills/umb-review/references/impact-analysis.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/impact-analysis.md)","src/Umbraco.Web.UI.Client/src/packages/block/package.json":"# Block Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/block/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/block/package.json)","src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json":"# Clipboard Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json)","src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json":"# Code-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json)","src/Umbraco.Web.UI.Client/src/packages/content/package.json":"# Content Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/content/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/content/package.json)","src/Umbraco.Web.UI.Client/src/packages/core/package.json":"# Core Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/package.json)","src/Umbraco.Web.UI.Client/src/packages/data-type/package.json":"# Data-type Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/data-type/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/data-type/package.json)","src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json":"# Dictionary Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json)","src/Umbraco.Web.UI.Client/src/packages/documents/package.json":"# Documents Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/documents/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/documents/package.json)","src/Umbraco.Web.UI.Client/src/packages/elements/package.json":"# Elements Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/elements/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/elements/package.json)","src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json":"# Embedded-media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json)","src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json":"# Extension-insights Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json)","src/Umbraco.Web.UI.Client/src/packages/health-check/package.json":"# Health-check Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/health-check/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/health-check/package.json)","src/Umbraco.Web.UI.Client/src/packages/help/package.json":"# Help Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/help/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/help/package.json)","src/Umbraco.Web.UI.Client/src/packages/language/package.json":"# Language Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/language/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/language/package.json)","src/Umbraco.Web.UI.Client/src/packages/library/package.json":"# Library Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/library/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/library/package.json)","src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json":"# Log-viewer Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json)","src/Umbraco.Web.UI.Client/src/packages/management-api/package.json":"# Management-api Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/management-api/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json)","src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json":"# Markdown-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json)","src/Umbraco.Web.UI.Client/src/packages/media/package.json":"# Media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/media/package.json)","src/Umbraco.Web.UI.Client/src/packages/members/package.json":"# Members Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/members/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/members/package.json)","src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json":"# Models-builder Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json)","src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json":"# Multi-url-picker Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json)","src/Umbraco.Web.UI.Client/src/packages/packages/package.json":"# Packages Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/packages/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/packages/package.json)","src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json":"# Performance-profiling Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json)","src/Umbraco.Web.UI.Client/src/packages/preview/package.json":"# Preview Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/preview/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/preview/package.json)","src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json":"# Property-editors Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json)","src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json":"# Publish-cache Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json)","src/Umbraco.Web.UI.Client/src/packages/relations/package.json":"# Relations Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/relations/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/relations/package.json)","src/Umbraco.Web.UI.Client/src/packages/rte/package.json":"# Rte Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/rte/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/rte/package.json)","src/Umbraco.Web.UI.Client/src/packages/segment/package.json":"# Segment Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/segment/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/segment/package.json)","src/Umbraco.Web.UI.Client/src/packages/settings/package.json":"# Settings Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/settings/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/settings/package.json)","src/Umbraco.Web.UI.Client/src/packages/static-file/package.json":"# Static-file Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/static-file/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/static-file/package.json)","src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json":"# Sysinfo Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json)","src/Umbraco.Web.UI.Client/src/packages/tags/package.json":"# Tags Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tags/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tags/package.json)","src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json":"# Telemetry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json)","src/Umbraco.Web.UI.Client/src/packages/templating/package.json":"# Templating Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/templating/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/templating/package.json)","src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md)","src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json)","src/Umbraco.Web.UI.Client/src/packages/translation/package.json":"# Translation Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/translation/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/translation/package.json)","src/Umbraco.Web.UI.Client/src/packages/ufm/package.json":"# Ufm Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/ufm/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/ufm/package.json)","src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json":"# Umbraco-news Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json)","src/Umbraco.Web.UI.Client/src/packages/user/package.json":"# User Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/user/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/user/package.json)","src/Umbraco.Web.UI.Client/src/packages/webhook/package.json":"# Webhook Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/webhook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/webhook/package.json)","src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json":"# Icon-registry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json)","src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md":"# validation Documentation\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md)"},"items":[{"name":"SKILL.md","path":".claude/skills/umb-bump-version/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-bump-version/SKILL.md","title":"umb-bump-version","category":"anthropic-skill","format":"markdown","content":"---\nname: umb-bump-version\ndescription: Bump the Umbraco CMS version across all required files. Use when the user asks to bump, update, or set the version number — e.g., \"bump version to 17.3.4\", \"set version to 18.0.0-rc\", \"update version\". Accepts the target version as an argument.\nargument-hint: <version> (e.g., 17.3.4, 18.0.0-rc)\n---\n\n# Bump Version - Umbraco CMS\n\nUpdates the Umbraco CMS version string across all files that track it.\n\n**Do NOT use AskUserQuestion if a version argument is provided. Only ask if `$ARGUMENTS` is empty or cannot be parsed as a version.**\n\n## Arguments\n\n- `$ARGUMENTS` - Required: the target version string (e.g., `17.3.4`, `18.0.0-rc`)\n\n## Files to Update\n\nThe following 5 files must be updated with the new version:\n\n| # | File | Field |\n|---|------|-------|\n| 1 | `version.json` | `\"version\"` |\n| 2 | `src/Umbraco.Web.UI.Client/package.json` | `\"version\"` |\n| 3 | `src/Umbraco.Web.UI.Client/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n| 4 | `tests/Umbraco.Tests.AcceptanceTest/package.json` | `\"version\"` |\n| 5 | `tests/Umbraco.Tests.AcceptanceTest/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n\n**Note**: For major version bumps (e.g., 17.x to 18.x), `src/Umbraco.Web.UI.Login/package.json` has a caret-ranged dependency on `@umbraco-cms/backoffice` (e.g., `^17.2.0`) that will need manual updating. This skill does not handle that — major bumps involve many other changes beyond version strings.\n\n## Instructions\n\n### 1. Parse and Validate the Version\n\nExtract the version from `$ARGUMENTS`. It must be a valid semver-like string (e.g., `17.3.4`, `18.0.0-rc`, `17.4.0-preview.1`). If no version is provided or it cannot be parsed, ask the user for the target version.\n\n### 2. Read the Current Version\n\nRead `version.json` and extract the current `\"version\"` value. If the current version already equals the target version, report that the version is already set and stop — do not edit, stage, or commit anything.\n\nOtherwise, display both versions:\n\n```\nBumping version: {current} -> {target}\n```\n\n### 3. Update All Files\n\nUpdate each of the 5 files listed above, replacing the old version with the new version. For each file:\n\n- **`version.json`**: Replace the `\"version\"` value.\n- **`package.json` files**: Replace the `\"version\"` value (near the top of the file).\n- **`package-lock.json` files**: Replace BOTH the top-level `\"version\"` value AND the `\"version\"` inside the `\"packages\": { \"\": { ... } }` block. These are always in the first ~10 lines of the file.\n\nUse targeted edits — do NOT rewrite entire files. Be precise to avoid changing version strings in dependency entries.\n\n### 4. Verify\n\nAfter all edits, grep for the target version value across the 5 files to confirm all updates landed correctly:\n\n```bash\ngrep -n \"\\\"version\\\": \\\"{version}\\\"\" version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nExpect exactly 7 matches (one per `package.json` and `version.json`, two per `package-lock.json`).\n\n### 5. Stage and Commit\n\nStage only the 5 changed files:\n\n```bash\ngit add version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nThen commit with the message `Bump version to {version}.` — replacing `{version}` with the target version:\n\n```bash\ngit commit -m \"Bump version to {version}.\"\n```\n\n### 6. Report\n\nOutput a summary:\n\n```\nVersion bumped to {version} in:\n- version.json\n- src/Umbraco.Web.UI.Client/package.json\n- src/Umbraco.Web.UI.Client/package-lock.json\n- tests/Umbraco.Tests.AcceptanceTest/package.json\n- tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n\nChanges staged and committed.\n```\n","frontmatter":{"name":"umb-bump-version","description":"Bump the Umbraco CMS version across all required files. Use when the user asks to bump, update, or set the version number — e.g., \"bump version to 17.3.4\", \"set version to 18.0.0-rc\", \"update version\". Accepts the target version as an argument.","argument-hint":"<version> (e.g., 17.3.4, 18.0.0-rc)"},"isInternal":false,"tokens":1019,"sizeBytes":3918},{"name":"SKILL.md","path":".claude/skills/umb-release-notes/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-release-notes/SKILL.md","title":"umb-release-notes","category":"anthropic-skill","format":"markdown","content":"---\nname: umb-release-notes\ndescription: Improve a set of auto-generated GitHub release notes for an Umbraco CMS release. Cross-checks the notes against every PR carrying the release label, adds any that are missing, re-files every PR under the most appropriate category, and strips purely-internal entries. Use whenever the user asks to tidy up, improve, complete, or recategorize release notes for a given version, or mentions a release-notes text file plus a version number.\nargument-hint: <version> <path-to-generated-notes-file>\n---\n\n# Umbraco CMS - Improve Release Notes\n\nTakes a file of auto-generated GitHub release notes and produces an improved version that:\n\n1. **Is complete** — every merged PR carrying the `release/<version>` label appears.\n2. **Is well-categorized** — every PR sits under the most appropriate heading.\n3. **Is free of noise** — purely-internal entries of no value to a reader are removed.\n\nThe result is written to a **new** file alongside the input, so the user can diff the two.\n\n**Run autonomously.** Do NOT use `AskUserQuestion` once the required arguments (version and input file path) are available — only ask if one of them is missing from `$ARGUMENTS` and cannot be inferred (see Arguments). Beyond that, make the categorization calls yourself using the rules below; if a handful are genuinely borderline, place them anyway and note the borderline ones in your closing summary so the user can override.\n\n## Arguments\n\n`$ARGUMENTS` contains two values:\n\n1. **Version** — e.g. `17.5.0`, `18.1.0`. The GitHub label to search is `release/<version>` (so version `17.5.0` → label `release/17.5.0`).\n2. **Input file path** — full path to the text file holding the auto-generated notes (e.g. `C:\\Temp\\release-17.5.0-rc.md`).\n\nIf either is missing, ask the user once for the missing value, then proceed.\n\n## Prerequisites\n\nRun `gh auth status`. If it fails, tell the user to authenticate `gh` (e.g. `gh auth login`) and stop — the skill needs the GitHub CLI to query PRs. The repo is always `umbraco/Umbraco-CMS`.\n\n## Procedure\n\n### 1. Read the input notes\n\nRead the input file. Note its structure — it is GitHub's generated format:\n\n- A leading HTML comment (`<!-- Release notes generated ... -->`).\n- A `## What's Changed` heading followed by `### <emoji> <Category>` sub-headings, each with `* <title> by @<author> in <url>` bullets.\n- A trailing `## New Contributors` section and a `**Full Changelog**: ...` line.\n\nExtract the set of PR numbers already present (parse the `/pull/<number>` from each bullet). Preserve each existing bullet's **exact text** (title, author, URL) when you re-emit it — only its category placement may change.\n\n### 2. Fetch every labelled PR\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 \\\n  --json number,title,author,labels,mergedAt \\\n  --jq '.[] | select(.mergedAt != null) | \"\\(.number)\\t\\(.author.login)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"' | sort -n\n```\n\nThis is the authoritative list of what the release *should* contain. Each row gives number, author, labels, title.\n\n**Guard against silent truncation.** `gh pr list` caps at `--limit` without warning, so a large release could drop the overflow and the skill would still look \"complete\". Count the returned rows and compare against the limit:\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 --json number --jq 'length'\n```\n\nIf this equals 1000, the limit was hit — raise `--limit` and re-fetch before continuing. Do **not** proceed on a truncated list.\n\n### 3. Reconcile\n\n- **Missing labelled PRs** (labelled but not in the input file): these must be **added**. Build a bullet as `* <title> by @<author> in https://github.com/umbraco/Umbraco-CMS/pull/<number>`.\n  - **Author handle.** `<author>` in the template is the raw `.author.login` value — the bullet supplies the leading `@`, so do not prepend another. `gh`'s `.author.login` already returns bot accounts with the `[bot]` suffix as part of the login — Dependabot comes back as `dependabot[bot]`, not `dependabot` or `app/dependabot` (the `app/` form only appears in git committer metadata and CODEOWNERS, never in `gh`'s JSON). So the login is already in the right shape; use it verbatim (e.g. `.author.login` of `dependabot[bot]` renders as `@dependabot[bot]`, matching what GitHub's generator wrote for the existing bullets). The only thing to guard against is accidentally stripping or altering the `[bot]` suffix.\n- **PRs in the file but not labelled**: keep them. The generated notes span a commit range (see the `Full Changelog` compare link), so they legitimately include backports / earlier-version PRs that lack the current label. For any of these you need to categorize, fetch its labels with:\n\n  ```bash\n  gh pr view <number> --repo umbraco/Umbraco-CMS --json number,title,labels \\\n    --jq '\"\\(.number)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"'\n  ```\n\nDo **not** invent or alter the `New Contributors` section — carry it over verbatim. You cannot reliably recompute first-time contributors, so leave it as the generator produced it (mention this in the summary).\n\n### 4. Categorize every PR\n\nUse exactly these headings, in this order. Omit any heading that ends up with no entries.\n\n| Heading | What goes here | Primary signal |\n|---|---|---|\n| `### 🙌 Notable Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/notable` |\n| `### 💥 Breaking Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/breaking` |\n| `### 📦 Dependencies` | Dependency bumps | label `dependencies`; or dependabot author |\n| `### 🚀 New Features` | New user- or developer-facing capability | label `type/feature` / `category/feature`; or title introduces/adds a genuinely new capability |\n| `### 🚤 Performance` | Performance improvements | label `category/performance`; or `Performance:` title prefix |\n| `### 🌈 Accessibility Improvements` | A11y improvements (labels, contrast, keyboard) | label `category/accessibility` / `accessibility`; or clear a11y intent (e.g. \"improve contrast\", \"missing labels\") |\n| `### 🐛 Bug Fixes` | Fixes to broken/incorrect behaviour | default for anything describing a fix |\n| `### 🧪 Testing` | Test additions/changes only | label `category/test-automation` / `area/test`; or `E2E`/`QA`/\"acceptance tests\"/\"unit test coverage\"/\"add tests\" titles |\n| `### 🛡️ Code Quality, Documentation and Refactoring` | Refactors, deprecations, API tidy-ups, XML/MD documentation, knowledge-base (`MD`) updates | label `category/refactor`; or titles about refactoring, deprecating, renaming, documenting, constants extraction, MD/CLAUDE.md content |\n| `### 🧑‍💻 Developer Experience` | Things that improve the experience of developers building on or contributing to Umbraco — dev tooling, build/watch ergonomics, test mocks/harnesses, backoffice dev utilities | `Developer Experience` title prefix; dev tooling; mock/harness changes |\n\n**Rules:**\n\n- **Notable and Breaking are off-limits for recategorization** — never move a PR that is *already in the input file* into or out of these sections; they are driven purely by their labels and the generator placed them correctly. This does **not** exempt them from completeness: a PR discovered as missing in step 3 that carries `category/notable` or `category/breaking` must still be **added** under the matching section.\n- Label signals beat title wording, except a `Performance:`/`Developer Experience:` title prefix is decisive for its section.\n- A PR with both `type/feature` and `category/refactor` whose title clearly describes a refactor (e.g. \"swap relative imports\", \"re-export type\") belongs under Code Quality, not New Features.\n- \"Add ... tests\"/\"unit test coverage\" → Testing, even if it also touches docs. If a PR adds XML documentation *and* tests, lead with where the title's emphasis lies (documentation → Code Quality; test coverage → Testing).\n- When a PR is genuinely 50/50, pick the more reader-useful heading and list it in your closing summary as borderline.\n\n### 5. Remove purely-internal noise\n\nDrop entries that have **no value to anyone reading release notes** — pure repository plumbing with no shipped impact. Examples:\n\n- Branch/merge maintenance (\"Fix main branch after merge issue\").\n- CI/pipeline fixes that don't change the product.\n- Reverts of changes that never shipped in a release.\n\n**Keep** anything that ships in the product or genuinely helps developers building on Umbraco — that includes documentation/MD updates, dev tooling, and test mocks (those go to Code Quality or Developer Experience, they are *not* noise). When unsure whether something is noise, keep it and flag it in the summary rather than silently dropping it. List every removal in your closing summary.\n\n### 6. Write the output\n\nWrite to a new file in the **same folder** as the input, named by appending ` - with updates` before the extension:\n\n- Input `C:\\Temp\\release-17.5.0-rc.md` → Output `C:\\Temp\\release-17.5.0-rc - with updates.md`\n\nPreserve the leading HTML comment, the `## What's Changed` heading, the `## New Contributors` section, and the `**Full Changelog**` line exactly. Only the `### <category>` groupings and their bullets change.\n\n### 7. Report\n\nGive a concise summary:\n\n- Count of PRs added (with their numbers), and which categories they landed in.\n- Notable recategorizations (PRs moved out of the catch-all Bug Fixes into Features/Performance/Testing/etc.).\n- Every entry removed, with the one-line reason.\n- Any borderline calls the user may want to override.\n- The output file path.\n\n## Verification\n\nBefore reporting done, confirm:\n\n- Every PR number from step 2 is present in the output (except any you deliberately removed in step 5 — and those must be in the removal list).\n- No PR appears under more than one heading.\n- Notable and Breaking sections are byte-for-byte unchanged from the input.\n- The header comment, New Contributors, and Full Changelog lines are intact.\n","frontmatter":{"name":"umb-release-notes","description":"Improve a set of auto-generated GitHub release notes for an Umbraco CMS release. Cross-checks the notes against every PR carrying the release label, adds any that are missing, re-files every PR under the most appropriate category, and strips purely-internal entries. Use whenever the user asks to tidy up, improve, complete, or recategorize release notes for a given version, or mentions a release-notes text file plus a version number.","argument-hint":"<version> <path-to-generated-notes-file>"},"isInternal":false,"tokens":2488,"sizeBytes":10228},{"name":"evals.json","path":".claude/skills/umb-review/evals/evals.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/evals/evals.json","title":"Evals Skill","category":"anthropic-skill","format":"json","content":"# Evals Skill\n\nPath: `.claude/skills/umb-review/evals/evals.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/evals/evals.json)","tokens":300,"sizeBytes":198},{"name":"breaking-changes.md","path":".claude/skills/umb-review/references/breaking-changes.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/breaking-changes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/umb-review/references/breaking-changes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/breaking-changes.md)","tokens":300,"sizeBytes":231},{"name":"coding-preferences.md","path":".claude/skills/umb-review/references/coding-preferences.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/coding-preferences.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/umb-review/references/coding-preferences.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/coding-preferences.md)","tokens":300,"sizeBytes":235},{"name":"complexity-assessment.md","path":".claude/skills/umb-review/references/complexity-assessment.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/complexity-assessment.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/umb-review/references/complexity-assessment.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/complexity-assessment.md)","tokens":300,"sizeBytes":241},{"name":"gh-cli-setup.md","path":".claude/skills/umb-review/references/gh-cli-setup.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/gh-cli-setup.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/umb-review/references/gh-cli-setup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/gh-cli-setup.md)","tokens":300,"sizeBytes":223},{"name":"impact-analysis.md","path":".claude/skills/umb-review/references/impact-analysis.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/impact-analysis.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.claude/skills/umb-review/references/impact-analysis.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/impact-analysis.md)","tokens":300,"sizeBytes":229},{"name":"SKILL.md","path":".claude/skills/umb-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/SKILL.md","title":"umb-review","category":"anthropic-skill","format":"markdown","content":"---\nname: umb-review\ndescription: Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say \"review\" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.\nargument-hint: <target-branch>\n---\n\n# PR Review - Umbraco CMS\n\nAutomated, non-interactive PR code review. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality.\n\n**Do NOT use AskUserQuestion at any point. This skill runs fully autonomously.**\n\n## Arguments\n\n- `$ARGUMENTS` - Optional: target branch to diff against (auto-detected from PR, falls back to `origin/main`)\n\n## Instructions\n\n### 0. Verify GH CLI is Available\n\nRun `gh auth status`. If it fails, read `references/gh-cli-setup.md` and present the setup instructions to the user. Do not proceed with the review.\n\n### 1. Resolve Target Branch\n\nDetermine the target branch for comparison using this priority order:\n\n1. **Explicit argument**: If `$ARGUMENTS` is provided and non-empty, use it as the target branch\n2. **PR target branch**: If no argument, run `gh pr view --json baseRefName --jq '.baseRefName'` to detect the target branch of the current branch's open PR. If a PR exists, use `origin/{baseRefName}` as the target branch.\n3. **Fallback**: If no argument and no PR found (command fails or returns empty), default to `origin/main`\n\nStore the resolved target branch for use in subsequent steps. Log which resolution method was used (e.g., \"Target branch: `origin/v18/dev` (from PR #1234)\").\n\n### 2. Load Review Standards\n\n#### 2a. Load coding preferences\n\nRead the coding preferences and code review scoring criteria from:\n\n- `references/coding-preferences.md` (relative to this skill file)\n\nParse and internalize all rules, conventions, scoring categories, and severity definitions. These are your review criteria.\n\n#### 2b. Load area-specific documentation\n\nOnce the changed file list is known (after step 3a), determine which areas of the codebase are touched and load the relevant documentation. Execute this sub-step between 3a and 3b. This documentation takes precedence over sibling comparison for architectural and pattern validation.\n\n**Resolution order for each changed file:**\n\n1. **Find the nearest `CLAUDE.md`** — walk up from the changed file's directory toward the repository root. The first `CLAUDE.md` found is the area guide for that file. Read it.\n2. **Read referenced docs** — if the `CLAUDE.md` references documentation files (e.g., a `docs/` directory), use the descriptions in the `CLAUDE.md` to determine which docs are relevant to the type of code being changed, and read those. If unsure, read all referenced docs — the cost of reading is low, the cost of missing a convention is high.\n3. **Follow cross-references in loaded docs** — if a loaded doc references another doc as covering a complementary or related concern, and the changed files touch that concern, read the referenced doc too. Repeat until no new relevant cross-references remain.\n4. **Check for applicable skills** — review the available skills list. If a skill exists for the type of code being changed, read the skill file to understand the expected patterns, structure, and conventions it enforces. Do NOT invoke the skill — just use it as a reference for what the correct implementation should look like.\n\n**Store all loaded documentation** for use in step 4. These docs define the authoritative patterns and conventions that the review evaluates against.\n\n### 3. Gather Changed Files\n\n#### 3a. Collect file list, stats, and diff\n\nRun these git commands (where `{target}` is the resolved target branch):\n\n```bash\ngit diff {target}...HEAD --name-only --diff-filter=d   # changed files (excluding deleted)\ngit diff {target}...HEAD --stat                         # line counts per file\ngit log {target}...HEAD --oneline                       # commit history\ngit diff {target}...HEAD                                # full diff (primary review source)\n```\n\n**If no changes found**: Output \"No changes found between current branch and `{target}`. Nothing to review.\" and stop.\n\n#### 3b. Filter out noise files\n\nFrom the changed file list, classify each file as **noise** or **reviewable**.\n\n**Noise files** (skip entirely — do not read, do not review):\n\n| Pattern                                              | Reason                          |\n| ---------------------------------------------------- | ------------------------------- |\n| `*.gen.ts`, `*.gen.cs`                               | Auto-generated API client code  |\n| `*.generated.cs`, `*.Designer.cs` (in `Migrations/`) | Auto-generated models/snapshots |\n| `*/assets/lang/*.ts` (except `en.ts`)                | Non-English translation files   |\n| `*/mocks/data/*.ts`                                  | Test fixture data               |\n| `*/dist-cms/*`, `*/storybook-static/*`               | Build output                    |\n| `*/TEMP/InMemoryAuto/*`                              | Runtime-generated models        |\n| `package-lock.json`                                  | Dependency lock file            |\n| `appsettings-schema.*.json`                          | Generated JSON schema           |\n\nLog the skip list: \"Skipped {N} noise files: {comma-separated list of filenames}\"\n\n#### 3c. Read reviewable changed files\n\nRead the full file for every reviewable changed file.\n\n#### 3d. Track file counts\n\nKeep track of these numbers for the review output in step 7: total changed files, noise files skipped, and reviewable files read. Also record: distinct production layers touched, distinct project directories, and total lines changed — these feed step 3e.\n\n#### 3e. Assess PR complexity\n\nFollow the procedure in `references/complexity-assessment.md`. Store the triggered dimensions and suggestions for step 7.\n\n#### 3f. Classify PR scope\n\nClassify the PR to determine which review steps are relevant:\n\n| Classification  | Condition                                                                       | Effect                                                                          |\n| --------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |\n| **Gen-only**    | All reviewable files are `gen.ts`                                               | Skip steps 5 and 6; step 4 reviews impact on other code only                    |\n| **Docs-only**   | All reviewable files are `.md`                                                  | Skip steps 5 and 6; step 4 reviews intent and readability only                  |\n| **Test-only**   | All reviewable files are in `tests/`                                            | Skip steps 5 and 6; step 4 reviews intent, code quality, and test coverage only |\n| **Config-only** | All reviewable files are `.csproj`, `.props`, `.json` config, or CI/build files | Skip step 5; step 6 checks dependency version changes only                      |\n| **Standard**    | Anything else                                                                   | No skips — run all steps                                                        |\n\n### 4. Raw Code Review\n\nReview each changed file holistically. Think like a senior developer reading a colleague's PR. Note all findings without worrying about format or severity yet.\n\n#### 4a. Read and reason about each file\n\nFor each changed file, reason about: What does this code do? Is it correct? What's missing — validation, error handling, notifications, cleanup, edge cases? Could this break anything for consumers?\n\n**Unused usings (C# only):** flag a `using` directive only if this diff adds it, or removes the last code that used it — pre-existing unused usings are out of scope. Only flag when confident: a namespace can be used without appearing by name (extension methods, attributes, `<see cref>`, LINQ query syntax, `using static`, aliases, `#if` blocks). Report them as one Suggestion listing `file: namespace` pairs, per root `CLAUDE.md` §11; this is the one stylistic finding that is allowed.\n\n#### 4b. Validate against documentation and patterns\n\nUse a **docs-first** approach: classify the code by what it does, check it against documented conventions, and only fall back to sibling comparison when docs don't cover the pattern.\n\n**Step 1 — Determine the correct approach from documentation, then check whether the PR matches**\n\nA PR is a proposed solution, not the source of truth. This step has two parts that must happen in order — do not start part B until part A is complete.\n\n**Part A — Before validating/judging the implementation**, determine what the correct approach is for each new class or file based on what it does. Use the documentation loaded in step 2b to identify the expected base classes, patterns, and conventions. Write down the expected approach. Classify based on what the code does, not based on what neighboring files look like.\n\n**Part B — Now compare the PR's implementation** against the expected approach from Part A. If it deviates from the documented approach, flag it. If the documentation specifies reference examples, read those examples to verify the implementation matches.\n\n**Pattern match is the leading finding.** If the documentation defines a pattern that fits what the code does, the first and most important finding is whether the code follows that pattern.\n\n**Step 2 — Fall back to sibling comparison**\n\nIf the documentation does not cover the specific pattern, or for cross-cutting concerns not addressed in docs, fall back to sibling comparison:\n\n1. **New method on existing class/interface**: Grep for the most similar existing method on the same class using `-A 80` to capture the full method body (e.g., `UpdateCurrentUserAsync` → grep for `UpdateAsync` in the same file with `-A 80`). Compare line by line for missing cross-cutting concerns: notifications/events, validation, scoping, authorization, error handling, audit logging.\n2. **New TS class**: Grep for siblings by base class (`extends {BaseClass}`) or by interface (`implements {Interface}`) or by name suffix (e.g., `CurrentUserController` → grep for `UserController`). Compare for missing concerns.\n3. **New CS class**: Grep for siblings by base class (`class {ClassName} : {BaseClass}`) or by interface (`class {ClassName} : {Interface}`) or by name suffix (e.g., `ManagementApiComposer` → grep for `ApiComposer`). Compare for missing concerns.\n\n**Important:** Sibling comparison validates cross-cutting concerns, but it must not override documented conventions. If a sibling deviates from documented patterns, that sibling is wrong — do not copy its deviation.\n\nStore your raw findings — they feed into step 7.\n\n### 5. Impact Analysis\n\n**Skip this step if PR scope is docs-only, test-only, or config-only.**\n\nFollow the procedure in `references/impact-analysis.md`.\n\n### 6. Breaking Changes Check\n\n**Skip this step if PR scope is docs-only or test-only. If config-only, only check for dependency version changes that could break consumers.**\n\nFollow the procedure in `references/breaking-changes.md`.\n\n### 7. Consolidate and Output Review\n\nMerge findings from step 4 (raw review), step 5 (impact analysis), and step 6 (breaking changes). For each finding, assign severity (Critical/Important/Suggestion) and verify it relates to changed code — not pre-existing issues. Before outputting, drop any finding about whitespace, blank lines, formatting, or comment wording — but keep findings about comments or code pitched at the wrong altitude (see the review output guidelines below). Then present the review in this exact format:\n\n```markdown\n## PR Review\n\n**Target:** `{target_branch}` · **Based on commit:** `{head_sha}`\n[If any skipped files, append: · **Skipped:** {skipped} files out of {total} total]\n[If step 3f classification is not \"Standard\", append: · **Classified as:** {classification}]\n\n[1–2 sentences: what this PR accomplishes , keep it as short as possible, only highlight the primary essence.]\n\n- **Modified public API:** {changed existing interfaces/types/classes/methods}\n  [Omit bullet if none]\n- **Affected implementations (outside this PR):** {interfaces/types/classes/methods using modified public API}\n  [Omit bullet if none]\n- **Breaking changes:** {violations with specifics}\n  [Omit bullet if none]\n- **Other changes:** {changes not listed above that an Umbraco user, plugin developer, or API consumer would notice — e.g., behavior changes, default value changes, error message changes, new configuration options, removed functionality. Exclude internal renames, formatting, and private implementation details.}\n  [Omit bullet if none]\n\n[If step 3e triggered any dimensions, insert this block. Omit entirely if nothing triggered:]\n\n> [!NOTE]\n> **Complexity advisory** — This PR may benefit from splitting.\n>\n> - **{Dimension}:** {Explanation and concrete split suggestion from step 3e}\n>   [one bullet per triggered dimension]\n>\n> _This is an observation, not a blocker. The full review follows below._\n\n---\n\n### Critical\n\n[Must fix before merge — security vulnerabilities, data loss, broken functionality, breaking changes without proper patterns]\n\n- **`{file}:{line}`**: {problem} → {fix}\n\n[Omit section if none]\n\n### Important\n\n[Should fix — performance issues, missing tests, architectural violations, pattern misuse]\n\n- **`{file}:{line}`**: {observation} → {suggestion}\n\n[Omit section if none]\n\n### Suggestions\n\n[Nice to have — readability, minor refactoring, alternative approaches]\n\n- **`{file}:{line}`**: {detail}\n\n[Omit section if none]\n\n---\n\n[One of:]\n\n## Approved\n\nThis looks good to be merged as-is, but please do a manual sanity check and testing before merging.\n\n## Approved with Suggestions for improvement\n\nGood to go, but please carefully consider the importance of the suggestions.\n\n## Request Changes\n\nCritical and important issues must be addressed first.\n\n## Needs re-work\n\nThis is in such a bad state that the feedback of this review is not sufficient to guide improvements, the PR cannot be approved.\n```\n\n**Guidelines for the review output:**\n\n— When reporting information, be extremely concise and sacrifice grammar for sake of concision.\n\n- Only review code that was changed in the diff — pre-existing issues are out of scope. Focus on what compilers and linters cannot catch: behavioral side-effects (e.g., a changed default alters runtime behavior for consumers), architectural violations (e.g., a new dependency breaks layering), breaking changes for external consumers of the public API, and security implications. Leave type errors, missing imports, and broken references to CI.\n- Be specific — always reference file and line number\n- Explain WHY something is an issue, not just WHAT, but avoid stating the obvious.\n- For complex matters, provide concrete fix suggestions, including code snippets when helpful\n- Keep it constructive — the goal is to help, not gatekeep\n- Don't repeat the same finding for every occurrence — mention it once and note \"same pattern in {other files}\"\n- Focus on substantive issues only. Do NOT flag purely cosmetic or stylistic concerns. Specifically, never flag: code formatting or whitespace, comment grammar or wording, redundant-but-harmless syntax (e.g., optional chaining after a truthiness check), code duplication that doesn't cause bugs, or HTML template cosmetics. The only exception is when a stylistic issue has a concrete impact on performance or rendering. Note: missing JSDoc/documentation on public or exported APIs is a substantive finding (per coding preferences), not a cosmetic one — flag it as a Suggestion. A comment, doc, or branch inside a shared/generic layer that names a specific editor alias, entity type, or manifest type instead of describing the contract (root `CLAUDE.md` §2, General-Purpose by Default) is likewise substantive, not stylistic — flag it as a Suggestion.\n- For breaking changes, reference the specific pattern from the CLAUDE.md that should be applied\n- Do not suggest changes that would themselves introduce breaking changes. If a suggestion would alter public API surface (e.g., changing return types, renaming public members), it is not appropriate for a PR targeting `main` within a major version. Only suggest non-breaking alternatives.\n","frontmatter":{"name":"umb-review","description":"Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say \"review\" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.","argument-hint":"<target-branch>"},"isInternal":false,"tokens":3498,"sizeBytes":16763},{"name":"SKILL.md","path":".claude/skills/umb-update-openapi/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-update-openapi/SKILL.md","title":"umb-update-openapi","category":"anthropic-skill","format":"markdown","content":"---\nname: umb-update-openapi\ndescription: Regenerate the Management API OpenApi.json for Umbraco CMS from a running backend, and optionally regenerate the backoffice hey-api client so its generated types match. Proactively offer to run this at the end of any work that adds, removes, or changes Management API controllers, endpoints, routes, or their request/response/view models — OpenApi.json and the generated backend-api client go stale the moment the API surface changes, so suggest syncing rather than waiting to be asked. Also use it whenever the user asks to update/refresh/regenerate OpenApi.json or the openapi document, says the generated backend-api / hey-api client is stale or out of date, or has just added an endpoint and needs the client types — including phrasings like \"sync the OpenAPI spec\", \"update the API client\", \"the openapi doc is out of date\", or \"regenerate the server API\", even if OpenApi.json isn't named explicitly. Does NOT apply to Delivery API changes or to internal service/repository edits that don't alter the Management API surface.\n---\n\n# Update Management API OpenApi.json (Umbraco CMS)\n\nRefresh `src/Umbraco.Cms.Api.Management/OpenApi.json` **byte-for-byte** from a running backend's OpenAPI document, then optionally regenerate the backoffice hey-api client so its generated types match.\n\nThis replaces the old manual routine (run the app, open OpenAPI UI, copy JSON, paste, fix formatting). The point of automating it is that a byte-for-byte fetch from the canonical endpoint is deterministic — there's no editor reformatting to strip out, and repeated runs produce identical output.\n\n**Run from the repository root** (the directory containing `umbraco.sln`). Bash's working directory persists between calls, but `cd` can trigger a permission prompt — prefer paths relative to the repo root over `cd`.\n\n## Key facts\n\n- **OpenAPI endpoint:** `/umbraco/openapi/management.json`, served over HTTPS with a dev cert. OpenApi is only mapped when the environment is **not Production** — a backend running in Production returns 404 here even though it's healthy.\n- **Port:** default **`44339`** (the `launchSettings.json` https profile). Assume it unless the user says otherwise.\n- **Target file:** `src/Umbraco.Cms.Api.Management/OpenApi.json` — git-tracked, so git is the safety net for a bad fetch.\n- **The mechanical work is a script, not this document.** `npm run generate:openapi` fetches, validates and writes the spec; `npm run generate:server-api` then regenerates the client **from the committed file**. Prefer these over hand-rolled `curl`.\n- **Keep those two commands separate — never chain them into one.** The client generator reads the committed `OpenApi.json` and only that. Pointing it at a live server instead has been tried and removed (b8c2a3366fd): it broke base-url handling, and worse, it produced a client with no matching committed schema. Schema and client belong in the same commit.\n- **This skill exists for the parts a script can't do:** knowing _when_ to run it, managing the backend lifecycle, and reading the diff.\n\n## Procedure\n\n### 1. Confirm the target exists\n\nVerify `src/Umbraco.Cms.Api.Management/OpenApi.json` is present. If it isn't, you're likely not at the repo root — stop and say so rather than writing a file in the wrong place.\n\n### 2. Make sure a backend is running on 44339\n\n> **The fetched document reflects the _running_ build, not the working tree.** If Management API code changed since the server started, a still-running instance serves a stale spec — you'll get a byte-for-byte-valid file that's silently missing your latest endpoints. If the user changed the API, make sure the instance was (re)built/restarted after those changes; if in doubt, restart it.\n>\n> **Not every endpoint appears in the document.** Controllers marked `[ApiExplorerSettings(IgnoreApi = true)]` are excluded by design — notably the back-office _security_ endpoints (`BackOfficeController`: login, token, sign-out, authorize, keep-alive). So the absence of, say, a new `keep-alive` endpoint is expected, not a sign of a stale build. Don't use \"is my new endpoint in the doc?\" as a freshness check unless you've confirmed that controller is actually exposed to the API explorer.\n\nProbe for a live document (short timeout, accept the dev cert):\n\n```bash\nCODE=$(curl -sk --max-time 5 -o /dev/null -w \"%{http_code}\" \"https://localhost:44339/umbraco/openapi/management.json\")\n```\n\n- **`200`** → a backend is already up. Reuse it; do **not** stop it afterward (the user had it running).\n- **`000`** → nothing is listening on 44339. Start one yourself, in the background, on 44339, and remember that _you_ started it so you can stop it in step 5. No `--no-build` here on purpose, so a cold repo still works.\n- **anything else** → something is already listening on 44339 but isn't serving the expected OpenAPI document. Don't start another instance on the same port; surface the HTTP status and stop. A `404` from an otherwise healthy Umbraco is the common one — that's a backend running in Production, where OpenApi isn't mapped. Say so, and let the user decide whether to restart it in Development rather than restarting it for them.\n\nFor the `000` case, start the backend with:\n\n```bash\ndotnet run --project src/Umbraco.Web.UI --no-launch-profile -- \\\n  --environment Development --urls https://localhost:44339\n```\n\nRun that with `run_in_background: true` — that also puts the process under the harness's control so step 5 can stop it cleanly. First-run startup (build + boot) can take a couple of minutes — the fetch in step 3 waits for it.\n\n### 3. Fetch byte-for-byte straight into the file\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi -- --wait\n```\n\nThe script writes the raw response body straight into the target — that's the byte-for-byte copy — and only after it parses as JSON, so a failed run leaves `OpenApi.json` untouched rather than restoring it after the fact. `--wait` keeps retrying while nothing is listening yet, so a backend you started in step 2 is picked up the moment it's up (no `sleep` loops); against a server that was already running it returns straight away.\n\nPass a URL as the first argument if the user is on a non-default port.\n\nA non-zero exit is a failed run — report the script's own message rather than retrying. The usual causes are a backend in Production (OpenApi unmapped), a non-`Run` runtime level mid-install/upgrade, or a different app on the port.\n\n### 4. Show the diff\n\nSurface exactly what changed so the user can sanity-check it:\n\n```bash\ngit diff --stat -- src/Umbraco.Cms.Api.Management/OpenApi.json\n```\n\n- **No diff** → the spec is already up to date; say so. **This does not mean the generated client is in sync** — a previous spec change may never have been followed by a regen, so step 6 still applies.\n- **A focused diff** → summarize the changed schemas/paths at a high level.\n- **A whole-file reformat** → flag it. Byte-for-byte is intentional, but a run that rewrites the _entire_ file means the server's serialization differs from what was committed. That's the canonical format going forward and is fine — just make sure the user knows it's a formatting shift, not hundreds of real API changes.\n\n### 5. Stop the backend if you started it\n\nOnly if **you** started it in step 2 (it wasn't already running) — if the probe returned `200`, skip this step and leave the user's server alone.\n\nThe instance you launched in step 2 ran with `run_in_background: true`, so the harness owns it as a tracked background task. Stop _that task_ directly, using the background shell's own stop — not a port scan. This is fully cross-platform (no `lsof`, which isn't installed on Windows) and can only ever stop the process you started, never an unrelated one that happens to be bound to 44339.\n\n### 6. Regenerate the backoffice client to check it's in sync\n\nThe generated hey-api client is downstream of `OpenApi.json`, but it can drift **independently** of it: a previous spec change that was never followed by a regen leaves a stale client even when step 4 shows no spec diff. So don't gate this on step 4 — regenerating is the only way to know the committed client actually matches the current spec. The generator is deterministic and git-recoverable, so running it against an already-synced client is a no-op (empty diff).\n\nGet the user's agreement before running it — it rewrites generated sources and can be noisy. **Ask once, not twice.** If you got here from a proactive offer (you suggested the sync yourself at the end of some Management API work), the client regen belongs in that same offer — \"shall I sync `OpenApi.json` and the generated client?\" — and a yes covers this step. Only ask separately if you're mid-run and it genuinely hasn't come up yet:\n\n> \"Want me to regenerate the backoffice hey-api client (`npm run generate:server-api`) to confirm it's in sync with `OpenApi.json`? It's a no-op if it already matches.\"\n\nIf yes:\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\ngit diff --stat -- src/Umbraco.Web.UI.Client/src/packages/core/backend-api/\n```\n\nRead the resulting diff honestly:\n\n- **No diff** → the client is in sync; done.\n- **A diff even though step 4 showed no spec change** → the client had drifted (a prior spec update wasn't regenerated). Surface it — this is exactly the case worth catching, and the regen you just ran fixes it.\n- **A diff matching this run's spec changes** → expected; summarize it.\n\nIf the user declines, note the client's sync status is now unverified against `OpenApi.json`.\n\n## Notes\n\n- **Don't reformat the fetched JSON.** The user asked for byte-for-byte; the endpoint is the source of truth. If diffs ever look noisy from formatting alone, that's a decision to make with the user (e.g. a normalization pass) — not something to do silently.\n- **JSON-valid does not mean fresh.** A well-formed response from a pre-change build is still adopted (see step 2). When correctness matters more than speed, restart the backend so it reflects current source before fetching.\n- This skill talks to a live server and, optionally, mutates generated sources — it is not read-only, but it is safe to re-run and git-recoverable. It's idempotent when the spec hasn't changed.\n- If the port isn't reachable and starting the backend fails (build errors, DB not configured), surface the `dotnet run` output rather than retrying blindly — the fix is in the app, not the skill.\n","frontmatter":{"name":"umb-update-openapi","description":"Regenerate the Management API OpenApi.json for Umbraco CMS from a running backend, and optionally regenerate the backoffice hey-api client so its generated types match. Proactively offer to run this at the end of any work that adds, removes, or changes Management API controllers, endpoints, routes, or their request/response/view models — OpenApi.json and the generated backend-api client go stale the moment the API surface changes, so suggest syncing rather than waiting to be asked. Also use it whenever the user asks to update/refresh/regenerate OpenApi.json or the openapi document, says the generated backend-api / hey-api client is stale or out of date, or has just added an endpoint and needs the client types — including phrasings like \"sync the OpenAPI spec\", \"update the API client\", \"the openapi doc is out of date\", or \"regenerate the server API\", even if OpenApi.json isn't named explicitly. Does NOT apply to Delivery API changes or to internal service/repository edits that don't alter the Management API surface."},"isInternal":false,"tokens":2433,"sizeBytes":10542},{"name":"SKILL.md","path":".claude/skills/umb-update-server-dependencies-for-minor/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-update-server-dependencies-for-minor/SKILL.md","title":"umb-update-server-dependencies-for-minor","category":"anthropic-skill","format":"markdown","content":"---\nname: umb-update-server-dependencies-for-minor\ndescription: Update all server-side (NuGet) dependencies to their latest minor/patch versions for the current branch's version line, then open a PR. Runs dotnet-outdated, bumps minors and patches only (never majors; never moves a stable package onto a pre-release), syncs the template projects, builds and runs unit tests, commits, and creates a PR. Use when asked to update/bump backend/server/NuGet/.NET dependencies for a release (e.g. \"update server dependencies for 17.6\", \"bump NuGet packages to latest minors\").\nargument-hint: [base-branch] (optional; defaults to the current branch)\n---\n\n# Update Server-Side Dependencies (minor & patch) - Umbraco CMS\n\nBumps every NuGet dependency that is behind to its latest available **minor or patch** version for a single version line (the current branch), then builds, tests, commits and opens a PR.\n\n**Scope: one version line at a time.** This skill operates on the branch that is currently checked out (or the branch given as `$ARGUMENTS`). To update multiple release lines (e.g. `v17/dev` and `main`), run it once per line.\n\n**Guardrails (non-negotiable):**\n- **Never** update a major version. Only minors and patches where a newer one exists.\n- **Never** move a stable package onto a pre-release. Packages already on a pre-release *may* be updated to a newer pre-release — this is what `--pre-release Auto` does.\n- **Never** update packages explicitly held back for compatibility (e.g. marked \"HOLD\"/\"do not bump\" in `Directory.Packages.props`). `TODO` comments there may also mark transitive security pins, which are in-scope to bump if needed (see step 3).\n\n## Prerequisites\n\n- `dotnet-outdated-tool` installed globally: `dotnet tool install --global dotnet-outdated-tool` (verify with `dotnet outdated --version`).\n- `gh` CLI authenticated (`gh auth status`).\n\n## Where versions live\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production packages + `GlobalPackageReference` build tooling + transitive security pins |\n| `tests/Directory.Packages.props` | Test-only packages (inherits the root file) |\n| `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` | Inline versions (opts out of central management) |\n| `templates/UmbracoExtension/Umbraco.Extension.csproj` and its `Directory.Packages.props` | **Not in `umbraco.sln`** — dotnet-outdated will NOT touch these; sync them by hand (step 4) |\n\n## Instructions\n\n### 1. Establish the branch\n\nRead `version.json` and take the major version (e.g. `18` from `18.1.0-rc`) — call it `{major}`.\n\nIf the current branch is a long-lived line (`main`, `v{major}/dev`, a release branch) rather than a task branch, create and switch to a new branch:\n\n```bash\ngit switch -c v{major}/task/update-backend-dependencies\n```\n\n(If `$ARGUMENTS` names a base branch, branch from that instead.) If you are already on a suitable task branch, stay on it. Confirm the working tree is clean before starting.\n\n### 2. Discover what is outdated\n\nRun dotnet-outdated restricted to within the current major. `--version-lock Major` keeps updates inside the current major version; `--pre-release Auto` (the default) offers a pre-release upgrade **only when the package is already on a pre-release** — so a stable package never jumps to a pre-release, while an existing pre-release can move to a newer pre-release:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto umbraco.sln\n```\n\nReview the report. Everything listed is a legitimate minor/patch (or same-track pre-release) candidate. Note anything you intend to skip, and verify afterwards (via `git diff`) that no intentionally pinned/held versions were changed; revert any that were.\n\n### 3. Apply the updates\n\nApply the same filter with `-u`:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto -u umbraco.sln\n```\n\n`dotnet-outdated` restores per-package and may print `Failed to restore project after upgrading!` for the test projects — this is a known quirk with the multi-level `tests/Directory.Packages.props` merge; the version writes still land. Verify with `git diff` afterwards.\n\nThis updates the two `Directory.Packages.props` files, the inline `Umbraco.Web.UI.csproj` versions, and any in-solution project. It also handles `GlobalPackageReference` entries (e.g. `Nerdbank.GitVersioning`).\n\nYou can also update transitive **security pins** if a new minor or patch is available.\n\n### 4. Sync the template projects by hand\n\n`templates/UmbracoExtension/` is not part of `umbraco.sln`, so its versions are not updated automatically. For any package that (a) was bumped in the root `Directory.Packages.props` **and** (b) appears in the template, update it to match:\n\n```bash\ngrep -rnE \"Version=\\\"[0-9]\" templates/UmbracoExtension/\n```\n\nTypically this is `Microsoft.AspNetCore.OpenApi` and/or `Swashbuckle.AspNetCore*` in both `templates/UmbracoExtension/Umbraco.Extension.csproj` and `templates/UmbracoExtension/Directory.Packages.props`. Keep them aligned with the root file.\n\n### 5. Build and run unit tests\n\nBuild the full solution in Release, skipping the frontend/npm steps (these are gated for CI/non-VS builds):\n\n```bash\ndotnet build umbraco.sln -c Release -p:UmbracoBuild=true\n```\n\nThen run the unit test suite:\n\n```bash\ndotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj -c Release -p:UmbracoBuild=true --no-build\n```\n\nBoth build and tests must be green before continuing.\n\nConfirm the bumps did not leave (or introduce) a known-vulnerable transitive version:\n\n```bash\ndotnet list umbraco.sln package --vulnerable --include-transitive\n```\n\nPre-existing, unrelated advisories are out of scope for a routine bump — but if a bump you made is the cause, resolve it (raise the pin, or add an inline reference for `Umbraco.Web.UI`).\n\n### 6. Review and commit\n\nConfirm `git status` shows only the intended package files (the two `Directory.Packages.props`, `Umbraco.Web.UI.csproj`, and any template files) — no stray build output, logs, or the schema placeholder. Stage those files explicitly (do not `git add -A`) and commit:\n\n```\nUpdate NuGet packages to latest minor and patch versions\n\nBump all NuGet dependencies that were behind to their latest available\nminor or patch release, as reported by dotnet-outdated. No major-version\nupdates are included.\n```\n\n### 7. Push and open the PR\n\n```bash\ngit push -u origin <branch>\n```\n\nGenerate the PR body from the actual `git diff` of the package files (do not invent versions). Use tables grouping the changes, and keep the **Testing** section to a single line — CI is the source of truth, do not paste build/test counts.\n\nFill in the template below and **write it to a temp file** (e.g. `/tmp/pr-body.md`), then create the PR against the line's dev branch (`v{major}/dev` for a v-line, `main` for the current major):\n\n```bash\ngh pr create --base <dev-branch> --head <branch> \\\n  --title \"Dependencies: Update NuGet packages to latest minor and patch versions\" \\\n  --body-file /tmp/pr-body.md\n```\n\n**PR body template:**\n\n```markdown\n## Description\n\nRoutine dependency maintenance for the **{version}** release. Updates all NuGet packages that were behind to their latest available **minor or patch** version, as reported by `dotnet-outdated`. No major-version updates are included.\n\n### Production packages (`Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Test packages (`tests/Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Inline / template versions\n\n- `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj`: …\n- `templates/UmbracoExtension`: …\n\n### Deliberately left unchanged\n\n- Any package where only a **major** update is available (out of scope for this PR).\n- Intentionally-held packages carrying a `HOLD`/`do not bump` comment (e.g. `Umbraco.Code`).\n\n## Testing\n\nSolution should build and CI checks pass.\n```\n\n### 8. Report\n\nOutput the PR URL, the branch name, and a short summary of what was bumped (and anything deliberately skipped or any pin that had to be raised).\n\n## Notes\n\n- If `dotnet-outdated` reports nothing outdated, say so and stop — do not create an empty PR.\n- No `OpenApi.json` regeneration is needed for pure dependency bumps (no Management API controllers/models change).\n","isInternal":false,"tokens":2016,"sizeBytes":8298},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md","title":"core-add-module","category":"anthropic-skill","format":"markdown","content":"---\nname: core-add-module\ndescription: Add a new module to the core package (src/packages/core/). Use when adding shared UI framework infrastructure that all packages can use — e.g., a new extension system feature, a new utility, a new shared component pattern, a new picker, or a new workspace primitive. Core modules are imported as @umbraco-cms/backoffice/{module-name}. Also use when the user says things like \"add a module to core\", \"create a new core feature\", or \"add shared infrastructure\".\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Core Module\n\nAdd a new module to the core package (`src/packages/core/`).\n\n## Naming conventions\n\n- **Module names** are **singular** (e.g., `picker`, `sorter`, `entity-flag`, `modal`, `section`)\n\n## What you need from the user\n\n1. **Module name** — What to call it in singular form (kebab-case, e.g., `picker`, `sorter`, `entity-flag`)\n2. **Purpose** — What shared infrastructure this provides\n\n## When to add to core vs. create a new package\n\n- **Core module**: Shared UI framework infrastructure used by multiple packages — extension primitives, pickers, validation, repository base classes, workspace utilities.\n- **New package**: Domain-specific CMS features (documents, media, webhook). Use the `create-package` skill instead.\n\nCore modules should not implement CMS-specific features. They provide the building blocks that CMS feature packages use.\n\n## Files to create\n\n```\nsrc/packages/core/{module-name}/\n├── index.ts                   # Public API exports\n├── manifests.ts               # Extension registrations (if any)\n├── constants.ts               # Module constants\n└── types.ts                   # Module types\n```\n\n## Step 1: Create the module directory and files\n\n### {module-name}/index.ts\n\nThe public API. Only export what other packages need:\n\n```typescript\nexport * from './constants.js';\nexport type * from './types.js';\n```\n\nUse `export type *` for type-only exports.\n\n### {module-name}/manifests.ts\n\nExtension registrations for this module:\n\n```typescript\nexport const manifests: Array<UmbExtensionManifest> = [\n\t// Extension registrations\n];\n```\n\nIf the module has no extensions (pure utility), export an empty array.\n\n### {module-name}/constants.ts\n\n```typescript\n// Aliases, context tokens, entity types\n```\n\n### {module-name}/types.ts\n\n```typescript\n// Type definitions, interfaces, meta types\n```\n\n## Step 2: Wire into core's manifests.ts\n\nAdd the module's manifests to `src/packages/core/manifests.ts`:\n\n```typescript\nimport { manifests as {moduleName}Manifests } from './{module-name}/manifests.js';\n\n// In the manifests array:\n...{moduleName}Manifests,\n```\n\nThe imports are in alphabetical order — insert in the right position.\n\n## Step 3: Add entry point to core's vite.config.ts\n\nAdd the module to the `entry` object in `src/packages/core/vite.config.ts`:\n\n```typescript\n'{module-name}/index': './{module-name}/index.ts',\n```\n\nThe entries are in alphabetical order — insert in the right position. This tells Vite to build the module as a separate entry point.\n\n## Step 4: Register the subpath export\n\nAdd to the `exports` field in the root `package.json` (`src/Umbraco.Web.UI.Client/package.json`):\n\n```json\n\"./{module-name}\": \"./dist-cms/packages/core/{module-name}/index.js\"\n```\n\nThis enables imports like `@umbraco-cms/backoffice/{module-name}`.\n\n## Step 5: Regenerate TypeScript config\n\n```bash\nnpm run generate:tsconfig\n```\n\nThis updates `tsconfig.json` paths so the new module resolves correctly in TypeScript.\n\n## How consumers import\n\nAfter registration, other packages import from the module like:\n\n```typescript\nimport { MyThing } from '@umbraco-cms/backoffice/{module-name}';\nimport type { MyType } from '@umbraco-cms/backoffice/{module-name}';\n```\n\n## Reference: existing core modules to study\n\n- **Simple utility**: `src/packages/core/culture/` — repository + components, minimal surface\n- **Extension primitive**: `src/packages/core/entity-action/` — kinds, base classes, shared element\n- **Infrastructure**: `src/packages/core/repository/` — base classes for the data access pattern\n\n## Checklist\n\n- [ ] Module directory created under `src/packages/core/`\n- [ ] `index.ts` exports only the public API\n- [ ] `manifests.ts` exports extension registrations (or empty array)\n- [ ] `constants.ts` and `types.ts` created\n- [ ] Module manifests imported in `src/packages/core/manifests.ts` (alphabetical order)\n- [ ] Entry point added to `src/packages/core/vite.config.ts` (alphabetical order)\n- [ ] Subpath export added to root `package.json`\n- [ ] `npm run generate:tsconfig` executed\n- [ ] Compiles: `npm run compile`\n","frontmatter":{"name":"core-add-module","description":"Add a new module to the core package (src/packages/core/). Use when adding shared UI framework infrastructure that all packages can use — e.g., a new extension system feature, a new utility, a new shared component pattern, a new picker, or a new workspace primitive. Core modules are imported as @umbraco-cms/backoffice/{module-name}. Also use when the user says things like \"add a module to core\", \"create a new core feature\", or \"add shared infrastructure\".","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":1083,"sizeBytes":4661},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md","title":"general-add-localization","category":"anthropic-skill","format":"markdown","content":"---\nname: general-add-localization\ndescription: Add localization keys and use them in elements or controllers. Use when adding user-facing text that should be translatable — labels, descriptions, error messages, button text, status text, or any string shown in the backoffice UI.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Localization\n\nAdd translatable text to the Umbraco backoffice.\n\n## What you need from the user\n\n1. **The text to localize** — What the user sees (e.g., \"Create item\", \"Status\")\n2. **Which group it belongs to** — Feature area (e.g., `actions`, `general`, `content`, `user`)\n3. **Where it's used** — Element template, controller logic, or standalone component\n\n## Step 1: Add the key to the English dictionary\n\n**File:** `src/assets/lang/en.ts`\n\nThis file exports a `UmbLocalizationDictionary` — a nested object where top-level keys are groups and nested keys are the terms.\n\n```typescript\n// src/assets/lang/en.ts\nexport default {\n\t// ... existing groups\n\tmyFeature: {\n\t\tmyLabel: 'My Label',\n\t\tmyDescription: 'A description of the feature',\n\t\tcreateFor: (name: string) => (name ? `Create item for ${name}` : 'Create'),\n\t},\n} satisfies UmbLocalizationDictionary;\n```\n\n### Key naming rules\n\n- **Group**: camelCase feature name (e.g., `actions`, `general`, `content`, `media`, `user`)\n- **Term**: camelCase descriptive name (e.g., `assignDomain`, `auditTrail`, `browse`)\n- **Full key** used in code: `group_termName` (underscore separator)\n\n#### Choosing a group\n\nA group covers a **shared UX area**, not just the one component you're editing. Don't pick a group unilaterally — confirm it with the user:\n\n1. **If you already have a good idea of the scope** (from the surrounding code, the feature being worked on, etc.), propose it: \"This looks like it belongs to the `{scope}` scope — should that be the localization group?\"\n2. **If you don't**, ask directly: \"What is the common group name for this localization?\"\n3. **Either way, check for an existing match first.** Search the groups already in `src/assets/lang/en.ts` for one that already covers this UX area, and if you find one, ask whether it should be reused instead of creating a new group: \"`{existingGroup}` already covers this — should I use that instead?\"\n\nExamples already in this codebase:\n\n- `blockEditor` — Block List, Block Grid, and Block RTE share the same block-configuration UX\n- `contentTypeEditor` — Document Type, Media Type, and Member Type share the same editing UX\n- `codeEditor` — anything embedding the code editor\n\nOnly create a new group once the user confirms no existing one fits.\n\n#### Choosing a term\n\nA term names the **situation**, not the wording — two situations with identical English text today should still get separate terms, since the copy can diverge later.\n\nBuild it from two parts:\n\n1. **Subject**: `CreateBlock`, `ConfirmDelete`, `AddGroup`.\n2. **Presentation role**: `Title`, `Description`, `Action`, `Label`, `Notice`, `Message`, `ValidationMessage`, `Headline`, etc.\n\nCombined (subject + role suffix): `createAction`, `confirmDeleteTitle`, `addGroupDescription`.\n\nExample — three terms for one dialog in the `blockEditor` group, same subject (`confirmDeleteBlockGroup`) with different roles:\n\n```typescript\nconfirmDeleteBlockGroupTitle: 'Delete group?',\nconfirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',\nconfirmDeleteBlockGroupNotice: 'The content of these Blocks will still be present, editing of this content will no longer be available and will be shown as unsupported content.',\n```\n\nGrouping by subject keeps every piece of the dialog together, even though the wording shares nothing.\n\n### Value types\n\n| Type               | Use when                 | Example                                           |\n| ------------------ | ------------------------ | ------------------------------------------------- |\n| `string`           | Static text              | `myLabel: 'My Label'`                             |\n| `(args) => string` | Text with dynamic values | `createFor: (name: string) => \\`Create ${name}\\`` |\n\n## Step 2: Use the localized text\n\n### In element templates — `this.localize.term()`\n\nAvailable on any class extending `UmbLitElement`. The `localize` property is provided automatically.\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-element')\nexport class UmbMyElement extends UmbLitElement {\n\toverride render() {\n\t\treturn html`<uui-button label=${this.localize.term('myFeature_myLabel')}></uui-button>`;\n\t}\n}\n```\n\n### In templates — `<umb-localize>` element\n\nFor inline localized text in HTML templates:\n\n```html\n<umb-localize key=\"myFeature_myLabel\"></umb-localize>\n\n<!-- With fallback text (shown if key is missing) -->\n<umb-localize key=\"myFeature_myLabel\">Fallback text</umb-localize>\n```\n\n### In controllers or non-element classes — `UmbLocalizationController`\n\n```typescript\nimport { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';\n\nexport class UmbMyManager extends UmbControllerBase {\n\treadonly #localization = new UmbLocalizationController(this);\n\n\tsomeMethod() {\n\t\tconst label = this.#localization.term('myFeature_myLabel');\n\t}\n}\n```\n\n## Checklist\n\n- [ ] Key added to `src/assets/lang/en.ts` in the correct group\n- [ ] Key follows `group_termName` convention (camelCase, underscore separator)\n- [ ] Used `this.localize.term()` in elements or `UmbLocalizationController` in non-elements\n- [ ] No hardcoded user-facing strings remain\n- [ ] Compiles: `npm run compile`\n","frontmatter":{"name":"general-add-localization","description":"Add localization keys and use them in elements or controllers. Use when adding user-facing text that should be translatable — labels, descriptions, error messages, button text, status text, or any string shown in the backoffice UI.","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":1300,"sizeBytes":5673},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md","title":"general-add-value-summary","category":"anthropic-skill","format":"markdown","content":"---\nname: general-add-value-summary\ndescription: Add a value summary extension so a feature's typed value can be displayed compactly in collection views (e.g., table columns). Use when a feature or property editor already has a value type and needs a renderer for collection table cells. Two variants — simple (element only, no API call) and resolver (batch API resolution needed). Prerequisite: a value type constant must already exist — use the general-add-value-type skill first if it doesn't.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Summary\n\nAdd a `valueSummary` extension so a feature's values are rendered in collection views.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Summary](../../../docs/value-summary.md)** — extension type, base classes, resolver contract, rules\n\n## Prerequisite\n\nA value type constant (`UMB_{FEATURE}_VALUE_TYPE`) must already exist and be exported from the package `index.ts`. If it doesn't exist yet, use the **`general-add-value-type`** skill first.\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type constant** — the existing constant to bind to (e.g., `UMB_COLOR_PICKER_VALUE_TYPE`)\n4. **Resolver needed?** — Yes if rendering requires an API call (resolving IDs to names). No if the raw value can be formatted directly.\n\n**If resolver is needed, also ask:**\n5. **Resolved type** — the TypeScript type after resolution (e.g., `UmbUserGroupItemModel`)\n6. **Repository to use** — which item/detail repository resolves the values\n\n---\n\n## Option A: Simple summary (no resolver)\n\nUse when the raw value can be formatted or rendered without an API call — dates, booleans, strings, color hex codes, numeric ranges, etc.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n└── {feature}-value-summary.element.ts\n```\n\n### Step 1: Create the summary element\n\nFile: `{package-path}/value-summary/{feature}-value-summary.element.ts`\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbValueSummaryElementBase } from '@umbraco-cms/backoffice/value-summary';\n\n@customElement('umb-{feature}-value-summary')\nexport class Umb{Feature}ValueSummaryElement extends UmbValueSummaryElementBase<{TValue}> {\n\toverride render() {\n\t\tif (!this._value) return html``;\n\t\treturn html`<span>${this._value}</span>`;\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryElement as element };\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'umb-{feature}-value-summary': Umb{Feature}ValueSummaryElement;\n\t}\n}\n```\n\nAdjust `render()` for the value type:\n- Dates → `this.localize.dateTime(value)`\n- Booleans → `<umb-icon>`\n- References → tags or comma-separated names\n\n### Step 2: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t},\n];\n```\n\n### Step 3: Wire manifests into parent\n\n```typescript\nimport { manifests as valueSummaryManifests } from './value-summary/manifests.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t...valueSummaryManifests,\n\t// ... other manifests\n];\n```\n\n### Simple summary checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase` and exports as `element`\n- [ ] Manifest uses `kind: 'default'`, correct `forValueType`, lazy `element` import\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Option B: Summary with resolver (batch API resolution)\n\nUse when displaying the value requires fetching data — e.g., resolving reference uniques to display names. The resolver receives all values from the entire column in one batch call.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n├── {feature}-value-summary.element.ts\n└── {feature}-value-summary.resolver.ts\n```\n\n### Step 1: Create the summary element\n\nSame pattern as Option A, Step 1. Pass `{TResolved}` as the generic instead of `{TValue}` — the element receives the resolved value in `this._value`, already correctly typed.\n\n### Step 2: Create the resolver\n\nFile: `{package-path}/value-summary/{feature}-value-summary.resolver.ts`\n\n```typescript\nimport type { UmbValueSummaryResolver } from '@umbraco-cms/backoffice/value-summary';\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\nimport { Umb{Entity}ItemRepository } from '../repository/item/{entity}-item.repository.js';\n\nexport class Umb{Feature}ValueSummaryResolver\n\textends UmbControllerBase\n\timplements UmbValueSummaryResolver<{TValue}, {TResolved}>\n{\n\t#repo = new Umb{Entity}ItemRepository(this);\n\n\tasync resolveValues(values: ReadonlyArray<{TValue}>) {\n\t\t// Deduplicate all IDs needed across the entire batch\n\t\tconst allUniques = [...new Set(values.flatMap((v) => /* extract IDs from v */))];\n\n\t\tconst { data: items, asObservable } = await this.#repo.requestItems(allUniques);\n\t\tif (!items) return { data: values.map(() => undefined as unknown as {TResolved}) };\n\n\t\t// Positional mapping — output must align with input\n\t\treturn {\n\t\t\tdata: values.map((v) => /* find resolved item(s) for v */),\n\t\t\tasObservable: asObservable\n\t\t\t\t? () => asObservable().pipe(map((latest) => values.map((v) => /* remap */)))\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryResolver as valueResolver };\n```\n\nCritical rules:\n- `data` must have the **same length** as `values` and be **positionally aligned**.\n- Export the class as `valueResolver` (named export) — the loader checks for this name.\n- Provide `asObservable` only when the resolved data can change after initial load.\n\n### Step 3: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t\tvalueResolver: () => import('./{feature}-value-summary.resolver.js'),\n\t},\n];\n```\n\n### Step 4: Wire manifests into parent\n\nSame as Option A, Step 3.\n\n### Resolver checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase`, renders resolved type, exports as `element`\n- [ ] Resolver extends `UmbControllerBase`, implements `UmbValueSummaryResolver<{TValue}, {TResolved}>`\n- [ ] `resolveValues()` returns `data` positionally aligned with `values` (same length, same order)\n- [ ] Resolver exported as `valueResolver` (named export)\n- [ ] Manifest includes `valueResolver: () => import(...)` lazy loader\n- [ ] `asObservable` included only if resolved data can change reactively\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Collection wiring\n\n**Property editors**: no extra step needed. The document collection reads `editorAlias` from the server and matches it to your `forValueType`.\n\n**Generic table collection kind**: set `valueType` in the collection manifest column:\n\n```typescript\n{ field: 'myField', label: 'My Field', valueType: UMB_{FEATURE}_VALUE_TYPE }\n```\n","isInternal":false,"tokens":1870,"sizeBytes":7786},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md","title":"general-add-value-type","category":"anthropic-skill","format":"markdown","content":"---\nname: general-add-value-type\ndescription: Add a new value type — a named, typed string constant that extends UmbValueTypeMap. Use when a feature needs to declare what type of value it holds so it can be referenced in collection columns or value summary manifests. Also use when a property editor needs to expose its value type for use in collection views.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Type\n\nAdd a new value type constant to `UmbValueTypeMap`.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Type](../../../docs/value-type.md)** — full reference, naming convention, rules\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type key** — the string alias:\n   - For a property editor: use the schema alias (e.g., `'Umbraco.ColorPicker'`)\n   - For a domain type: use `Umb.ValueType.{Entity}.{Shape}` (e.g., `'Umb.ValueType.UserGroup.References'`)\n4. **TypeScript type** — the type of the raw value (e.g., `string`, `boolean`, `UmbReferenceByUnique[]`)\n\n---\n\n## Step 1: Create the constant file\n\nFile: `{package-path}/value-type/constants.ts`\n\n```typescript\nexport const UMB_{FEATURE}_VALUE_TYPE = '{value-type-key}' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_{FEATURE}_VALUE_TYPE]: {TValue};\n\t}\n}\n```\n\nRules:\n- The string value is always `as const`.\n- The `declare global` block is in the **same file** as the constant.\n- One constant per file — do not group multiple value types.\n\n### Property editor example\n\n```typescript\nimport type { UmbColorPickerPropertyEditorValue } from '../types.js';\n\nexport const UMB_COLOR_PICKER_VALUE_TYPE = 'Umbraco.ColorPicker' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_COLOR_PICKER_VALUE_TYPE]: UmbColorPickerPropertyEditorValue;\n\t}\n}\n```\n\n### Domain type example\n\n```typescript\nimport type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';\n\nexport const UMB_USER_GROUP_REFERENCES_VALUE_TYPE = 'Umb.ValueType.UserGroup.References' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_USER_GROUP_REFERENCES_VALUE_TYPE]: UmbReferenceByUnique[];\n\t}\n}\n```\n\n---\n\n## Step 2: Export from the package index\n\nAdd to the package `index.ts`:\n\n```typescript\nexport * from './value-type/constants.js';\n```\n\n---\n\n## Checklist\n\n- [ ] Constant declared with `as const`\n- [ ] `declare global` block extends `UmbValueTypeMap` in the same file\n- [ ] Key follows naming convention (schema alias for property editors, `Umb.ValueType.{Entity}.{Shape}` for domain types)\n- [ ] TypeScript type is the correct raw value type\n- [ ] One constant per file\n- [ ] Constant exported from package `index.ts`\n- [ ] Compiles: `npm run compile`\n","frontmatter":{"name":"general-add-value-type","description":"Add a new value type — a named, typed string constant that extends UmbValueTypeMap. Use when a feature needs to declare what type of value it holds so it can be referenced in collection columns or value summary manifests. Also use when a property editor needs to expose its value type for use in collection views.","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":697,"sizeBytes":2841},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md","title":"general-create-condition","category":"anthropic-skill","format":"markdown","content":"---\nname: general-create-condition\ndescription: Create a new extension condition that controls when extensions are active. Use when you need to conditionally show or hide extensions based on application state (e.g., current section, user permissions, entity state, workspace context). Conditions are registered as extensions and referenced in manifest condition arrays.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Condition\n\nCreate a new extension condition that controls when extensions are active.\n\n## What you need from the user\n\n1. **What it checks** — What context or state determines the condition (e.g., current section, user role, entity state)\n2. **Config properties** — What parameters consumers provide (e.g., `match`, `oneOf`)\n3. **Context dependency** — Which context token provides the data to evaluate\n\n## Files to create\n\nPlace in a `conditions/` subdirectory of the relevant feature:\n\n```\nmy-feature/\n└── conditions/\n    └── my-condition/\n        ├── constants.ts                     # Condition alias constant\n        ├── types.ts                         # Config type + global declaration\n        ├── my-condition.condition.ts         # Condition controller\n        └── manifests.ts                     # Condition manifest\n```\n\n## Step 1: Define the alias constant\n\n```typescript\n// constants.ts\nexport const UMB_MY_CONDITION_ALIAS = 'Umb.Condition.MyCondition';\n```\n\n## Step 2: Define the config type\n\n```typescript\n// types.ts\nimport type { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MyConditionConfig extends UmbConditionConfigBase<typeof UMB_MY_CONDITION_ALIAS> {\n\t/**\n\t * The value to match against.\n\t * @example \"Umb.Section.Content\"\n\t */\n\tmatch?: string;\n\n\t/**\n\t * One or more values to match against (any match passes).\n\t * @example [\"Umb.Section.Content\", \"Umb.Section.Media\"]\n\t */\n\toneOf?: Array<string>;\n}\n\ndeclare global {\n\tinterface UmbExtensionConditionConfigMap {\n\t\tUmbMyConditionConfig: MyConditionConfig;\n\t}\n}\n```\n\n### Key rules\n\n- Extend `UmbConditionConfigBase<typeof ALIAS_CONSTANT>` — this types the `alias` property\n- The `declare global` block on `UmbExtensionConditionConfigMap` makes the config type-safe when used in extension manifests\n- Use a unique key in the map\n- Add JSDoc with `@example` — these feed into JSON schema generation\n\n### Common config patterns\n\n| Pattern | Use when |\n|---------|----------|\n| `match: string` | Single value match |\n| `oneOf: Array<string>` | Any of multiple values |\n| `allOf: Array<string>` | All values must match |\n| `noneOf: Array<string>` | None of the values match |\n| No config properties | Condition checks a boolean state (e.g., \"is trashed\", \"is admin\") |\n\n## Step 3: Implement the condition controller\n\n```typescript\n// my-condition.condition.ts\nimport type { MyConditionConfig } from './types.js';\nimport { UMB_MY_CONTEXT } from '../../my-context.token.js';\nimport type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';\nimport type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';\nimport { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';\n\nexport class UmbMyCondition\n\textends UmbConditionBase<MyConditionConfig>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<MyConditionConfig>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_MY_CONTEXT, (context) => {\n\t\t\tthis.observe(\n\t\t\t\tcontext.someObservable,\n\t\t\t\t(value) => {\n\t\t\t\t\tthis.permitted = this.#check(value);\n\t\t\t\t},\n\t\t\t\t'_observeConditionValue',\n\t\t\t);\n\t\t});\n\t}\n\n\t#check(value: string | undefined): boolean {\n\t\tif (!value) return false;\n\n\t\tif (this.config.match) {\n\t\t\treturn value === this.config.match;\n\t\t}\n\n\t\tif (this.config.oneOf) {\n\t\t\treturn this.config.oneOf.includes(value);\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\nexport { UmbMyCondition as api };\n```\n\n### Key rules\n\n- Extend `UmbConditionBase<ConfigType>` — provides `permitted` property and `config` access\n- Implement `UmbExtensionCondition`\n- Constructor signature: `(host: UmbControllerHost, args: UmbConditionControllerArguments<ConfigType>)`\n- Set `this.permitted = true/false` — the base class notifies the extension system on change\n- Use `consumeContext()` + `observe()` for reactive conditions that update as state changes\n- Export as `api` (named export) so the class can be imported by the manifest\n- Use an explicit observer alias so re-evaluation replaces the previous subscription\n\n### For simple boolean conditions (no config)\n\n```typescript\nexport class UmbIsSomethingCondition\n\textends UmbConditionBase<UmbConditionConfigBase>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_SOME_CONTEXT, (context) => {\n\t\t\tthis.observe(context.isSomething, (value) => {\n\t\t\t\tthis.permitted = value === true;\n\t\t\t});\n\t\t});\n\t}\n}\n```\n\n## Step 4: Register the condition manifest\n\nConditions must load fast — always import the class directly (no lazy `() => import()`).\n\n```typescript\n// manifests.ts\nimport { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport { UmbMyCondition } from './my-condition.condition.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'condition',\n\t\tname: 'My Condition',\n\t\talias: UMB_MY_CONDITION_ALIAS,\n\t\tapi: UmbMyCondition,\n\t},\n];\n```\n\nAdd to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## How consumers use the condition\n\nIn any extension manifest that supports conditions:\n\n```typescript\n{\n\ttype: 'workspaceAction',\n\talias: 'My.WorkspaceAction',\n\tname: 'My Action',\n\tconditions: [\n\t\t{\n\t\t\talias: 'Umb.Condition.MyCondition',\n\t\t\tmatch: 'some-value',\n\t\t},\n\t],\n}\n```\n\nAll conditions in the array must be `permitted` for the extension to be active. They are AND-ed together.\n\n## Checklist\n\n- [ ] Alias constant exported from `constants.ts`\n- [ ] Config type extends `UmbConditionConfigBase<typeof ALIAS>` with the alias constant\n- [ ] Config type declared on global `UmbExtensionConditionConfigMap`\n- [ ] Condition class extends `UmbConditionBase<ConfigType>` and implements `UmbExtensionCondition`\n- [ ] Constructor calls `super(host, args)` and sets up context observation\n- [ ] `this.permitted` is set reactively (via `observe()`) — not just once in the constructor\n- [ ] Condition manifest registered with `type: 'condition'`\n- [ ] Manifests bubbled up to the package-level `manifests.ts`\n- [ ] Compiles: `npm run compile`\n","frontmatter":{"name":"general-create-condition","description":"Create a new extension condition that controls when extensions are active. Use when you need to conditionally show or hide extensions based on application state (e.g., current section, user permissions, entity state, workspace context). Conditions are registered as extensions and referenced in manifest condition arrays.","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":1537,"sizeBytes":6670},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md","title":"general-create-extension-type","category":"anthropic-skill","format":"markdown","content":"---\nname: general-create-extension-type\ndescription: Create a new extension type for the Umbraco backoffice extension registry. Use when adding a new type of extension that can be registered via manifests (e.g., a new dashboard type, a new sidebar app type, a new toolbar extension type). Any package can define extension types.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Extension Type\n\nCreate a new extension type for the Umbraco backoffice extension registry.\n\n## What you need from the user\n\n1. **Type name** — The `type` string used in manifests (e.g., `'dashboard'`, `'searchProvider'`)\n2. **Purpose** — What this extension type does (determines which base interface to extend)\n3. **Meta shape** — What configuration the extension needs from consumers\n4. **Whether it needs conditions** — Should extensions of this type support conditional loading?\n5. **Whether it has an element, API, or both** — Does it render UI, provide logic, or both?\n\n## Files to create\n\nAll files go in the relevant feature directory (e.g., `src/packages/my-package/my-feature/`).\n\n```\nmy-feature/\n├── my-feature.extension.ts     # Type definition + global declaration\n├── my-feature-element.interface.ts  # Element interface (if element-based)\n└── types.ts                    # Meta interface (if needed)\n```\n\n## Step 1: Choose the base manifest interface\n\n| Your extension... | Extend from | Import |\n|---|---|---|\n| Renders a custom element | `ManifestElement<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Provides an API class (no UI) | `ManifestApi<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Has both element and API | `ManifestElementAndApi<E, A>` | `@umbraco-cms/backoffice/extension-api` |\n| Needs conditional loading | Also extend `ManifestWithDynamicConditions<UmbExtensionConditionConfig>` | `@umbraco-cms/backoffice/extension-api` |\n\n## Step 2: Define the manifest interface\n\n```typescript\n// my-feature.extension.ts\nimport type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MetaMyFeature {\n\tlabel?: string;\n\t// ... properties consumers configure via meta\n}\n\nexport interface ManifestMyFeature\n\textends ManifestElement<UmbMyFeatureElement>,\n\t\tManifestWithDynamicConditions<UmbExtensionConditionConfig> {\n\ttype: 'myFeature';\n\tmeta: MetaMyFeature;\n}\n\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature;\n\t}\n}\n```\n\n### Key rules\n\n- The `type` property must be a **string literal type** (not `string`)\n- The key in `UmbExtensionManifestMap` must be unique across the entire codebase\n- The `declare global` block is what makes the registry type-aware — without it, the type won't be recognized\n\n## Step 3: Define the element/API interface (if needed)\n\n```typescript\n// my-feature-element.interface.ts\nimport type { UmbElement } from '@umbraco-cms/backoffice/element-api';\n\nexport interface UmbMyFeatureElement extends UmbElement {\n\t// Properties/methods that implementations must provide\n}\n```\n\n## Step 4: If the type supports kinds\n\nAdd a union type for the base manifest plus kind-specific variants:\n\n```typescript\nexport interface ManifestMyFeatureButtonKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'button';\n\tmeta: MetaMyFeatureButtonKind;\n}\n\nexport interface MetaMyFeatureButtonKind extends MetaMyFeature {\n\ticon: string;\n}\n\n// Union all variants in the global declaration\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureButtonKind;\n\t}\n}\n```\n\n## Step 5: Export from the package barrel\n\nAdd the type export to the package's `index.ts`:\n\n```typescript\nexport type * from './my-feature/my-feature.extension.js';\n```\n\nUse `export type *` since these are type-only exports.\n\n## Step 6: Register manifests that use this type\n\nVerify the type works by creating a test manifest:\n\n```typescript\nconst manifest: UmbExtensionManifest = {\n\ttype: 'myFeature',\n\talias: 'Umb.MyFeature.Test',\n\tname: 'Test My Feature',\n\telement: () => import('./my-feature-test.element.js'),\n\tmeta: {\n\t\tlabel: 'Test',\n\t},\n};\n```\n\nTypeScript should provide autocomplete for `type` and validate `meta` against `MetaMyFeature`.\n\n## Checklist\n\n- [ ] Manifest interface extends the correct base (`ManifestElement`, `ManifestApi`, or `ManifestElementAndApi`)\n- [ ] `type` property is a string literal, not `string`\n- [ ] `declare global` block adds to `UmbExtensionManifestMap` with a unique key\n- [ ] Meta interface is defined separately and exported\n- [ ] Element/API interface is defined if the type renders UI or provides logic\n- [ ] If kinds are supported: kind-specific interfaces extend the base with literal `kind` property\n- [ ] Type is exported from the package's `index.ts`\n- [ ] Compiles: `npm run compile`\n","frontmatter":{"name":"general-create-extension-type","description":"Create a new extension type for the Umbraco backoffice extension registry. Use when adding a new type of extension that can be registered via manifests (e.g., a new dashboard type, a new sidebar app type, a new toolbar extension type). Any package can define extension types.","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":1113,"sizeBytes":4809},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md","title":"general-create-kind","category":"anthropic-skill","format":"markdown","content":"---\nname: general-create-kind\ndescription: Create a new extension kind — a reusable default implementation for an extension type. Use when multiple extensions of the same type share the same element/API implementation and only differ in configuration. Kinds reduce boilerplate by providing pre-built defaults that extensions customize via meta.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Kind\n\nCreate a new extension kind — a reusable default implementation for an extension type.\n\n## What you need from the user\n\n1. **Which extension type** — The `type` this kind applies to (e.g., `'entityAction'`, `'section'`)\n2. **Kind name** — The `kind` string (e.g., `'delete'`, `'default'`, `'button'`)\n3. **What it provides** — Default element, API, or both\n4. **Meta defaults** — What meta values the kind pre-fills for consumers\n\n## When to create a kind\n\nCreate a kind when **multiple extensions of the same type share the same element/API implementation** and only differ in meta configuration. If the implementation is unique, a kind adds unnecessary indirection.\n\n## Files to create\n\n```\nmy-feature/\n├── kinds/\n│   └── my-kind/\n│       ├── my-kind.kind.ts              # Kind manifest definition\n│       ├── my-kind.element.ts           # Default element (if providing UI)\n│       ├── my-kind.api.ts               # Default API class (if providing logic)\n│       └── manifests.ts                 # Exports the kind manifest\n```\n\n## Step 1: Define the kind manifest\n\n```typescript\n// my-kind.kind.ts\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const UMB_MY_FEATURE_MY_KIND_MANIFEST: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.MyKind',\n\tmatchType: 'myFeature',\n\tmatchKind: 'myKind',\n\tmanifest: {\n\t\ttype: 'myFeature',\n\t\tkind: 'myKind',\n\t\telement: () => import('./my-kind.element.js'),\n\t\tapi: () => import('./my-kind.api.js'),\n\t\tweight: 1000,\n\t\tmeta: {\n\t\t\ticon: '',\n\t\t\tlabel: '',\n\t\t\t// ... defaults that consumers override\n\t\t},\n\t},\n};\n\nexport const manifest = UMB_MY_FEATURE_MY_KIND_MANIFEST;\n```\n\n### Key fields\n\n- **`type`**: Always `'kind'`\n- **`alias`**: Unique identifier. Convention: `Umb.Kind.{ExtensionType}.{KindName}`\n- **`matchType`**: The extension type this kind applies to\n- **`matchKind`**: The kind name that extensions reference\n- **`manifest`**: Partial manifest with defaults — any property here becomes the base for extensions using this kind\n\n## Step 2: Create the default element (if providing UI)\n\n```typescript\n// my-kind.element.ts\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-kind-element')\nexport class UmbMyKindElement extends UmbLitElement {\n\t// This element is shared by all extensions using this kind.\n\t// Use manifest meta for customization — not hardcoded values.\n\n\toverride render() {\n\t\treturn html`<!-- default rendering -->`;\n\t}\n}\n\nexport { UmbMyKindElement as element };\n```\n\n**Important:** Export as `element` (named export) — the lazy `import()` in the kind manifest resolves this name.\n\n## Step 3: Create the default API (if providing logic)\n\n```typescript\n// my-kind.api.ts\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\n\nexport class UmbMyKindApi extends UmbControllerBase {\n\t// Shared logic for all extensions using this kind.\n\t// Access extension-specific config via the manifest meta.\n}\n\nexport { UmbMyKindApi as api };\n```\n\n## Step 4: Register the kind in manifests\n\n```typescript\n// manifests.ts\nimport { manifest as myKindManifest } from './my-kind.kind.js';\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [myKindManifest];\n```\n\nAdd this to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## Step 5: Add kind-specific type to the extension type (if not already done)\n\nIn the extension type definition file, add a variant interface for this kind:\n\n```typescript\nexport interface ManifestMyFeatureMyKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'myKind';\n\tmeta: MetaMyFeatureMyKind;\n}\n\nexport interface MetaMyFeatureMyKind extends MetaMyFeature {\n\ticon: string;\n\tlabel: string;\n}\n\n// Update the global declaration to include the kind variant\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureMyKind;\n\t}\n}\n```\n\n## How consumers use the kind\n\nExtensions reference the kind and only specify what differs:\n\n```typescript\n{\n\ttype: 'myFeature',\n\tkind: 'myKind',\n\talias: 'My.Package.MyFeature.Specific',\n\tname: 'My Specific Feature',\n\tmeta: {\n\t\ticon: 'icon-heart',\n\t\tlabel: 'My Label',\n\t},\n}\n```\n\nThe registry merges the kind defaults with the extension manifest:\n- Extension properties **override** kind properties\n- `meta` is **shallow-merged** (extension meta extends kind meta, doesn't replace it)\n\n## Extending an existing kind\n\nA kind can build on another kind by spreading its manifest:\n\n```typescript\nimport { UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST } from '../default/default.kind.js';\n\nexport const manifest: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.Special',\n\tmatchType: 'myFeature',\n\tmatchKind: 'special',\n\tmanifest: {\n\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest,\n\t\ttype: 'myFeature',\n\t\tkind: 'special',\n\t\tapi: () => import('./special.api.js'),\n\t\tmeta: {\n\t\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest.meta,\n\t\t\t// additional meta defaults\n\t\t},\n\t},\n};\n```\n\n## Checklist\n\n- [ ] Kind manifest has `type: 'kind'` with unique `alias`\n- [ ] `matchType` matches the target extension type exactly\n- [ ] `matchKind` matches the `kind` string extensions will use\n- [ ] Default element exported as `element`, default API exported as `api` (named exports for lazy import)\n- [ ] Kind-specific manifest interface added to the extension type's `declare global` block\n- [ ] Kind registered in `manifests.ts` with type `Array<UmbExtensionManifest | UmbExtensionManifestKind>`\n- [ ] Meta defaults use empty strings or sensible fallbacks (not undefined)\n- [ ] Compiles: `npm run compile`\n- [ ] Blast radius considered: changing this kind affects all extensions that use it\n","frontmatter":{"name":"general-create-kind","description":"Create a new extension kind — a reusable default implementation for an extension type. Use when multiple extensions of the same type share the same element/API implementation and only differ in configuration. Kinds reduce boilerplate by providing pre-built defaults that extensions customize via meta.","allowed-tools":"Read, Write, Edit, Grep, Glob","allowedTools":["Read,","Write,","Edit,","Grep,","Glob"]},"allowedTools":["Read,","Write,","Edit,","Grep,","Glob"],"isInternal":false,"tokens":1504,"sizeBytes":6372},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md","title":"general-create-package","category":"anthropic-skill","format":"markdown","content":"# general-create-package\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md)","tokens":300,"sizeBytes":269},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md","title":"general-create-repository","category":"anthropic-skill","format":"markdown","content":"# general-create-repository\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md)","tokens":300,"sizeBytes":278},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md","title":"general-create-workspace","category":"anthropic-skill","format":"markdown","content":"# general-create-workspace\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md)","tokens":300,"sizeBytes":275},{"name":"SKILL.md","path":"src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md","title":"general-deprecate-api","category":"anthropic-skill","format":"markdown","content":"# general-deprecate-api\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md)","tokens":300,"sizeBytes":266},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/block/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/block/package.json","title":"Block Skill","category":"anthropic-skill","format":"json","content":"# Block Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/block/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/block/package.json)","tokens":300,"sizeBytes":228},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json","title":"Clipboard Skill","category":"anthropic-skill","format":"json","content":"# Clipboard Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json)","tokens":300,"sizeBytes":240},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json","title":"Code-editor Skill","category":"anthropic-skill","format":"json","content":"# Code-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json)","tokens":300,"sizeBytes":246},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/content/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/content/package.json","title":"Content Skill","category":"anthropic-skill","format":"json","content":"# Content Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/content/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/content/package.json)","tokens":300,"sizeBytes":234},{"name":"icon-dictionary.json","path":"src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json","title":"Icon-registry Skill","category":"anthropic-skill","format":"json","content":"# Icon-registry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json)","tokens":300,"sizeBytes":278},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/core/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/package.json","title":"Core Skill","category":"anthropic-skill","format":"json","content":"# Core Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/package.json)","tokens":300,"sizeBytes":225},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/data-type/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/data-type/package.json","title":"Data-type Skill","category":"anthropic-skill","format":"json","content":"# Data-type Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/data-type/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/data-type/package.json)","tokens":300,"sizeBytes":240},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json","title":"Dictionary Skill","category":"anthropic-skill","format":"json","content":"# Dictionary Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json)","tokens":300,"sizeBytes":243},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/documents/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/documents/package.json","title":"Documents Skill","category":"anthropic-skill","format":"json","content":"# Documents Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/documents/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/documents/package.json)","tokens":300,"sizeBytes":240},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/elements/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/elements/package.json","title":"Elements Skill","category":"anthropic-skill","format":"json","content":"# Elements Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/elements/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/elements/package.json)","tokens":300,"sizeBytes":237},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json","title":"Embedded-media Skill","category":"anthropic-skill","format":"json","content":"# Embedded-media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/embedded-media/package.json)","tokens":300,"sizeBytes":255},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json","title":"Extension-insights Skill","category":"anthropic-skill","format":"json","content":"# Extension-insights Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/extension-insights/package.json)","tokens":300,"sizeBytes":267},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/health-check/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/health-check/package.json","title":"Health-check Skill","category":"anthropic-skill","format":"json","content":"# Health-check Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/health-check/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/health-check/package.json)","tokens":300,"sizeBytes":249},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/help/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/help/package.json","title":"Help Skill","category":"anthropic-skill","format":"json","content":"# Help Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/help/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/help/package.json)","tokens":300,"sizeBytes":225},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/language/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/language/package.json","title":"Language Skill","category":"anthropic-skill","format":"json","content":"# Language Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/language/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/language/package.json)","tokens":300,"sizeBytes":237},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/library/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/library/package.json","title":"Library Skill","category":"anthropic-skill","format":"json","content":"# Library Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/library/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/library/package.json)","tokens":300,"sizeBytes":234},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json","title":"Log-viewer Skill","category":"anthropic-skill","format":"json","content":"# Log-viewer Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/log-viewer/package.json)","tokens":300,"sizeBytes":243},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/management-api/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json","title":"Management-api Skill","category":"anthropic-skill","format":"json","content":"# Management-api Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/management-api/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/management-api/package.json)","tokens":300,"sizeBytes":255},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json","title":"Markdown-editor Skill","category":"anthropic-skill","format":"json","content":"# Markdown-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/markdown-editor/package.json)","tokens":300,"sizeBytes":258},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/media/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/media/package.json","title":"Media Skill","category":"anthropic-skill","format":"json","content":"# Media Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/media/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/media/package.json)","tokens":300,"sizeBytes":228},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/members/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/members/package.json","title":"Members Skill","category":"anthropic-skill","format":"json","content":"# Members Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/members/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/members/package.json)","tokens":300,"sizeBytes":234},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json","title":"Models-builder Skill","category":"anthropic-skill","format":"json","content":"# Models-builder Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/models-builder/package.json)","tokens":300,"sizeBytes":255},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json","title":"Multi-url-picker Skill","category":"anthropic-skill","format":"json","content":"# Multi-url-picker Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/package.json)","tokens":300,"sizeBytes":261},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/packages/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/packages/package.json","title":"Packages Skill","category":"anthropic-skill","format":"json","content":"# Packages Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/packages/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/packages/package.json)","tokens":300,"sizeBytes":237},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json","title":"Performance-profiling Skill","category":"anthropic-skill","format":"json","content":"# Performance-profiling Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/performance-profiling/package.json)","tokens":300,"sizeBytes":276},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/preview/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/preview/package.json","title":"Preview Skill","category":"anthropic-skill","format":"json","content":"# Preview Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/preview/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/preview/package.json)","tokens":300,"sizeBytes":234},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json","title":"Property-editors Skill","category":"anthropic-skill","format":"json","content":"# Property-editors Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/property-editors/package.json)","tokens":300,"sizeBytes":261},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json","title":"Publish-cache Skill","category":"anthropic-skill","format":"json","content":"# Publish-cache Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/publish-cache/package.json)","tokens":300,"sizeBytes":252},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/relations/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/relations/package.json","title":"Relations Skill","category":"anthropic-skill","format":"json","content":"# Relations Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/relations/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/relations/package.json)","tokens":300,"sizeBytes":240},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/rte/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/rte/package.json","title":"Rte Skill","category":"anthropic-skill","format":"json","content":"# Rte Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/rte/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/rte/package.json)","tokens":300,"sizeBytes":222},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/segment/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/segment/package.json","title":"Segment Skill","category":"anthropic-skill","format":"json","content":"# Segment Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/segment/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/segment/package.json)","tokens":300,"sizeBytes":234},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/settings/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/settings/package.json","title":"Settings Skill","category":"anthropic-skill","format":"json","content":"# Settings Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/settings/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/settings/package.json)","tokens":300,"sizeBytes":237},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/static-file/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/static-file/package.json","title":"Static-file Skill","category":"anthropic-skill","format":"json","content":"# Static-file Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/static-file/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/static-file/package.json)","tokens":300,"sizeBytes":246},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json","title":"Sysinfo Skill","category":"anthropic-skill","format":"json","content":"# Sysinfo Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/sysinfo/package.json)","tokens":300,"sizeBytes":234},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/tags/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tags/package.json","title":"Tags Skill","category":"anthropic-skill","format":"json","content":"# Tags Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tags/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tags/package.json)","tokens":300,"sizeBytes":225},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json","title":"Telemetry Skill","category":"anthropic-skill","format":"json","content":"# Telemetry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/telemetry/package.json)","tokens":300,"sizeBytes":240},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/templating/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/templating/package.json","title":"Templating Skill","category":"anthropic-skill","format":"json","content":"# Templating Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/templating/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/templating/package.json)","tokens":300,"sizeBytes":243},{"name":"CLAUDE.md","path":"src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md","title":"Tiptap Skill","category":"anthropic-skill","format":"markdown","content":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/CLAUDE.md)","tokens":300,"sizeBytes":225},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json","title":"Tiptap Skill","category":"anthropic-skill","format":"json","content":"# Tiptap Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/tiptap/package.json)","tokens":300,"sizeBytes":231},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/translation/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/translation/package.json","title":"Translation Skill","category":"anthropic-skill","format":"json","content":"# Translation Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/translation/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/translation/package.json)","tokens":300,"sizeBytes":246},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/ufm/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/ufm/package.json","title":"Ufm Skill","category":"anthropic-skill","format":"json","content":"# Ufm Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/ufm/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/ufm/package.json)","tokens":300,"sizeBytes":222},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json","title":"Umbraco-news Skill","category":"anthropic-skill","format":"json","content":"# Umbraco-news Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/umbraco-news/package.json)","tokens":300,"sizeBytes":249},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/user/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/user/package.json","title":"User Skill","category":"anthropic-skill","format":"json","content":"# User Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/user/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/user/package.json)","tokens":300,"sizeBytes":225},{"name":"package.json","path":"src/Umbraco.Web.UI.Client/src/packages/webhook/package.json","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/webhook/package.json","title":"Webhook Skill","category":"anthropic-skill","format":"json","content":"# Webhook Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/webhook/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/webhook/package.json)","tokens":300,"sizeBytes":234},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/CLAUDE.md)","tokens":300,"sizeBytes":160},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.github/copilot-instructions.md","title":"GitHub Copilot Instructions","category":"copilot-instructions","format":"markdown","content":"# GitHub Copilot Instructions\n\nPath: `.github/copilot-instructions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.github/copilot-instructions.md)","tokens":300,"sizeBytes":192},{"name":"README.md","path":"src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md","rawUrl":"https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md","title":"validation Documentation","category":"plugin-manifest","format":"markdown","content":"# validation Documentation\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/validation/README.md)","tokens":300,"sizeBytes":255}],"systemPromptSnippet":"<yakaai_skills repo=\"umbraco/Umbraco-CMS\">\n<!-- File: .claude/skills/umb-bump-version/SKILL.md (Tokens: ~1019 | Category: anthropic-skill) -->\n---\nname: umb-bump-version\ndescription: Bump the Umbraco CMS version across all required files. Use when the user asks to bump, update, or set the version number — e.g., \"bump version to 17.3.4\", \"set version to 18.0.0-rc\", \"update version\". Accepts the target version as an argument.\nargument-hint: <version> (e.g., 17.3.4, 18.0.0-rc)\n---\n\n# Bump Version - Umbraco CMS\n\nUpdates the Umbraco CMS version string across all files that track it.\n\n**Do NOT use AskUserQuestion if a version argument is provided. Only ask if `$ARGUMENTS` is empty or cannot be parsed as a version.**\n\n## Arguments\n\n- `$ARGUMENTS` - Required: the target version string (e.g., `17.3.4`, `18.0.0-rc`)\n\n## Files to Update\n\nThe following 5 files must be updated with the new version:\n\n| # | File | Field |\n|---|------|-------|\n| 1 | `version.json` | `\"version\"` |\n| 2 | `src/Umbraco.Web.UI.Client/package.json` | `\"version\"` |\n| 3 | `src/Umbraco.Web.UI.Client/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n| 4 | `tests/Umbraco.Tests.AcceptanceTest/package.json` | `\"version\"` |\n| 5 | `tests/Umbraco.Tests.AcceptanceTest/package-lock.json` | top-level `\"version\"` AND `packages[\"\"].version` |\n\n**Note**: For major version bumps (e.g., 17.x to 18.x), `src/Umbraco.Web.UI.Login/package.json` has a caret-ranged dependency on `@umbraco-cms/backoffice` (e.g., `^17.2.0`) that will need manual updating. This skill does not handle that — major bumps involve many other changes beyond version strings.\n\n## Instructions\n\n### 1. Parse and Validate the Version\n\nExtract the version from `$ARGUMENTS`. It must be a valid semver-like string (e.g., `17.3.4`, `18.0.0-rc`, `17.4.0-preview.1`). If no version is provided or it cannot be parsed, ask the user for the target version.\n\n### 2. Read the Current Version\n\nRead `version.json` and extract the current `\"version\"` value. If the current version already equals the target version, report that the version is already set and stop — do not edit, stage, or commit anything.\n\nOtherwise, display both versions:\n\n```\nBumping version: {current} -> {target}\n```\n\n### 3. Update All Files\n\nUpdate each of the 5 files listed above, replacing the old version with the new version. For each file:\n\n- **`version.json`**: Replace the `\"version\"` value.\n- **`package.json` files**: Replace the `\"version\"` value (near the top of the file).\n- **`package-lock.json` files**: Replace BOTH the top-level `\"version\"` value AND the `\"version\"` inside the `\"packages\": { \"\": { ... } }` block. These are always in the first ~10 lines of the file.\n\nUse targeted edits — do NOT rewrite entire files. Be precise to avoid changing version strings in dependency entries.\n\n### 4. Verify\n\nAfter all edits, grep for the target version value across the 5 files to confirm all updates landed correctly:\n\n```bash\ngrep -n \"\\\"version\\\": \\\"{version}\\\"\" version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nExpect exactly 7 matches (one per `package.json` and `version.json`, two per `package-lock.json`).\n\n### 5. Stage and Commit\n\nStage only the 5 changed files:\n\n```bash\ngit add version.json src/Umbraco.Web.UI.Client/package.json src/Umbraco.Web.UI.Client/package-lock.json tests/Umbraco.Tests.AcceptanceTest/package.json tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n```\n\nThen commit with the message `Bump version to {version}.` — replacing `{version}` with the target version:\n\n```bash\ngit commit -m \"Bump version to {version}.\"\n```\n\n### 6. Report\n\nOutput a summary:\n\n```\nVersion bumped to {version} in:\n- version.json\n- src/Umbraco.Web.UI.Client/package.json\n- src/Umbraco.Web.UI.Client/package-lock.json\n- tests/Umbraco.Tests.AcceptanceTest/package.json\n- tests/Umbraco.Tests.AcceptanceTest/package-lock.json\n\nChanges staged and committed.\n```\n\n<!-- File: .claude/skills/umb-release-notes/SKILL.md (Tokens: ~2488 | Category: anthropic-skill) -->\n---\nname: umb-release-notes\ndescription: Improve a set of auto-generated GitHub release notes for an Umbraco CMS release. Cross-checks the notes against every PR carrying the release label, adds any that are missing, re-files every PR under the most appropriate category, and strips purely-internal entries. Use whenever the user asks to tidy up, improve, complete, or recategorize release notes for a given version, or mentions a release-notes text file plus a version number.\nargument-hint: <version> <path-to-generated-notes-file>\n---\n\n# Umbraco CMS - Improve Release Notes\n\nTakes a file of auto-generated GitHub release notes and produces an improved version that:\n\n1. **Is complete** — every merged PR carrying the `release/<version>` label appears.\n2. **Is well-categorized** — every PR sits under the most appropriate heading.\n3. **Is free of noise** — purely-internal entries of no value to a reader are removed.\n\nThe result is written to a **new** file alongside the input, so the user can diff the two.\n\n**Run autonomously.** Do NOT use `AskUserQuestion` once the required arguments (version and input file path) are available — only ask if one of them is missing from `$ARGUMENTS` and cannot be inferred (see Arguments). Beyond that, make the categorization calls yourself using the rules below; if a handful are genuinely borderline, place them anyway and note the borderline ones in your closing summary so the user can override.\n\n## Arguments\n\n`$ARGUMENTS` contains two values:\n\n1. **Version** — e.g. `17.5.0`, `18.1.0`. The GitHub label to search is `release/<version>` (so version `17.5.0` → label `release/17.5.0`).\n2. **Input file path** — full path to the text file holding the auto-generated notes (e.g. `C:\\Temp\\release-17.5.0-rc.md`).\n\nIf either is missing, ask the user once for the missing value, then proceed.\n\n## Prerequisites\n\nRun `gh auth status`. If it fails, tell the user to authenticate `gh` (e.g. `gh auth login`) and stop — the skill needs the GitHub CLI to query PRs. The repo is always `umbraco/Umbraco-CMS`.\n\n## Procedure\n\n### 1. Read the input notes\n\nRead the input file. Note its structure — it is GitHub's generated format:\n\n- A leading HTML comment (`<!-- Release notes generated ... -->`).\n- A `## What's Changed` heading followed by `### <emoji> <Category>` sub-headings, each with `* <title> by @<author> in <url>` bullets.\n- A trailing `## New Contributors` section and a `**Full Changelog**: ...` line.\n\nExtract the set of PR numbers already present (parse the `/pull/<number>` from each bullet). Preserve each existing bullet's **exact text** (title, author, URL) when you re-emit it — only its category placement may change.\n\n### 2. Fetch every labelled PR\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 \\\n  --json number,title,author,labels,mergedAt \\\n  --jq '.[] | select(.mergedAt != null) | \"\\(.number)\\t\\(.author.login)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"' | sort -n\n```\n\nThis is the authoritative list of what the release *should* contain. Each row gives number, author, labels, title.\n\n**Guard against silent truncation.** `gh pr list` caps at `--limit` without warning, so a large release could drop the overflow and the skill would still look \"complete\". Count the returned rows and compare against the limit:\n\n```bash\ngh pr list --repo umbraco/Umbraco-CMS --label \"release/<version>\" --state closed --limit 1000 --json number --jq 'length'\n```\n\nIf this equals 1000, the limit was hit — raise `--limit` and re-fetch before continuing. Do **not** proceed on a truncated list.\n\n### 3. Reconcile\n\n- **Missing labelled PRs** (labelled but not in the input file): these must be **added**. Build a bullet as `* <title> by @<author> in https://github.com/umbraco/Umbraco-CMS/pull/<number>`.\n  - **Author handle.** `<author>` in the template is the raw `.author.login` value — the bullet supplies the leading `@`, so do not prepend another. `gh`'s `.author.login` already returns bot accounts with the `[bot]` suffix as part of the login — Dependabot comes back as `dependabot[bot]`, not `dependabot` or `app/dependabot` (the `app/` form only appears in git committer metadata and CODEOWNERS, never in `gh`'s JSON). So the login is already in the right shape; use it verbatim (e.g. `.author.login` of `dependabot[bot]` renders as `@dependabot[bot]`, matching what GitHub's generator wrote for the existing bullets). The only thing to guard against is accidentally stripping or altering the `[bot]` suffix.\n- **PRs in the file but not labelled**: keep them. The generated notes span a commit range (see the `Full Changelog` compare link), so they legitimately include backports / earlier-version PRs that lack the current label. For any of these you need to categorize, fetch its labels with:\n\n  ```bash\n  gh pr view <number> --repo umbraco/Umbraco-CMS --json number,title,labels \\\n    --jq '\"\\(.number)\\t\\([.labels[].name] | join(\", \"))\\t\\(.title)\"'\n  ```\n\nDo **not** invent or alter the `New Contributors` section — carry it over verbatim. You cannot reliably recompute first-time contributors, so leave it as the generator produced it (mention this in the summary).\n\n### 4. Categorize every PR\n\nUse exactly these headings, in this order. Omit any heading that ends up with no entries.\n\n| Heading | What goes here | Primary signal |\n|---|---|---|\n| `### 🙌 Notable Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/notable` |\n| `### 💥 Breaking Changes` | **Don't recategorize existing entries.** Label-driven — but still add any missing PR carrying this label here. | label `category/breaking` |\n| `### 📦 Dependencies` | Dependency bumps | label `dependencies`; or dependabot author |\n| `### 🚀 New Features` | New user- or developer-facing capability | label `type/feature` / `category/feature`; or title introduces/adds a genuinely new capability |\n| `### 🚤 Performance` | Performance improvements | label `category/performance`; or `Performance:` title prefix |\n| `### 🌈 Accessibility Improvements` | A11y improvements (labels, contrast, keyboard) | label `category/accessibility` / `accessibility`; or clear a11y intent (e.g. \"improve contrast\", \"missing labels\") |\n| `### 🐛 Bug Fixes` | Fixes to broken/incorrect behaviour | default for anything describing a fix |\n| `### 🧪 Testing` | Test additions/changes only | label `category/test-automation` / `area/test`; or `E2E`/`QA`/\"acceptance tests\"/\"unit test coverage\"/\"add tests\" titles |\n| `### 🛡️ Code Quality, Documentation and Refactoring` | Refactors, deprecations, API tidy-ups, XML/MD documentation, knowledge-base (`MD`) updates | label `category/refactor`; or titles about refactoring, deprecating, renaming, documenting, constants extraction, MD/CLAUDE.md content |\n| `### 🧑‍💻 Developer Experience` | Things that improve the experience of developers building on or contributing to Umbraco — dev tooling, build/watch ergonomics, test mocks/harnesses, backoffice dev utilities | `Developer Experience` title prefix; dev tooling; mock/harness changes |\n\n**Rules:**\n\n- **Notable and Breaking are off-limits for recategorization** — never move a PR that is *already in the input file* into or out of these sections; they are driven purely by their labels and the generator placed them correctly. This does **not** exempt them from completeness: a PR discovered as missing in step 3 that carries `category/notable` or `category/breaking` must still be **added** under the matching section.\n- Label signals beat title wording, except a `Performance:`/`Developer Experience:` title prefix is decisive for its section.\n- A PR with both `type/feature` and `category/refactor` whose title clearly describes a refactor (e.g. \"swap relative imports\", \"re-export type\") belongs under Code Quality, not New Features.\n- \"Add ... tests\"/\"unit test coverage\" → Testing, even if it also touches docs. If a PR adds XML documentation *and* tests, lead with where the title's emphasis lies (documentation → Code Quality; test coverage → Testing).\n- When a PR is genuinely 50/50, pick the more reader-useful heading and list it in your closing summary as borderline.\n\n### 5. Remove purely-internal noise\n\nDrop entries that have **no value to anyone reading release notes** — pure repository plumbing with no shipped impact. Examples:\n\n- Branch/merge maintenance (\"Fix main branch after merge issue\").\n- CI/pipeline fixes that don't change the product.\n- Reverts of changes that never shipped in a release.\n\n**Keep** anything that ships in the product or genuinely helps developers building on Umbraco — that includes documentation/MD updates, dev tooling, and test mocks (those go to Code Quality or Developer Experience, they are *not* noise). When unsure whether something is noise, keep it and flag it in the summary rather than silently dropping it. List every removal in your closing summary.\n\n### 6. Write the output\n\nWrite to a new file in the **same folder** as the input, named by appending ` - with updates` before the extension:\n\n- Input `C:\\Temp\\release-17.5.0-rc.md` → Output `C:\\Temp\\release-17.5.0-rc - with updates.md`\n\nPreserve the leading HTML comment, the `## What's Changed` heading, the `## New Contributors` section, and the `**Full Changelog**` line exactly. Only the `### <category>` groupings and their bullets change.\n\n### 7. Report\n\nGive a concise summary:\n\n- Count of PRs added (with their numbers), and which categories they landed in.\n- Notable recategorizations (PRs moved out of the catch-all Bug Fixes into Features/Performance/Testing/etc.).\n- Every entry removed, with the one-line reason.\n- Any borderline calls the user may want to override.\n- The output file path.\n\n## Verification\n\nBefore reporting done, confirm:\n\n- Every PR number from step 2 is present in the output (except any you deliberately removed in step 5 — and those must be in the removal list).\n- No PR appears under more than one heading.\n- Notable and Breaking sections are byte-for-byte unchanged from the input.\n- The header comment, New Contributors, and Full Changelog lines are intact.\n\n<!-- File: .claude/skills/umb-review/evals/evals.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Evals Skill\n\nPath: `.claude/skills/umb-review/evals/evals.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/evals/evals.json)\n\n<!-- File: .claude/skills/umb-review/references/breaking-changes.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/umb-review/references/breaking-changes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/breaking-changes.md)\n\n<!-- File: .claude/skills/umb-review/references/coding-preferences.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/umb-review/references/coding-preferences.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/coding-preferences.md)\n\n<!-- File: .claude/skills/umb-review/references/complexity-assessment.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/umb-review/references/complexity-assessment.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/complexity-assessment.md)\n\n<!-- File: .claude/skills/umb-review/references/gh-cli-setup.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/umb-review/references/gh-cli-setup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/gh-cli-setup.md)\n\n<!-- File: .claude/skills/umb-review/references/impact-analysis.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.claude/skills/umb-review/references/impact-analysis.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/.claude/skills/umb-review/references/impact-analysis.md)\n\n<!-- File: .claude/skills/umb-review/SKILL.md (Tokens: ~3498 | Category: anthropic-skill) -->\n---\nname: umb-review\ndescription: Automated PR code review for Umbraco CMS. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality. Non-interactive — outputs a full structured review. Use this skill whenever the user asks to review a branch, review a PR, check their changes for issues, analyze a diff, or validate breaking change patterns — even if they don't say \"review\" explicitly. Does NOT apply to writing new code, fixing bugs, refactoring, explaining architecture, writing tests, or reviewing documentation content.\nargument-hint: <target-branch>\n---\n\n# PR Review - Umbraco CMS\n\nAutomated, non-interactive PR code review. Analyzes changed files for intent, impact on consumers, breaking changes, architecture compliance, and code quality.\n\n**Do NOT use AskUserQuestion at any point. This skill runs fully autonomously.**\n\n## Arguments\n\n- `$ARGUMENTS` - Optional: target branch to diff against (auto-detected from PR, falls back to `origin/main`)\n\n## Instructions\n\n### 0. Verify GH CLI is Available\n\nRun `gh auth status`. If it fails, read `references/gh-cli-setup.md` and present the setup instructions to the user. Do not proceed with the review.\n\n### 1. Resolve Target Branch\n\nDetermine the target branch for comparison using this priority order:\n\n1. **Explicit argument**: If `$ARGUMENTS` is provided and non-empty, use it as the target branch\n2. **PR target branch**: If no argument, run `gh pr view --json baseRefName --jq '.baseRefName'` to detect the target branch of the current branch's open PR. If a PR exists, use `origin/{baseRefName}` as the target branch.\n3. **Fallback**: If no argument and no PR found (command fails or returns empty), default to `origin/main`\n\nStore the resolved target branch for use in subsequent steps. Log which resolution method was used (e.g., \"Target branch: `origin/v18/dev` (from PR #1234)\").\n\n### 2. Load Review Standards\n\n#### 2a. Load coding preferences\n\nRead the coding preferences and code review scoring criteria from:\n\n- `references/coding-preferences.md` (relative to this skill file)\n\nParse and internalize all rules, conventions, scoring categories, and severity definitions. These are your review criteria.\n\n#### 2b. Load area-specific documentation\n\nOnce the changed file list is known (after step 3a), determine which areas of the codebase are touched and load the relevant documentation. Execute this sub-step between 3a and 3b. This documentation takes precedence over sibling comparison for architectural and pattern validation.\n\n**Resolution order for each changed file:**\n\n1. **Find the nearest `CLAUDE.md`** — walk up from the changed file's directory toward the repository root. The first `CLAUDE.md` found is the area guide for that file. Read it.\n2. **Read referenced docs** — if the `CLAUDE.md` references documentation files (e.g., a `docs/` directory), use the descriptions in the `CLAUDE.md` to determine which docs are relevant to the type of code being changed, and read those. If unsure, read all referenced docs — the cost of reading is low, the cost of missing a convention is high.\n3. **Follow cross-references in loaded docs** — if a loaded doc references another doc as covering a complementary or related concern, and the changed files touch that concern, read the referenced doc too. Repeat until no new relevant cross-references remain.\n4. **Check for applicable skills** — review the available skills list. If a skill exists for the type of code being changed, read the skill file to understand the expected patterns, structure, and conventions it enforces. Do NOT invoke the skill — just use it as a reference for what the correct implementation should look like.\n\n**Store all loaded documentation** for use in step 4. These docs define the authoritative patterns and conventions that the review evaluates against.\n\n### 3. Gather Changed Files\n\n#### 3a. Collect file list, stats, and diff\n\nRun these git commands (where `{target}` is the resolved target branch):\n\n```bash\ngit diff {target}...HEAD --name-only --diff-filter=d   # changed files (excluding deleted)\ngit diff {target}...HEAD --stat                         # line counts per file\ngit log {target}...HEAD --oneline                       # commit history\ngit diff {target}...HEAD                                # full diff (primary review source)\n```\n\n**If no changes found**: Output \"No changes found between current branch and `{target}`. Nothing to review.\" and stop.\n\n#### 3b. Filter out noise files\n\nFrom the changed file list, classify each file as **noise** or **reviewable**.\n\n**Noise files** (skip entirely — do not read, do not review):\n\n| Pattern                                              | Reason                          |\n| ---------------------------------------------------- | ------------------------------- |\n| `*.gen.ts`, `*.gen.cs`                               | Auto-generated API client code  |\n| `*.generated.cs`, `*.Designer.cs` (in `Migrations/`) | Auto-generated models/snapshots |\n| `*/assets/lang/*.ts` (except `en.ts`)                | Non-English translation files   |\n| `*/mocks/data/*.ts`                                  | Test fixture data               |\n| `*/dist-cms/*`, `*/storybook-static/*`               | Build output                    |\n| `*/TEMP/InMemoryAuto/*`                              | Runtime-generated models        |\n| `package-lock.json`                                  | Dependency lock file            |\n| `appsettings-schema.*.json`                          | Generated JSON schema           |\n\nLog the skip list: \"Skipped {N} noise files: {comma-separated list of filenames}\"\n\n#### 3c. Read reviewable changed files\n\nRead the full file for every reviewable changed file.\n\n#### 3d. Track file counts\n\nKeep track of these numbers for the review output in step 7: total changed files, noise files skipped, and reviewable files read. Also record: distinct production layers touched, distinct project directories, and total lines changed — these feed step 3e.\n\n#### 3e. Assess PR complexity\n\nFollow the procedure in `references/complexity-assessment.md`. Store the triggered dimensions and suggestions for step 7.\n\n#### 3f. Classify PR scope\n\nClassify the PR to determine which review steps are relevant:\n\n| Classification  | Condition                                                                       | Effect                                                                          |\n| --------------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |\n| **Gen-only**    | All reviewable files are `gen.ts`                                               | Skip steps 5 and 6; step 4 reviews impact on other code only                    |\n| **Docs-only**   | All reviewable files are `.md`                                                  | Skip steps 5 and 6; step 4 reviews intent and readability only                  |\n| **Test-only**   | All reviewable files are in `tests/`                                            | Skip steps 5 and 6; step 4 reviews intent, code quality, and test coverage only |\n| **Config-only** | All reviewable files are `.csproj`, `.props`, `.json` config, or CI/build files | Skip step 5; step 6 checks dependency version changes only                      |\n| **Standard**    | Anything else                                                                   | No skips — run all steps                                                        |\n\n### 4. Raw Code Review\n\nReview each changed file holistically. Think like a senior developer reading a colleague's PR. Note all findings without worrying about format or severity yet.\n\n#### 4a. Read and reason about each file\n\nFor each changed file, reason about: What does this code do? Is it correct? What's missing — validation, error handling, notifications, cleanup, edge cases? Could this break anything for consumers?\n\n**Unused usings (C# only):** flag a `using` directive only if this diff adds it, or removes the last code that used it — pre-existing unused usings are out of scope. Only flag when confident: a namespace can be used without appearing by name (extension methods, attributes, `<see cref>`, LINQ query syntax, `using static`, aliases, `#if` blocks). Report them as one Suggestion listing `file: namespace` pairs, per root `CLAUDE.md` §11; this is the one stylistic finding that is allowed.\n\n#### 4b. Validate against documentation and patterns\n\nUse a **docs-first** approach: classify the code by what it does, check it against documented conventions, and only fall back to sibling comparison when docs don't cover the pattern.\n\n**Step 1 — Determine the correct approach from documentation, then check whether the PR matches**\n\nA PR is a proposed solution, not the source of truth. This step has two parts that must happen in order — do not start part B until part A is complete.\n\n**Part A — Before validating/judging the implementation**, determine what the correct approach is for each new class or file based on what it does. Use the documentation loaded in step 2b to identify the expected base classes, patterns, and conventions. Write down the expected approach. Classify based on what the code does, not based on what neighboring files look like.\n\n**Part B — Now compare the PR's implementation** against the expected approach from Part A. If it deviates from the documented approach, flag it. If the documentation specifies reference examples, read those examples to verify the implementation matches.\n\n**Pattern match is the leading finding.** If the documentation defines a pattern that fits what the code does, the first and most important finding is whether the code follows that pattern.\n\n**Step 2 — Fall back to sibling comparison**\n\nIf the documentation does not cover the specific pattern, or for cross-cutting concerns not addressed in docs, fall back to sibling comparison:\n\n1. **New method on existing class/interface**: Grep for the most similar existing method on the same class using `-A 80` to capture the full method body (e.g., `UpdateCurrentUserAsync` → grep for `UpdateAsync` in the same file with `-A 80`). Compare line by line for missing cross-cutting concerns: notifications/events, validation, scoping, authorization, error handling, audit logging.\n2. **New TS class**: Grep for siblings by base class (`extends {BaseClass}`) or by interface (`implements {Interface}`) or by name suffix (e.g., `CurrentUserController` → grep for `UserController`). Compare for missing concerns.\n3. **New CS class**: Grep for siblings by base class (`class {ClassName} : {BaseClass}`) or by interface (`class {ClassName} : {Interface}`) or by name suffix (e.g., `ManagementApiComposer` → grep for `ApiComposer`). Compare for missing concerns.\n\n**Important:** Sibling comparison validates cross-cutting concerns, but it must not override documented conventions. If a sibling deviates from documented patterns, that sibling is wrong — do not copy its deviation.\n\nStore your raw findings — they feed into step 7.\n\n### 5. Impact Analysis\n\n**Skip this step if PR scope is docs-only, test-only, or config-only.**\n\nFollow the procedure in `references/impact-analysis.md`.\n\n### 6. Breaking Changes Check\n\n**Skip this step if PR scope is docs-only or test-only. If config-only, only check for dependency version changes that could break consumers.**\n\nFollow the procedure in `references/breaking-changes.md`.\n\n### 7. Consolidate and Output Review\n\nMerge findings from step 4 (raw review), step 5 (impact analysis), and step 6 (breaking changes). For each finding, assign severity (Critical/Important/Suggestion) and verify it relates to changed code — not pre-existing issues. Before outputting, drop any finding about whitespace, blank lines, formatting, or comment wording — but keep findings about comments or code pitched at the wrong altitude (see the review output guidelines below). Then present the review in this exact format:\n\n```markdown\n## PR Review\n\n**Target:** `{target_branch}` · **Based on commit:** `{head_sha}`\n[If any skipped files, append: · **Skipped:** {skipped} files out of {total} total]\n[If step 3f classification is not \"Standard\", append: · **Classified as:** {classification}]\n\n[1–2 sentences: what this PR accomplishes , keep it as short as possible, only highlight the primary essence.]\n\n- **Modified public API:** {changed existing interfaces/types/classes/methods}\n  [Omit bullet if none]\n- **Affected implementations (outside this PR):** {interfaces/types/classes/methods using modified public API}\n  [Omit bullet if none]\n- **Breaking changes:** {violations with specifics}\n  [Omit bullet if none]\n- **Other changes:** {changes not listed above that an Umbraco user, plugin developer, or API consumer would notice — e.g., behavior changes, default value changes, error message changes, new configuration options, removed functionality. Exclude internal renames, formatting, and private implementation details.}\n  [Omit bullet if none]\n\n[If step 3e triggered any dimensions, insert this block. Omit entirely if nothing triggered:]\n\n> [!NOTE]\n> **Complexity advisory** — This PR may benefit from splitting.\n>\n> - **{Dimension}:** {Explanation and concrete split suggestion from step 3e}\n>   [one bullet per triggered dimension]\n>\n> _This is an observation, not a blocker. The full review follows below._\n\n---\n\n### Critical\n\n[Must fix before merge — security vulnerabilities, data loss, broken functionality, breaking changes without proper patterns]\n\n- **`{file}:{line}`**: {problem} → {fix}\n\n[Omit section if none]\n\n### Important\n\n[Should fix — performance issues, missing tests, architectural violations, pattern misuse]\n\n- **`{file}:{line}`**: {observation} → {suggestion}\n\n[Omit section if none]\n\n### Suggestions\n\n[Nice to have — readability, minor refactoring, alternative approaches]\n\n- **`{file}:{line}`**: {detail}\n\n[Omit section if none]\n\n---\n\n[One of:]\n\n## Approved\n\nThis looks good to be merged as-is, but please do a manual sanity check and testing before merging.\n\n## Approved with Suggestions for improvement\n\nGood to go, but please carefully consider the importance of the suggestions.\n\n## Request Changes\n\nCritical and important issues must be addressed first.\n\n## Needs re-work\n\nThis is in such a bad state that the feedback of this review is not sufficient to guide improvements, the PR cannot be approved.\n```\n\n**Guidelines for the review output:**\n\n— When reporting information, be extremely concise and sacrifice grammar for sake of concision.\n\n- Only review code that was changed in the diff — pre-existing issues are out of scope. Focus on what compilers and linters cannot catch: behavioral side-effects (e.g., a changed default alters runtime behavior for consumers), architectural violations (e.g., a new dependency breaks layering), breaking changes for external consumers of the public API, and security implications. Leave type errors, missing imports, and broken references to CI.\n- Be specific — always reference file and line number\n- Explain WHY something is an issue, not just WHAT, but avoid stating the obvious.\n- For complex matters, provide concrete fix suggestions, including code snippets when helpful\n- Keep it constructive — the goal is to help, not gatekeep\n- Don't repeat the same finding for every occurrence — mention it once and note \"same pattern in {other files}\"\n- Focus on substantive issues only. Do NOT flag purely cosmetic or stylistic concerns. Specifically, never flag: code formatting or whitespace, comment grammar or wording, redundant-but-harmless syntax (e.g., optional chaining after a truthiness check), code duplication that doesn't cause bugs, or HTML template cosmetics. The only exception is when a stylistic issue has a concrete impact on performance or rendering. Note: missing JSDoc/documentation on public or exported APIs is a substantive finding (per coding preferences), not a cosmetic one — flag it as a Suggestion. A comment, doc, or branch inside a shared/generic layer that names a specific editor alias, entity type, or manifest type instead of describing the contract (root `CLAUDE.md` §2, General-Purpose by Default) is likewise substantive, not stylistic — flag it as a Suggestion.\n- For breaking changes, reference the specific pattern from the CLAUDE.md that should be applied\n- Do not suggest changes that would themselves introduce breaking changes. If a suggestion would alter public API surface (e.g., changing return types, renaming public members), it is not appropriate for a PR targeting `main` within a major version. Only suggest non-breaking alternatives.\n\n<!-- File: .claude/skills/umb-update-openapi/SKILL.md (Tokens: ~2433 | Category: anthropic-skill) -->\n---\nname: umb-update-openapi\ndescription: Regenerate the Management API OpenApi.json for Umbraco CMS from a running backend, and optionally regenerate the backoffice hey-api client so its generated types match. Proactively offer to run this at the end of any work that adds, removes, or changes Management API controllers, endpoints, routes, or their request/response/view models — OpenApi.json and the generated backend-api client go stale the moment the API surface changes, so suggest syncing rather than waiting to be asked. Also use it whenever the user asks to update/refresh/regenerate OpenApi.json or the openapi document, says the generated backend-api / hey-api client is stale or out of date, or has just added an endpoint and needs the client types — including phrasings like \"sync the OpenAPI spec\", \"update the API client\", \"the openapi doc is out of date\", or \"regenerate the server API\", even if OpenApi.json isn't named explicitly. Does NOT apply to Delivery API changes or to internal service/repository edits that don't alter the Management API surface.\n---\n\n# Update Management API OpenApi.json (Umbraco CMS)\n\nRefresh `src/Umbraco.Cms.Api.Management/OpenApi.json` **byte-for-byte** from a running backend's OpenAPI document, then optionally regenerate the backoffice hey-api client so its generated types match.\n\nThis replaces the old manual routine (run the app, open OpenAPI UI, copy JSON, paste, fix formatting). The point of automating it is that a byte-for-byte fetch from the canonical endpoint is deterministic — there's no editor reformatting to strip out, and repeated runs produce identical output.\n\n**Run from the repository root** (the directory containing `umbraco.sln`). Bash's working directory persists between calls, but `cd` can trigger a permission prompt — prefer paths relative to the repo root over `cd`.\n\n## Key facts\n\n- **OpenAPI endpoint:** `/umbraco/openapi/management.json`, served over HTTPS with a dev cert. OpenApi is only mapped when the environment is **not Production** — a backend running in Production returns 404 here even though it's healthy.\n- **Port:** default **`44339`** (the `launchSettings.json` https profile). Assume it unless the user says otherwise.\n- **Target file:** `src/Umbraco.Cms.Api.Management/OpenApi.json` — git-tracked, so git is the safety net for a bad fetch.\n- **The mechanical work is a script, not this document.** `npm run generate:openapi` fetches, validates and writes the spec; `npm run generate:server-api` then regenerates the client **from the committed file**. Prefer these over hand-rolled `curl`.\n- **Keep those two commands separate — never chain them into one.** The client generator reads the committed `OpenApi.json` and only that. Pointing it at a live server instead has been tried and removed (b8c2a3366fd): it broke base-url handling, and worse, it produced a client with no matching committed schema. Schema and client belong in the same commit.\n- **This skill exists for the parts a script can't do:** knowing _when_ to run it, managing the backend lifecycle, and reading the diff.\n\n## Procedure\n\n### 1. Confirm the target exists\n\nVerify `src/Umbraco.Cms.Api.Management/OpenApi.json` is present. If it isn't, you're likely not at the repo root — stop and say so rather than writing a file in the wrong place.\n\n### 2. Make sure a backend is running on 44339\n\n> **The fetched document reflects the _running_ build, not the working tree.** If Management API code changed since the server started, a still-running instance serves a stale spec — you'll get a byte-for-byte-valid file that's silently missing your latest endpoints. If the user changed the API, make sure the instance was (re)built/restarted after those changes; if in doubt, restart it.\n>\n> **Not every endpoint appears in the document.** Controllers marked `[ApiExplorerSettings(IgnoreApi = true)]` are excluded by design — notably the back-office _security_ endpoints (`BackOfficeController`: login, token, sign-out, authorize, keep-alive). So the absence of, say, a new `keep-alive` endpoint is expected, not a sign of a stale build. Don't use \"is my new endpoint in the doc?\" as a freshness check unless you've confirmed that controller is actually exposed to the API explorer.\n\nProbe for a live document (short timeout, accept the dev cert):\n\n```bash\nCODE=$(curl -sk --max-time 5 -o /dev/null -w \"%{http_code}\" \"https://localhost:44339/umbraco/openapi/management.json\")\n```\n\n- **`200`** → a backend is already up. Reuse it; do **not** stop it afterward (the user had it running).\n- **`000`** → nothing is listening on 44339. Start one yourself, in the background, on 44339, and remember that _you_ started it so you can stop it in step 5. No `--no-build` here on purpose, so a cold repo still works.\n- **anything else** → something is already listening on 44339 but isn't serving the expected OpenAPI document. Don't start another instance on the same port; surface the HTTP status and stop. A `404` from an otherwise healthy Umbraco is the common one — that's a backend running in Production, where OpenApi isn't mapped. Say so, and let the user decide whether to restart it in Development rather than restarting it for them.\n\nFor the `000` case, start the backend with:\n\n```bash\ndotnet run --project src/Umbraco.Web.UI --no-launch-profile -- \\\n  --environment Development --urls https://localhost:44339\n```\n\nRun that with `run_in_background: true` — that also puts the process under the harness's control so step 5 can stop it cleanly. First-run startup (build + boot) can take a couple of minutes — the fetch in step 3 waits for it.\n\n### 3. Fetch byte-for-byte straight into the file\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:openapi -- --wait\n```\n\nThe script writes the raw response body straight into the target — that's the byte-for-byte copy — and only after it parses as JSON, so a failed run leaves `OpenApi.json` untouched rather than restoring it after the fact. `--wait` keeps retrying while nothing is listening yet, so a backend you started in step 2 is picked up the moment it's up (no `sleep` loops); against a server that was already running it returns straight away.\n\nPass a URL as the first argument if the user is on a non-default port.\n\nA non-zero exit is a failed run — report the script's own message rather than retrying. The usual causes are a backend in Production (OpenApi unmapped), a non-`Run` runtime level mid-install/upgrade, or a different app on the port.\n\n### 4. Show the diff\n\nSurface exactly what changed so the user can sanity-check it:\n\n```bash\ngit diff --stat -- src/Umbraco.Cms.Api.Management/OpenApi.json\n```\n\n- **No diff** → the spec is already up to date; say so. **This does not mean the generated client is in sync** — a previous spec change may never have been followed by a regen, so step 6 still applies.\n- **A focused diff** → summarize the changed schemas/paths at a high level.\n- **A whole-file reformat** → flag it. Byte-for-byte is intentional, but a run that rewrites the _entire_ file means the server's serialization differs from what was committed. That's the canonical format going forward and is fine — just make sure the user knows it's a formatting shift, not hundreds of real API changes.\n\n### 5. Stop the backend if you started it\n\nOnly if **you** started it in step 2 (it wasn't already running) — if the probe returned `200`, skip this step and leave the user's server alone.\n\nThe instance you launched in step 2 ran with `run_in_background: true`, so the harness owns it as a tracked background task. Stop _that task_ directly, using the background shell's own stop — not a port scan. This is fully cross-platform (no `lsof`, which isn't installed on Windows) and can only ever stop the process you started, never an unrelated one that happens to be bound to 44339.\n\n### 6. Regenerate the backoffice client to check it's in sync\n\nThe generated hey-api client is downstream of `OpenApi.json`, but it can drift **independently** of it: a previous spec change that was never followed by a regen leaves a stale client even when step 4 shows no spec diff. So don't gate this on step 4 — regenerating is the only way to know the committed client actually matches the current spec. The generator is deterministic and git-recoverable, so running it against an already-synced client is a no-op (empty diff).\n\nGet the user's agreement before running it — it rewrites generated sources and can be noisy. **Ask once, not twice.** If you got here from a proactive offer (you suggested the sync yourself at the end of some Management API work), the client regen belongs in that same offer — \"shall I sync `OpenApi.json` and the generated client?\" — and a yes covers this step. Only ask separately if you're mid-run and it genuinely hasn't come up yet:\n\n> \"Want me to regenerate the backoffice hey-api client (`npm run generate:server-api`) to confirm it's in sync with `OpenApi.json`? It's a no-op if it already matches.\"\n\nIf yes:\n\n```bash\nnpm --prefix src/Umbraco.Web.UI.Client run generate:server-api\ngit diff --stat -- src/Umbraco.Web.UI.Client/src/packages/core/backend-api/\n```\n\nRead the resulting diff honestly:\n\n- **No diff** → the client is in sync; done.\n- **A diff even though step 4 showed no spec change** → the client had drifted (a prior spec update wasn't regenerated). Surface it — this is exactly the case worth catching, and the regen you just ran fixes it.\n- **A diff matching this run's spec changes** → expected; summarize it.\n\nIf the user declines, note the client's sync status is now unverified against `OpenApi.json`.\n\n## Notes\n\n- **Don't reformat the fetched JSON.** The user asked for byte-for-byte; the endpoint is the source of truth. If diffs ever look noisy from formatting alone, that's a decision to make with the user (e.g. a normalization pass) — not something to do silently.\n- **JSON-valid does not mean fresh.** A well-formed response from a pre-change build is still adopted (see step 2). When correctness matters more than speed, restart the backend so it reflects current source before fetching.\n- This skill talks to a live server and, optionally, mutates generated sources — it is not read-only, but it is safe to re-run and git-recoverable. It's idempotent when the spec hasn't changed.\n- If the port isn't reachable and starting the backend fails (build errors, DB not configured), surface the `dotnet run` output rather than retrying blindly — the fix is in the app, not the skill.\n\n<!-- File: .claude/skills/umb-update-server-dependencies-for-minor/SKILL.md (Tokens: ~2016 | Category: anthropic-skill) -->\n---\nname: umb-update-server-dependencies-for-minor\ndescription: Update all server-side (NuGet) dependencies to their latest minor/patch versions for the current branch's version line, then open a PR. Runs dotnet-outdated, bumps minors and patches only (never majors; never moves a stable package onto a pre-release), syncs the template projects, builds and runs unit tests, commits, and creates a PR. Use when asked to update/bump backend/server/NuGet/.NET dependencies for a release (e.g. \"update server dependencies for 17.6\", \"bump NuGet packages to latest minors\").\nargument-hint: [base-branch] (optional; defaults to the current branch)\n---\n\n# Update Server-Side Dependencies (minor & patch) - Umbraco CMS\n\nBumps every NuGet dependency that is behind to its latest available **minor or patch** version for a single version line (the current branch), then builds, tests, commits and opens a PR.\n\n**Scope: one version line at a time.** This skill operates on the branch that is currently checked out (or the branch given as `$ARGUMENTS`). To update multiple release lines (e.g. `v17/dev` and `main`), run it once per line.\n\n**Guardrails (non-negotiable):**\n- **Never** update a major version. Only minors and patches where a newer one exists.\n- **Never** move a stable package onto a pre-release. Packages already on a pre-release *may* be updated to a newer pre-release — this is what `--pre-release Auto` does.\n- **Never** update packages explicitly held back for compatibility (e.g. marked \"HOLD\"/\"do not bump\" in `Directory.Packages.props`). `TODO` comments there may also mark transitive security pins, which are in-scope to bump if needed (see step 3).\n\n## Prerequisites\n\n- `dotnet-outdated-tool` installed globally: `dotnet tool install --global dotnet-outdated-tool` (verify with `dotnet outdated --version`).\n- `gh` CLI authenticated (`gh auth status`).\n\n## Where versions live\n\n| File | Scope |\n|------|-------|\n| `Directory.Packages.props` (root) | Production packages + `GlobalPackageReference` build tooling + transitive security pins |\n| `tests/Directory.Packages.props` | Test-only packages (inherits the root file) |\n| `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj` | Inline versions (opts out of central management) |\n| `templates/UmbracoExtension/Umbraco.Extension.csproj` and its `Directory.Packages.props` | **Not in `umbraco.sln`** — dotnet-outdated will NOT touch these; sync them by hand (step 4) |\n\n## Instructions\n\n### 1. Establish the branch\n\nRead `version.json` and take the major version (e.g. `18` from `18.1.0-rc`) — call it `{major}`.\n\nIf the current branch is a long-lived line (`main`, `v{major}/dev`, a release branch) rather than a task branch, create and switch to a new branch:\n\n```bash\ngit switch -c v{major}/task/update-backend-dependencies\n```\n\n(If `$ARGUMENTS` names a base branch, branch from that instead.) If you are already on a suitable task branch, stay on it. Confirm the working tree is clean before starting.\n\n### 2. Discover what is outdated\n\nRun dotnet-outdated restricted to within the current major. `--version-lock Major` keeps updates inside the current major version; `--pre-release Auto` (the default) offers a pre-release upgrade **only when the package is already on a pre-release** — so a stable package never jumps to a pre-release, while an existing pre-release can move to a newer pre-release:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto umbraco.sln\n```\n\nReview the report. Everything listed is a legitimate minor/patch (or same-track pre-release) candidate. Note anything you intend to skip, and verify afterwards (via `git diff`) that no intentionally pinned/held versions were changed; revert any that were.\n\n### 3. Apply the updates\n\nApply the same filter with `-u`:\n\n```bash\ndotnet outdated --version-lock Major --pre-release Auto -u umbraco.sln\n```\n\n`dotnet-outdated` restores per-package and may print `Failed to restore project after upgrading!` for the test projects — this is a known quirk with the multi-level `tests/Directory.Packages.props` merge; the version writes still land. Verify with `git diff` afterwards.\n\nThis updates the two `Directory.Packages.props` files, the inline `Umbraco.Web.UI.csproj` versions, and any in-solution project. It also handles `GlobalPackageReference` entries (e.g. `Nerdbank.GitVersioning`).\n\nYou can also update transitive **security pins** if a new minor or patch is available.\n\n### 4. Sync the template projects by hand\n\n`templates/UmbracoExtension/` is not part of `umbraco.sln`, so its versions are not updated automatically. For any package that (a) was bumped in the root `Directory.Packages.props` **and** (b) appears in the template, update it to match:\n\n```bash\ngrep -rnE \"Version=\\\"[0-9]\" templates/UmbracoExtension/\n```\n\nTypically this is `Microsoft.AspNetCore.OpenApi` and/or `Swashbuckle.AspNetCore*` in both `templates/UmbracoExtension/Umbraco.Extension.csproj` and `templates/UmbracoExtension/Directory.Packages.props`. Keep them aligned with the root file.\n\n### 5. Build and run unit tests\n\nBuild the full solution in Release, skipping the frontend/npm steps (these are gated for CI/non-VS builds):\n\n```bash\ndotnet build umbraco.sln -c Release -p:UmbracoBuild=true\n```\n\nThen run the unit test suite:\n\n```bash\ndotnet test tests/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj -c Release -p:UmbracoBuild=true --no-build\n```\n\nBoth build and tests must be green before continuing.\n\nConfirm the bumps did not leave (or introduce) a known-vulnerable transitive version:\n\n```bash\ndotnet list umbraco.sln package --vulnerable --include-transitive\n```\n\nPre-existing, unrelated advisories are out of scope for a routine bump — but if a bump you made is the cause, resolve it (raise the pin, or add an inline reference for `Umbraco.Web.UI`).\n\n### 6. Review and commit\n\nConfirm `git status` shows only the intended package files (the two `Directory.Packages.props`, `Umbraco.Web.UI.csproj`, and any template files) — no stray build output, logs, or the schema placeholder. Stage those files explicitly (do not `git add -A`) and commit:\n\n```\nUpdate NuGet packages to latest minor and patch versions\n\nBump all NuGet dependencies that were behind to their latest available\nminor or patch release, as reported by dotnet-outdated. No major-version\nupdates are included.\n```\n\n### 7. Push and open the PR\n\n```bash\ngit push -u origin <branch>\n```\n\nGenerate the PR body from the actual `git diff` of the package files (do not invent versions). Use tables grouping the changes, and keep the **Testing** section to a single line — CI is the source of truth, do not paste build/test counts.\n\nFill in the template below and **write it to a temp file** (e.g. `/tmp/pr-body.md`), then create the PR against the line's dev branch (`v{major}/dev` for a v-line, `main` for the current major):\n\n```bash\ngh pr create --base <dev-branch> --head <branch> \\\n  --title \"Dependencies: Update NuGet packages to latest minor and patch versions\" \\\n  --body-file /tmp/pr-body.md\n```\n\n**PR body template:**\n\n```markdown\n## Description\n\nRoutine dependency maintenance for the **{version}** release. Updates all NuGet packages that were behind to their latest available **minor or patch** version, as reported by `dotnet-outdated`. No major-version updates are included.\n\n### Production packages (`Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Test packages (`tests/Directory.Packages.props`)\n\n| Package | From | To |\n|---|---|---|\n| … | … | … |\n\n### Inline / template versions\n\n- `src/Umbraco.Web.UI/Umbraco.Web.UI.csproj`: …\n- `templates/UmbracoExtension`: …\n\n### Deliberately left unchanged\n\n- Any package where only a **major** update is available (out of scope for this PR).\n- Intentionally-held packages carrying a `HOLD`/`do not bump` comment (e.g. `Umbraco.Code`).\n\n## Testing\n\nSolution should build and CI checks pass.\n```\n\n### 8. Report\n\nOutput the PR URL, the branch name, and a short summary of what was bumped (and anything deliberately skipped or any pin that had to be raised).\n\n## Notes\n\n- If `dotnet-outdated` reports nothing outdated, say so and stop — do not create an empty PR.\n- No `OpenApi.json` regeneration is needed for pure dependency bumps (no Management API controllers/models change).\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/core-add-module/SKILL.md (Tokens: ~1083 | Category: anthropic-skill) -->\n---\nname: core-add-module\ndescription: Add a new module to the core package (src/packages/core/). Use when adding shared UI framework infrastructure that all packages can use — e.g., a new extension system feature, a new utility, a new shared component pattern, a new picker, or a new workspace primitive. Core modules are imported as @umbraco-cms/backoffice/{module-name}. Also use when the user says things like \"add a module to core\", \"create a new core feature\", or \"add shared infrastructure\".\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Core Module\n\nAdd a new module to the core package (`src/packages/core/`).\n\n## Naming conventions\n\n- **Module names** are **singular** (e.g., `picker`, `sorter`, `entity-flag`, `modal`, `section`)\n\n## What you need from the user\n\n1. **Module name** — What to call it in singular form (kebab-case, e.g., `picker`, `sorter`, `entity-flag`)\n2. **Purpose** — What shared infrastructure this provides\n\n## When to add to core vs. create a new package\n\n- **Core module**: Shared UI framework infrastructure used by multiple packages — extension primitives, pickers, validation, repository base classes, workspace utilities.\n- **New package**: Domain-specific CMS features (documents, media, webhook). Use the `create-package` skill instead.\n\nCore modules should not implement CMS-specific features. They provide the building blocks that CMS feature packages use.\n\n## Files to create\n\n```\nsrc/packages/core/{module-name}/\n├── index.ts                   # Public API exports\n├── manifests.ts               # Extension registrations (if any)\n├── constants.ts               # Module constants\n└── types.ts                   # Module types\n```\n\n## Step 1: Create the module directory and files\n\n### {module-name}/index.ts\n\nThe public API. Only export what other packages need:\n\n```typescript\nexport * from './constants.js';\nexport type * from './types.js';\n```\n\nUse `export type *` for type-only exports.\n\n### {module-name}/manifests.ts\n\nExtension registrations for this module:\n\n```typescript\nexport const manifests: Array<UmbExtensionManifest> = [\n\t// Extension registrations\n];\n```\n\nIf the module has no extensions (pure utility), export an empty array.\n\n### {module-name}/constants.ts\n\n```typescript\n// Aliases, context tokens, entity types\n```\n\n### {module-name}/types.ts\n\n```typescript\n// Type definitions, interfaces, meta types\n```\n\n## Step 2: Wire into core's manifests.ts\n\nAdd the module's manifests to `src/packages/core/manifests.ts`:\n\n```typescript\nimport { manifests as {moduleName}Manifests } from './{module-name}/manifests.js';\n\n// In the manifests array:\n...{moduleName}Manifests,\n```\n\nThe imports are in alphabetical order — insert in the right position.\n\n## Step 3: Add entry point to core's vite.config.ts\n\nAdd the module to the `entry` object in `src/packages/core/vite.config.ts`:\n\n```typescript\n'{module-name}/index': './{module-name}/index.ts',\n```\n\nThe entries are in alphabetical order — insert in the right position. This tells Vite to build the module as a separate entry point.\n\n## Step 4: Register the subpath export\n\nAdd to the `exports` field in the root `package.json` (`src/Umbraco.Web.UI.Client/package.json`):\n\n```json\n\"./{module-name}\": \"./dist-cms/packages/core/{module-name}/index.js\"\n```\n\nThis enables imports like `@umbraco-cms/backoffice/{module-name}`.\n\n## Step 5: Regenerate TypeScript config\n\n```bash\nnpm run generate:tsconfig\n```\n\nThis updates `tsconfig.json` paths so the new module resolves correctly in TypeScript.\n\n## How consumers import\n\nAfter registration, other packages import from the module like:\n\n```typescript\nimport { MyThing } from '@umbraco-cms/backoffice/{module-name}';\nimport type { MyType } from '@umbraco-cms/backoffice/{module-name}';\n```\n\n## Reference: existing core modules to study\n\n- **Simple utility**: `src/packages/core/culture/` — repository + components, minimal surface\n- **Extension primitive**: `src/packages/core/entity-action/` — kinds, base classes, shared element\n- **Infrastructure**: `src/packages/core/repository/` — base classes for the data access pattern\n\n## Checklist\n\n- [ ] Module directory created under `src/packages/core/`\n- [ ] `index.ts` exports only the public API\n- [ ] `manifests.ts` exports extension registrations (or empty array)\n- [ ] `constants.ts` and `types.ts` created\n- [ ] Module manifests imported in `src/packages/core/manifests.ts` (alphabetical order)\n- [ ] Entry point added to `src/packages/core/vite.config.ts` (alphabetical order)\n- [ ] Subpath export added to root `package.json`\n- [ ] `npm run generate:tsconfig` executed\n- [ ] Compiles: `npm run compile`\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-add-localization/SKILL.md (Tokens: ~1300 | Category: anthropic-skill) -->\n---\nname: general-add-localization\ndescription: Add localization keys and use them in elements or controllers. Use when adding user-facing text that should be translatable — labels, descriptions, error messages, button text, status text, or any string shown in the backoffice UI.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Localization\n\nAdd translatable text to the Umbraco backoffice.\n\n## What you need from the user\n\n1. **The text to localize** — What the user sees (e.g., \"Create item\", \"Status\")\n2. **Which group it belongs to** — Feature area (e.g., `actions`, `general`, `content`, `user`)\n3. **Where it's used** — Element template, controller logic, or standalone component\n\n## Step 1: Add the key to the English dictionary\n\n**File:** `src/assets/lang/en.ts`\n\nThis file exports a `UmbLocalizationDictionary` — a nested object where top-level keys are groups and nested keys are the terms.\n\n```typescript\n// src/assets/lang/en.ts\nexport default {\n\t// ... existing groups\n\tmyFeature: {\n\t\tmyLabel: 'My Label',\n\t\tmyDescription: 'A description of the feature',\n\t\tcreateFor: (name: string) => (name ? `Create item for ${name}` : 'Create'),\n\t},\n} satisfies UmbLocalizationDictionary;\n```\n\n### Key naming rules\n\n- **Group**: camelCase feature name (e.g., `actions`, `general`, `content`, `media`, `user`)\n- **Term**: camelCase descriptive name (e.g., `assignDomain`, `auditTrail`, `browse`)\n- **Full key** used in code: `group_termName` (underscore separator)\n\n#### Choosing a group\n\nA group covers a **shared UX area**, not just the one component you're editing. Don't pick a group unilaterally — confirm it with the user:\n\n1. **If you already have a good idea of the scope** (from the surrounding code, the feature being worked on, etc.), propose it: \"This looks like it belongs to the `{scope}` scope — should that be the localization group?\"\n2. **If you don't**, ask directly: \"What is the common group name for this localization?\"\n3. **Either way, check for an existing match first.** Search the groups already in `src/assets/lang/en.ts` for one that already covers this UX area, and if you find one, ask whether it should be reused instead of creating a new group: \"`{existingGroup}` already covers this — should I use that instead?\"\n\nExamples already in this codebase:\n\n- `blockEditor` — Block List, Block Grid, and Block RTE share the same block-configuration UX\n- `contentTypeEditor` — Document Type, Media Type, and Member Type share the same editing UX\n- `codeEditor` — anything embedding the code editor\n\nOnly create a new group once the user confirms no existing one fits.\n\n#### Choosing a term\n\nA term names the **situation**, not the wording — two situations with identical English text today should still get separate terms, since the copy can diverge later.\n\nBuild it from two parts:\n\n1. **Subject**: `CreateBlock`, `ConfirmDelete`, `AddGroup`.\n2. **Presentation role**: `Title`, `Description`, `Action`, `Label`, `Notice`, `Message`, `ValidationMessage`, `Headline`, etc.\n\nCombined (subject + role suffix): `createAction`, `confirmDeleteTitle`, `addGroupDescription`.\n\nExample — three terms for one dialog in the `blockEditor` group, same subject (`confirmDeleteBlockGroup`) with different roles:\n\n```typescript\nconfirmDeleteBlockGroupTitle: 'Delete group?',\nconfirmDeleteBlockGroupMessage: 'Are you sure you want to delete group <strong>%0%</strong>?',\nconfirmDeleteBlockGroupNotice: 'The content of these Blocks will still be present, editing of this content will no longer be available and will be shown as unsupported content.',\n```\n\nGrouping by subject keeps every piece of the dialog together, even though the wording shares nothing.\n\n### Value types\n\n| Type               | Use when                 | Example                                           |\n| ------------------ | ------------------------ | ------------------------------------------------- |\n| `string`           | Static text              | `myLabel: 'My Label'`                             |\n| `(args) => string` | Text with dynamic values | `createFor: (name: string) => \\`Create ${name}\\`` |\n\n## Step 2: Use the localized text\n\n### In element templates — `this.localize.term()`\n\nAvailable on any class extending `UmbLitElement`. The `localize` property is provided automatically.\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-element')\nexport class UmbMyElement extends UmbLitElement {\n\toverride render() {\n\t\treturn html`<uui-button label=${this.localize.term('myFeature_myLabel')}></uui-button>`;\n\t}\n}\n```\n\n### In templates — `<umb-localize>` element\n\nFor inline localized text in HTML templates:\n\n```html\n<umb-localize key=\"myFeature_myLabel\"></umb-localize>\n\n<!-- With fallback text (shown if key is missing) -->\n<umb-localize key=\"myFeature_myLabel\">Fallback text</umb-localize>\n```\n\n### In controllers or non-element classes — `UmbLocalizationController`\n\n```typescript\nimport { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';\n\nexport class UmbMyManager extends UmbControllerBase {\n\treadonly #localization = new UmbLocalizationController(this);\n\n\tsomeMethod() {\n\t\tconst label = this.#localization.term('myFeature_myLabel');\n\t}\n}\n```\n\n## Checklist\n\n- [ ] Key added to `src/assets/lang/en.ts` in the correct group\n- [ ] Key follows `group_termName` convention (camelCase, underscore separator)\n- [ ] Used `this.localize.term()` in elements or `UmbLocalizationController` in non-elements\n- [ ] No hardcoded user-facing strings remain\n- [ ] Compiles: `npm run compile`\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-summary/SKILL.md (Tokens: ~1870 | Category: anthropic-skill) -->\n---\nname: general-add-value-summary\ndescription: Add a value summary extension so a feature's typed value can be displayed compactly in collection views (e.g., table columns). Use when a feature or property editor already has a value type and needs a renderer for collection table cells. Two variants — simple (element only, no API call) and resolver (batch API resolution needed). Prerequisite: a value type constant must already exist — use the general-add-value-type skill first if it doesn't.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Summary\n\nAdd a `valueSummary` extension so a feature's values are rendered in collection views.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Summary](../../../docs/value-summary.md)** — extension type, base classes, resolver contract, rules\n\n## Prerequisite\n\nA value type constant (`UMB_{FEATURE}_VALUE_TYPE`) must already exist and be exported from the package `index.ts`. If it doesn't exist yet, use the **`general-add-value-type`** skill first.\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type constant** — the existing constant to bind to (e.g., `UMB_COLOR_PICKER_VALUE_TYPE`)\n4. **Resolver needed?** — Yes if rendering requires an API call (resolving IDs to names). No if the raw value can be formatted directly.\n\n**If resolver is needed, also ask:**\n5. **Resolved type** — the TypeScript type after resolution (e.g., `UmbUserGroupItemModel`)\n6. **Repository to use** — which item/detail repository resolves the values\n\n---\n\n## Option A: Simple summary (no resolver)\n\nUse when the raw value can be formatted or rendered without an API call — dates, booleans, strings, color hex codes, numeric ranges, etc.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n└── {feature}-value-summary.element.ts\n```\n\n### Step 1: Create the summary element\n\nFile: `{package-path}/value-summary/{feature}-value-summary.element.ts`\n\n```typescript\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbValueSummaryElementBase } from '@umbraco-cms/backoffice/value-summary';\n\n@customElement('umb-{feature}-value-summary')\nexport class Umb{Feature}ValueSummaryElement extends UmbValueSummaryElementBase<{TValue}> {\n\toverride render() {\n\t\tif (!this._value) return html``;\n\t\treturn html`<span>${this._value}</span>`;\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryElement as element };\n\ndeclare global {\n\tinterface HTMLElementTagNameMap {\n\t\t'umb-{feature}-value-summary': Umb{Feature}ValueSummaryElement;\n\t}\n}\n```\n\nAdjust `render()` for the value type:\n- Dates → `this.localize.dateTime(value)`\n- Booleans → `<umb-icon>`\n- References → tags or comma-separated names\n\n### Step 2: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t},\n];\n```\n\n### Step 3: Wire manifests into parent\n\n```typescript\nimport { manifests as valueSummaryManifests } from './value-summary/manifests.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t...valueSummaryManifests,\n\t// ... other manifests\n];\n```\n\n### Simple summary checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase` and exports as `element`\n- [ ] Manifest uses `kind: 'default'`, correct `forValueType`, lazy `element` import\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Option B: Summary with resolver (batch API resolution)\n\nUse when displaying the value requires fetching data — e.g., resolving reference uniques to display names. The resolver receives all values from the entire column in one batch call.\n\n### Files to create\n\n```\n{package-path}/value-summary/\n├── manifests.ts\n├── {feature}-value-summary.element.ts\n└── {feature}-value-summary.resolver.ts\n```\n\n### Step 1: Create the summary element\n\nSame pattern as Option A, Step 1. Pass `{TResolved}` as the generic instead of `{TValue}` — the element receives the resolved value in `this._value`, already correctly typed.\n\n### Step 2: Create the resolver\n\nFile: `{package-path}/value-summary/{feature}-value-summary.resolver.ts`\n\n```typescript\nimport type { UmbValueSummaryResolver } from '@umbraco-cms/backoffice/value-summary';\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\nimport { Umb{Entity}ItemRepository } from '../repository/item/{entity}-item.repository.js';\n\nexport class Umb{Feature}ValueSummaryResolver\n\textends UmbControllerBase\n\timplements UmbValueSummaryResolver<{TValue}, {TResolved}>\n{\n\t#repo = new Umb{Entity}ItemRepository(this);\n\n\tasync resolveValues(values: ReadonlyArray<{TValue}>) {\n\t\t// Deduplicate all IDs needed across the entire batch\n\t\tconst allUniques = [...new Set(values.flatMap((v) => /* extract IDs from v */))];\n\n\t\tconst { data: items, asObservable } = await this.#repo.requestItems(allUniques);\n\t\tif (!items) return { data: values.map(() => undefined as unknown as {TResolved}) };\n\n\t\t// Positional mapping — output must align with input\n\t\treturn {\n\t\t\tdata: values.map((v) => /* find resolved item(s) for v */),\n\t\t\tasObservable: asObservable\n\t\t\t\t? () => asObservable().pipe(map((latest) => values.map((v) => /* remap */)))\n\t\t\t\t: undefined,\n\t\t};\n\t}\n}\n\nexport { Umb{Feature}ValueSummaryResolver as valueResolver };\n```\n\nCritical rules:\n- `data` must have the **same length** as `values` and be **positionally aligned**.\n- Export the class as `valueResolver` (named export) — the loader checks for this name.\n- Provide `asObservable` only when the resolved data can change after initial load.\n\n### Step 3: Create the manifest\n\nFile: `{package-path}/value-summary/manifests.ts`\n\n```typescript\nimport type { UmbExtensionManifest } from '@umbraco-cms/backoffice/extension-api';\nimport { UMB_{FEATURE}_VALUE_TYPE } from '../value-type/constants.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'valueSummary',\n\t\tkind: 'default',\n\t\talias: 'Umb.ValueSummary.{Feature}',\n\t\tname: '{Feature} Value Summary',\n\t\tforValueType: UMB_{FEATURE}_VALUE_TYPE,\n\t\telement: () => import('./{feature}-value-summary.element.js'),\n\t\tvalueResolver: () => import('./{feature}-value-summary.resolver.js'),\n\t},\n];\n```\n\n### Step 4: Wire manifests into parent\n\nSame as Option A, Step 3.\n\n### Resolver checklist\n\n- [ ] Element extends `UmbValueSummaryElementBase`, renders resolved type, exports as `element`\n- [ ] Resolver extends `UmbControllerBase`, implements `UmbValueSummaryResolver<{TValue}, {TResolved}>`\n- [ ] `resolveValues()` returns `data` positionally aligned with `values` (same length, same order)\n- [ ] Resolver exported as `valueResolver` (named export)\n- [ ] Manifest includes `valueResolver: () => import(...)` lazy loader\n- [ ] `asObservable` included only if resolved data can change reactively\n- [ ] Manifests wired into parent `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n---\n\n## Collection wiring\n\n**Property editors**: no extra step needed. The document collection reads `editorAlias` from the server and matches it to your `forValueType`.\n\n**Generic table collection kind**: set `valueType` in the collection manifest column:\n\n```typescript\n{ field: 'myField', label: 'My Field', valueType: UMB_{FEATURE}_VALUE_TYPE }\n```\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-add-value-type/SKILL.md (Tokens: ~697 | Category: anthropic-skill) -->\n---\nname: general-add-value-type\ndescription: Add a new value type — a named, typed string constant that extends UmbValueTypeMap. Use when a feature needs to declare what type of value it holds so it can be referenced in collection columns or value summary manifests. Also use when a property editor needs to expose its value type for use in collection views.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Add Value Type\n\nAdd a new value type constant to `UmbValueTypeMap`.\n\n## Foundational documentation\n\nRead before proceeding:\n\n- **[Value Type](../../../docs/value-type.md)** — full reference, naming convention, rules\n\n## What you need from the user\n\n1. **Feature name** — kebab-case (e.g., `color-picker`, `user-group`)\n2. **Package path** — where the feature lives (e.g., `src/packages/property-editors/color-picker/`)\n3. **Value type key** — the string alias:\n   - For a property editor: use the schema alias (e.g., `'Umbraco.ColorPicker'`)\n   - For a domain type: use `Umb.ValueType.{Entity}.{Shape}` (e.g., `'Umb.ValueType.UserGroup.References'`)\n4. **TypeScript type** — the type of the raw value (e.g., `string`, `boolean`, `UmbReferenceByUnique[]`)\n\n---\n\n## Step 1: Create the constant file\n\nFile: `{package-path}/value-type/constants.ts`\n\n```typescript\nexport const UMB_{FEATURE}_VALUE_TYPE = '{value-type-key}' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_{FEATURE}_VALUE_TYPE]: {TValue};\n\t}\n}\n```\n\nRules:\n- The string value is always `as const`.\n- The `declare global` block is in the **same file** as the constant.\n- One constant per file — do not group multiple value types.\n\n### Property editor example\n\n```typescript\nimport type { UmbColorPickerPropertyEditorValue } from '../types.js';\n\nexport const UMB_COLOR_PICKER_VALUE_TYPE = 'Umbraco.ColorPicker' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_COLOR_PICKER_VALUE_TYPE]: UmbColorPickerPropertyEditorValue;\n\t}\n}\n```\n\n### Domain type example\n\n```typescript\nimport type { UmbReferenceByUnique } from '@umbraco-cms/backoffice/models';\n\nexport const UMB_USER_GROUP_REFERENCES_VALUE_TYPE = 'Umb.ValueType.UserGroup.References' as const;\n\ndeclare global {\n\tinterface UmbValueTypeMap {\n\t\t[UMB_USER_GROUP_REFERENCES_VALUE_TYPE]: UmbReferenceByUnique[];\n\t}\n}\n```\n\n---\n\n## Step 2: Export from the package index\n\nAdd to the package `index.ts`:\n\n```typescript\nexport * from './value-type/constants.js';\n```\n\n---\n\n## Checklist\n\n- [ ] Constant declared with `as const`\n- [ ] `declare global` block extends `UmbValueTypeMap` in the same file\n- [ ] Key follows naming convention (schema alias for property editors, `Umb.ValueType.{Entity}.{Shape}` for domain types)\n- [ ] TypeScript type is the correct raw value type\n- [ ] One constant per file\n- [ ] Constant exported from package `index.ts`\n- [ ] Compiles: `npm run compile`\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-condition/SKILL.md (Tokens: ~1537 | Category: anthropic-skill) -->\n---\nname: general-create-condition\ndescription: Create a new extension condition that controls when extensions are active. Use when you need to conditionally show or hide extensions based on application state (e.g., current section, user permissions, entity state, workspace context). Conditions are registered as extensions and referenced in manifest condition arrays.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Condition\n\nCreate a new extension condition that controls when extensions are active.\n\n## What you need from the user\n\n1. **What it checks** — What context or state determines the condition (e.g., current section, user role, entity state)\n2. **Config properties** — What parameters consumers provide (e.g., `match`, `oneOf`)\n3. **Context dependency** — Which context token provides the data to evaluate\n\n## Files to create\n\nPlace in a `conditions/` subdirectory of the relevant feature:\n\n```\nmy-feature/\n└── conditions/\n    └── my-condition/\n        ├── constants.ts                     # Condition alias constant\n        ├── types.ts                         # Config type + global declaration\n        ├── my-condition.condition.ts         # Condition controller\n        └── manifests.ts                     # Condition manifest\n```\n\n## Step 1: Define the alias constant\n\n```typescript\n// constants.ts\nexport const UMB_MY_CONDITION_ALIAS = 'Umb.Condition.MyCondition';\n```\n\n## Step 2: Define the config type\n\n```typescript\n// types.ts\nimport type { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport type { UmbConditionConfigBase } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MyConditionConfig extends UmbConditionConfigBase<typeof UMB_MY_CONDITION_ALIAS> {\n\t/**\n\t * The value to match against.\n\t * @example \"Umb.Section.Content\"\n\t */\n\tmatch?: string;\n\n\t/**\n\t * One or more values to match against (any match passes).\n\t * @example [\"Umb.Section.Content\", \"Umb.Section.Media\"]\n\t */\n\toneOf?: Array<string>;\n}\n\ndeclare global {\n\tinterface UmbExtensionConditionConfigMap {\n\t\tUmbMyConditionConfig: MyConditionConfig;\n\t}\n}\n```\n\n### Key rules\n\n- Extend `UmbConditionConfigBase<typeof ALIAS_CONSTANT>` — this types the `alias` property\n- The `declare global` block on `UmbExtensionConditionConfigMap` makes the config type-safe when used in extension manifests\n- Use a unique key in the map\n- Add JSDoc with `@example` — these feed into JSON schema generation\n\n### Common config patterns\n\n| Pattern | Use when |\n|---------|----------|\n| `match: string` | Single value match |\n| `oneOf: Array<string>` | Any of multiple values |\n| `allOf: Array<string>` | All values must match |\n| `noneOf: Array<string>` | None of the values match |\n| No config properties | Condition checks a boolean state (e.g., \"is trashed\", \"is admin\") |\n\n## Step 3: Implement the condition controller\n\n```typescript\n// my-condition.condition.ts\nimport type { MyConditionConfig } from './types.js';\nimport { UMB_MY_CONTEXT } from '../../my-context.token.js';\nimport type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';\nimport type { UmbConditionControllerArguments, UmbExtensionCondition } from '@umbraco-cms/backoffice/extension-api';\nimport { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';\n\nexport class UmbMyCondition\n\textends UmbConditionBase<MyConditionConfig>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<MyConditionConfig>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_MY_CONTEXT, (context) => {\n\t\t\tthis.observe(\n\t\t\t\tcontext.someObservable,\n\t\t\t\t(value) => {\n\t\t\t\t\tthis.permitted = this.#check(value);\n\t\t\t\t},\n\t\t\t\t'_observeConditionValue',\n\t\t\t);\n\t\t});\n\t}\n\n\t#check(value: string | undefined): boolean {\n\t\tif (!value) return false;\n\n\t\tif (this.config.match) {\n\t\t\treturn value === this.config.match;\n\t\t}\n\n\t\tif (this.config.oneOf) {\n\t\t\treturn this.config.oneOf.includes(value);\n\t\t}\n\n\t\treturn false;\n\t}\n}\n\nexport { UmbMyCondition as api };\n```\n\n### Key rules\n\n- Extend `UmbConditionBase<ConfigType>` — provides `permitted` property and `config` access\n- Implement `UmbExtensionCondition`\n- Constructor signature: `(host: UmbControllerHost, args: UmbConditionControllerArguments<ConfigType>)`\n- Set `this.permitted = true/false` — the base class notifies the extension system on change\n- Use `consumeContext()` + `observe()` for reactive conditions that update as state changes\n- Export as `api` (named export) so the class can be imported by the manifest\n- Use an explicit observer alias so re-evaluation replaces the previous subscription\n\n### For simple boolean conditions (no config)\n\n```typescript\nexport class UmbIsSomethingCondition\n\textends UmbConditionBase<UmbConditionConfigBase>\n\timplements UmbExtensionCondition\n{\n\tconstructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) {\n\t\tsuper(host, args);\n\n\t\tthis.consumeContext(UMB_SOME_CONTEXT, (context) => {\n\t\t\tthis.observe(context.isSomething, (value) => {\n\t\t\t\tthis.permitted = value === true;\n\t\t\t});\n\t\t});\n\t}\n}\n```\n\n## Step 4: Register the condition manifest\n\nConditions must load fast — always import the class directly (no lazy `() => import()`).\n\n```typescript\n// manifests.ts\nimport { UMB_MY_CONDITION_ALIAS } from './constants.js';\nimport { UmbMyCondition } from './my-condition.condition.js';\n\nexport const manifests: Array<UmbExtensionManifest> = [\n\t{\n\t\ttype: 'condition',\n\t\tname: 'My Condition',\n\t\talias: UMB_MY_CONDITION_ALIAS,\n\t\tapi: UmbMyCondition,\n\t},\n];\n```\n\nAdd to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## How consumers use the condition\n\nIn any extension manifest that supports conditions:\n\n```typescript\n{\n\ttype: 'workspaceAction',\n\talias: 'My.WorkspaceAction',\n\tname: 'My Action',\n\tconditions: [\n\t\t{\n\t\t\talias: 'Umb.Condition.MyCondition',\n\t\t\tmatch: 'some-value',\n\t\t},\n\t],\n}\n```\n\nAll conditions in the array must be `permitted` for the extension to be active. They are AND-ed together.\n\n## Checklist\n\n- [ ] Alias constant exported from `constants.ts`\n- [ ] Config type extends `UmbConditionConfigBase<typeof ALIAS>` with the alias constant\n- [ ] Config type declared on global `UmbExtensionConditionConfigMap`\n- [ ] Condition class extends `UmbConditionBase<ConfigType>` and implements `UmbExtensionCondition`\n- [ ] Constructor calls `super(host, args)` and sets up context observation\n- [ ] `this.permitted` is set reactively (via `observe()`) — not just once in the constructor\n- [ ] Condition manifest registered with `type: 'condition'`\n- [ ] Manifests bubbled up to the package-level `manifests.ts`\n- [ ] Compiles: `npm run compile`\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-extension-type/SKILL.md (Tokens: ~1113 | Category: anthropic-skill) -->\n---\nname: general-create-extension-type\ndescription: Create a new extension type for the Umbraco backoffice extension registry. Use when adding a new type of extension that can be registered via manifests (e.g., a new dashboard type, a new sidebar app type, a new toolbar extension type). Any package can define extension types.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Extension Type\n\nCreate a new extension type for the Umbraco backoffice extension registry.\n\n## What you need from the user\n\n1. **Type name** — The `type` string used in manifests (e.g., `'dashboard'`, `'searchProvider'`)\n2. **Purpose** — What this extension type does (determines which base interface to extend)\n3. **Meta shape** — What configuration the extension needs from consumers\n4. **Whether it needs conditions** — Should extensions of this type support conditional loading?\n5. **Whether it has an element, API, or both** — Does it render UI, provide logic, or both?\n\n## Files to create\n\nAll files go in the relevant feature directory (e.g., `src/packages/my-package/my-feature/`).\n\n```\nmy-feature/\n├── my-feature.extension.ts     # Type definition + global declaration\n├── my-feature-element.interface.ts  # Element interface (if element-based)\n└── types.ts                    # Meta interface (if needed)\n```\n\n## Step 1: Choose the base manifest interface\n\n| Your extension... | Extend from | Import |\n|---|---|---|\n| Renders a custom element | `ManifestElement<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Provides an API class (no UI) | `ManifestApi<T>` | `@umbraco-cms/backoffice/extension-api` |\n| Has both element and API | `ManifestElementAndApi<E, A>` | `@umbraco-cms/backoffice/extension-api` |\n| Needs conditional loading | Also extend `ManifestWithDynamicConditions<UmbExtensionConditionConfig>` | `@umbraco-cms/backoffice/extension-api` |\n\n## Step 2: Define the manifest interface\n\n```typescript\n// my-feature.extension.ts\nimport type { ManifestElement, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';\n\nexport interface MetaMyFeature {\n\tlabel?: string;\n\t// ... properties consumers configure via meta\n}\n\nexport interface ManifestMyFeature\n\textends ManifestElement<UmbMyFeatureElement>,\n\t\tManifestWithDynamicConditions<UmbExtensionConditionConfig> {\n\ttype: 'myFeature';\n\tmeta: MetaMyFeature;\n}\n\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature;\n\t}\n}\n```\n\n### Key rules\n\n- The `type` property must be a **string literal type** (not `string`)\n- The key in `UmbExtensionManifestMap` must be unique across the entire codebase\n- The `declare global` block is what makes the registry type-aware — without it, the type won't be recognized\n\n## Step 3: Define the element/API interface (if needed)\n\n```typescript\n// my-feature-element.interface.ts\nimport type { UmbElement } from '@umbraco-cms/backoffice/element-api';\n\nexport interface UmbMyFeatureElement extends UmbElement {\n\t// Properties/methods that implementations must provide\n}\n```\n\n## Step 4: If the type supports kinds\n\nAdd a union type for the base manifest plus kind-specific variants:\n\n```typescript\nexport interface ManifestMyFeatureButtonKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'button';\n\tmeta: MetaMyFeatureButtonKind;\n}\n\nexport interface MetaMyFeatureButtonKind extends MetaMyFeature {\n\ticon: string;\n}\n\n// Union all variants in the global declaration\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureButtonKind;\n\t}\n}\n```\n\n## Step 5: Export from the package barrel\n\nAdd the type export to the package's `index.ts`:\n\n```typescript\nexport type * from './my-feature/my-feature.extension.js';\n```\n\nUse `export type *` since these are type-only exports.\n\n## Step 6: Register manifests that use this type\n\nVerify the type works by creating a test manifest:\n\n```typescript\nconst manifest: UmbExtensionManifest = {\n\ttype: 'myFeature',\n\talias: 'Umb.MyFeature.Test',\n\tname: 'Test My Feature',\n\telement: () => import('./my-feature-test.element.js'),\n\tmeta: {\n\t\tlabel: 'Test',\n\t},\n};\n```\n\nTypeScript should provide autocomplete for `type` and validate `meta` against `MetaMyFeature`.\n\n## Checklist\n\n- [ ] Manifest interface extends the correct base (`ManifestElement`, `ManifestApi`, or `ManifestElementAndApi`)\n- [ ] `type` property is a string literal, not `string`\n- [ ] `declare global` block adds to `UmbExtensionManifestMap` with a unique key\n- [ ] Meta interface is defined separately and exported\n- [ ] Element/API interface is defined if the type renders UI or provides logic\n- [ ] If kinds are supported: kind-specific interfaces extend the base with literal `kind` property\n- [ ] Type is exported from the package's `index.ts`\n- [ ] Compiles: `npm run compile`\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-kind/SKILL.md (Tokens: ~1504 | Category: anthropic-skill) -->\n---\nname: general-create-kind\ndescription: Create a new extension kind — a reusable default implementation for an extension type. Use when multiple extensions of the same type share the same element/API implementation and only differ in configuration. Kinds reduce boilerplate by providing pre-built defaults that extensions customize via meta.\nallowed-tools: Read, Write, Edit, Grep, Glob\n---\n\n# Create Kind\n\nCreate a new extension kind — a reusable default implementation for an extension type.\n\n## What you need from the user\n\n1. **Which extension type** — The `type` this kind applies to (e.g., `'entityAction'`, `'section'`)\n2. **Kind name** — The `kind` string (e.g., `'delete'`, `'default'`, `'button'`)\n3. **What it provides** — Default element, API, or both\n4. **Meta defaults** — What meta values the kind pre-fills for consumers\n\n## When to create a kind\n\nCreate a kind when **multiple extensions of the same type share the same element/API implementation** and only differ in meta configuration. If the implementation is unique, a kind adds unnecessary indirection.\n\n## Files to create\n\n```\nmy-feature/\n├── kinds/\n│   └── my-kind/\n│       ├── my-kind.kind.ts              # Kind manifest definition\n│       ├── my-kind.element.ts           # Default element (if providing UI)\n│       ├── my-kind.api.ts               # Default API class (if providing logic)\n│       └── manifests.ts                 # Exports the kind manifest\n```\n\n## Step 1: Define the kind manifest\n\n```typescript\n// my-kind.kind.ts\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const UMB_MY_FEATURE_MY_KIND_MANIFEST: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.MyKind',\n\tmatchType: 'myFeature',\n\tmatchKind: 'myKind',\n\tmanifest: {\n\t\ttype: 'myFeature',\n\t\tkind: 'myKind',\n\t\telement: () => import('./my-kind.element.js'),\n\t\tapi: () => import('./my-kind.api.js'),\n\t\tweight: 1000,\n\t\tmeta: {\n\t\t\ticon: '',\n\t\t\tlabel: '',\n\t\t\t// ... defaults that consumers override\n\t\t},\n\t},\n};\n\nexport const manifest = UMB_MY_FEATURE_MY_KIND_MANIFEST;\n```\n\n### Key fields\n\n- **`type`**: Always `'kind'`\n- **`alias`**: Unique identifier. Convention: `Umb.Kind.{ExtensionType}.{KindName}`\n- **`matchType`**: The extension type this kind applies to\n- **`matchKind`**: The kind name that extensions reference\n- **`manifest`**: Partial manifest with defaults — any property here becomes the base for extensions using this kind\n\n## Step 2: Create the default element (if providing UI)\n\n```typescript\n// my-kind.element.ts\nimport { customElement, html } from '@umbraco-cms/backoffice/external/lit';\nimport { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';\n\n@customElement('umb-my-kind-element')\nexport class UmbMyKindElement extends UmbLitElement {\n\t// This element is shared by all extensions using this kind.\n\t// Use manifest meta for customization — not hardcoded values.\n\n\toverride render() {\n\t\treturn html`<!-- default rendering -->`;\n\t}\n}\n\nexport { UmbMyKindElement as element };\n```\n\n**Important:** Export as `element` (named export) — the lazy `import()` in the kind manifest resolves this name.\n\n## Step 3: Create the default API (if providing logic)\n\n```typescript\n// my-kind.api.ts\nimport { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';\n\nexport class UmbMyKindApi extends UmbControllerBase {\n\t// Shared logic for all extensions using this kind.\n\t// Access extension-specific config via the manifest meta.\n}\n\nexport { UmbMyKindApi as api };\n```\n\n## Step 4: Register the kind in manifests\n\n```typescript\n// manifests.ts\nimport { manifest as myKindManifest } from './my-kind.kind.js';\nimport type { UmbExtensionManifestKind } from '@umbraco-cms/backoffice/extension-registry';\n\nexport const manifests: Array<UmbExtensionManifest | UmbExtensionManifestKind> = [myKindManifest];\n```\n\nAdd this to the parent feature's `manifests.ts` so it bubbles up to the bundle.\n\n## Step 5: Add kind-specific type to the extension type (if not already done)\n\nIn the extension type definition file, add a variant interface for this kind:\n\n```typescript\nexport interface ManifestMyFeatureMyKind extends ManifestMyFeature {\n\ttype: 'myFeature';\n\tkind: 'myKind';\n\tmeta: MetaMyFeatureMyKind;\n}\n\nexport interface MetaMyFeatureMyKind extends MetaMyFeature {\n\ticon: string;\n\tlabel: string;\n}\n\n// Update the global declaration to include the kind variant\ndeclare global {\n\tinterface UmbExtensionManifestMap {\n\t\tumbMyFeature: ManifestMyFeature | ManifestMyFeatureMyKind;\n\t}\n}\n```\n\n## How consumers use the kind\n\nExtensions reference the kind and only specify what differs:\n\n```typescript\n{\n\ttype: 'myFeature',\n\tkind: 'myKind',\n\talias: 'My.Package.MyFeature.Specific',\n\tname: 'My Specific Feature',\n\tmeta: {\n\t\ticon: 'icon-heart',\n\t\tlabel: 'My Label',\n\t},\n}\n```\n\nThe registry merges the kind defaults with the extension manifest:\n- Extension properties **override** kind properties\n- `meta` is **shallow-merged** (extension meta extends kind meta, doesn't replace it)\n\n## Extending an existing kind\n\nA kind can build on another kind by spreading its manifest:\n\n```typescript\nimport { UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST } from '../default/default.kind.js';\n\nexport const manifest: UmbExtensionManifestKind = {\n\ttype: 'kind',\n\talias: 'Umb.Kind.MyFeature.Special',\n\tmatchType: 'myFeature',\n\tmatchKind: 'special',\n\tmanifest: {\n\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest,\n\t\ttype: 'myFeature',\n\t\tkind: 'special',\n\t\tapi: () => import('./special.api.js'),\n\t\tmeta: {\n\t\t\t...UMB_MY_FEATURE_DEFAULT_KIND_MANIFEST.manifest.meta,\n\t\t\t// additional meta defaults\n\t\t},\n\t},\n};\n```\n\n## Checklist\n\n- [ ] Kind manifest has `type: 'kind'` with unique `alias`\n- [ ] `matchType` matches the target extension type exactly\n- [ ] `matchKind` matches the `kind` string extensions will use\n- [ ] Default element exported as `element`, default API exported as `api` (named exports for lazy import)\n- [ ] Kind-specific manifest interface added to the extension type's `declare global` block\n- [ ] Kind registered in `manifests.ts` with type `Array<UmbExtensionManifest | UmbExtensionManifestKind>`\n- [ ] Meta defaults use empty strings or sensible fallbacks (not undefined)\n- [ ] Compiles: `npm run compile`\n- [ ] Blast radius considered: changing this kind affects all extensions that use it\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# general-create-package\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-package/SKILL.md)\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# general-create-repository\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-repository/SKILL.md)\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# general-create-workspace\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-create-workspace/SKILL.md)\n\n<!-- File: src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# general-deprecate-api\n\nPath: `src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/.claude/skills/general-deprecate-api/SKILL.md)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/block/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Block Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/block/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/block/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Clipboard Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/clipboard/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Code-editor Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/code-editor/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/content/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Content Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/content/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/content/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Icon-registry Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/icon-registry/icon-dictionary.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/core/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Core Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/core/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/core/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/data-type/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Data-type Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/data-type/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/data-type/package.json)\n\n<!-- File: src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Dictionary Skill\n\nPath: `src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/umbraco/Umbraco-CMS/HEAD/src/Umbraco.Web.UI.Client/src/packages/dictionary/package.json)\n\n<!-- ... +39 more modular skills available via /api/skills/umbraco/Umbraco-CMS?path={path} -->\n</yakaai_skills>"}