{"owner":"OrchardCMS","repo":"OrchardCore","hasSkills":true,"totalSkillsCount":54,"totalTokensCount":41136,"categories":["anthropic-skill","root-instruction","mcp-config","plugin-manifest"],"hasMcp":true,"mcpConfig":{"mcpServers":{"OrchardCore":{"command":"npx","args":["-y","@modelcontextprotocol/server-OrchardCore"]}}},"found":[".agents/skills/orchardcore-admin-edit-views/SKILL.md",".agents/skills/orchardcore-admin-index-views/SKILL.md",".agents/skills/orchardcore-data-migration/SKILL.md",".agents/skills/orchardcore-display-management/SKILL.md",".agents/skills/orchardcore-breadcrumbs/SKILL.md",".agents/skills/orchardcore-localization/SKILL.md",".agents/skills/orchardcore-nswag-regenerate/SKILL.md",".agents/skills/orchardcore-query-indexing/SKILL.md",".agents/skills/orchardcore-asset-manager/SKILL.md",".agents/skills/orchardcore-module-creator/SKILL.md",".agents/skills/orchardcore-recipe-creator/SKILL.md",".agents/skills/orchardcore-tenants/SKILL.md",".agents/skills/orchardcore-tester/SKILL.md",".agents/skills/orchardcore-theme-creator/SKILL.md",".agents/skills/orchardcore-unit-test/SKILL.md",".agents/skills/orchardcore-video-producer/SKILL.md",".agents/skills/orchardcore-workflow-activity/SKILL.md","AGENTS.md",".github/mcp.json",".vscode/mcp.json",".agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md",".agents/skills/orchardcore-admin-index-views/references/worked-example.md",".agents/skills/orchardcore-asset-manager/references/actions.md",".agents/skills/orchardcore-breadcrumbs/references/theming.md",".agents/skills/orchardcore-data-migration/references/patterns.md",".agents/skills/orchardcore-data-migration/references/schema-builder.md",".agents/skills/orchardcore-display-management/references/drivers.md",".agents/skills/orchardcore-display-management/references/placement.md",".agents/skills/orchardcore-docs-writer/references/docs-structure.md",".agents/skills/orchardcore-localization/references/localization.md",".agents/skills/orchardcore-module-creator/references/examples.md",".agents/skills/orchardcore-module-creator/references/module-structure.md",".agents/skills/orchardcore-module-creator/references/patterns.md",".agents/skills/orchardcore-query-indexing/references/indexing.md",".agents/skills/orchardcore-recipe-creator/references/recipe-structure.md",".agents/skills/orchardcore-recipe-creator/references/steps.md",".agents/skills/orchardcore-tenants/references/shells.md",".agents/skills/orchardcore-tester/references/admin-navigation.md",".agents/skills/orchardcore-tester/references/autosetup.md",".agents/skills/orchardcore-tester/references/common-features.md",".agents/skills/orchardcore-tester/references/debugging.md",".agents/skills/orchardcore-tester/references/playwright-cli.md",".agents/skills/orchardcore-tester/references/setup-wizard.md",".agents/skills/orchardcore-theme-creator/references/assets.md",".agents/skills/orchardcore-theme-creator/references/theme-structure.md",".agents/skills/orchardcore-unit-test/references/testing.md",".agents/skills/orchardcore-video-producer/references/recording.md",".agents/skills/orchardcore-video-producer/references/specs.md",".agents/skills/orchardcore-video-producer/references/storyboard.md",".agents/skills/orchardcore-workflow-activity/references/activities.md","src/docs/guides/integrate-facebook-plugins/README.md","src/docs/topics/local-nuget-packages/README.md",".agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json","src/docs/reference/modules/Rules/README.md"],"skills":{".agents/skills/orchardcore-admin-edit-views/SKILL.md":"---\nname: orchardcore-admin-edit-views\ndescription: Creates and updates OrchardCore admin edit views using the ocat-* CSS class conventions. Use when creating or modifying Edit view files (*.Edit.cshtml, *.Fields.Edit.cshtml) to ensure correct layout and styling.\nstatus: completed\n---\n\n# OrchardCore Admin Edit Views\n\nThis skill guides you through creating and updating admin edit view files using the `ocat-*` CSS class conventions required by the OrchardCore admin theme.\n\n## The `ocat-*` CSS Class System\n\nThe `ocat-*` CSS classes provide a standardized two-column layout for admin form fields. Every form field uses a consistent structure:\n\n- **Left column** – The field label (`ocat-label`)\n- **Right column** – The input, validation message, and hint (`ocat-end`)\n\n### Core Classes\n\n| Class | Element | Purpose |\n|-------|---------|---------|\n| `ocat-wrapper` | `<div>` | Outer container for a form field row (replaces `mb-3`/`form-group`) |\n| `ocat-label` | `<label>` | Styles the field label in the left column (replaces `form-label`) |\n| `ocat-label-required` | `<label>` | Add alongside `ocat-label` to mark a field as required |\n| `ocat-end` | `<div>` | Wraps the input, validation, and hint in the right column |\n| `ocat-end-offset` | `<div>` | Right-column content with no left label (e.g., checkboxes) |\n| `ocat-limited-wrapper` | `<div>` | Outer row for limited-width controls such as short text, numbers, selects, or small settings inputs |\n| `ocat-limited` | `<div>` | Constrains the control width inside `ocat-limited-wrapper` |\n\n## Choosing the Right Wrapper\n\nUse this decision guide when creating or updating admin edit views:\n\n| Scenario | Preferred Pattern |\n|----------|-------------------|\n| Standard full-width field row | `ocat-wrapper` + `ocat-label` + `ocat-end` |\n| Standalone checkbox/toggle row with no left label | `ocat-wrapper` + `ocat-end-offset` |\n| Standalone alert, notice, legend, or section headline in the form flow | `ocat-wrapper` + `ocat-end-offset` |\n| Top-level limited-width field row (small text, number, select, token, path, port, etc.) | `ocat-limited-wrapper` + `ocat-label` + `ocat-limited` |\n| Content part / content field editor that already needs wrapper classes like `field-wrapper-*` or `content-part-wrapper-*` | Keep the outer `ocat-wrapper`, then place `ocat-limited-wrapper` inside `ocat-end` |\n\n### Wrapper Selection Rules\n\n1. Use `ocat-wrapper` for the default admin edit-view row structure.\n2. Use `ocat-limited-wrapper` when the field should stay visually narrow instead of stretching the full editor width.\n3. If the outer row also needs Orchard-specific wrapper classes such as `field-wrapper-*` or `content-part-wrapper-*`, do **not** replace that outer row with `ocat-limited-wrapper`; keep the outer `ocat-wrapper` and nest `ocat-limited-wrapper` inside `ocat-end`.\n4. Do **not** use legacy helpers or Bootstrap-only layout classes like `mb-3`, `form-group`, `form-label`, `@Orchard.GetWrapperClasses()`, or `@Orchard.GetLimitedWidthWrapperClasses()`.\n5. Alerts, notices, `<legend>`, and section headings that are part of the edit form but have no left-column label should use their own `ocat-wrapper` + `ocat-end-offset` row.\n6. A single row should use **either** `ocat-end` **or** `ocat-end-offset`, never both.\n\n### Standard Field Structure\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label Text\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Descriptive hint text.\"]</span>\n    </div>\n</div>\n```\n\n## Field Type Templates\n\n### Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Required Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label ocat-label-required\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Code/Script Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n```\n\n### Textarea\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Content\">\n    <label asp-for=\"Content\" class=\"ocat-label\">@T[\"Content\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Content\" class=\"form-control\" rows=\"5\"></textarea>\n        <span asp-validation-for=\"Content\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Select / Dropdown\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"FieldName\" class=\"form-select\">\n            <option value=\"Option1\">@T[\"Option 1\"]</option>\n            <option value=\"Option2\">@T[\"Option 2\"]</option>\n        </select>\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Checkbox (No Left Label)\n\nUse `ocat-end-offset` when there is no label in the left column (e.g., for checkboxes):\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsEnabled\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsEnabled\" />\n            <label asp-for=\"IsEnabled\" class=\"form-check-label\">@T[\"Enable feature\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to enable this feature.\"]</span>\n    </div>\n</div>\n```\n\n### Headline / Alert Row (No Left Label)\n\nUse this for standalone form guidance, warnings, legends, or section headers:\n\n```html\n<div class=\"ocat-wrapper\">\n    <div class=\"ocat-end-offset\">\n        <h5>@T[\"Section heading\"]</h5>\n        <div class=\"alert alert-warning\" role=\"alert\">\n            @T[\"Important information for editors.\"]\n        </div>\n    </div>\n</div>\n```\n\n### Input Group (with prefix/suffix)\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Url\">\n    <label asp-for=\"Url\" class=\"ocat-label\">@T[\"URL\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"input-group\">\n            <span class=\"input-group-text\">https://</span>\n            <input asp-for=\"Url\" type=\"text\" class=\"form-control\" />\n        </div>\n        <span asp-validation-for=\"Url\"></span>\n        <span class=\"hint\">@T[\"The full URL.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Field Row\n\nUse this when the whole row is a compact admin field, such as a page size, port, callback path, or select list:\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"PageSize\">\n    <label asp-for=\"PageSize\" class=\"ocat-label\">@T[\"Page size\"]</label>\n    <div class=\"ocat-limited\">\n        <input asp-for=\"PageSize\" type=\"number\" class=\"form-control\" />\n        <span asp-validation-for=\"PageSize\"></span>\n        <span class=\"hint\">@T[\"The default page size.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Select Row\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"TimeZone\">\n    <label asp-for=\"TimeZone\" class=\"ocat-label\">@T[\"Default Time Zone\"]</label>\n    <div class=\"ocat-limited\">\n        <select asp-for=\"TimeZone\" class=\"form-select\">\n            <option value=\"\">@T[\"Local to server\"]</option>\n        </select>\n        <span asp-validation-for=\"TimeZone\"></span>\n        <span class=\"hint\">@T[\"Determines the default time zone.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Control Inside a Field Wrapper\n\nUse this when the editor already needs a content field or content part wrapper on the outer row:\n\n```html\n<div class=\"@($\"ocat-wrapper field-wrapper field-wrapper-{Model.PartFieldDefinition.PartDefinition.Name.HtmlClassify()}-{Model.PartFieldDefinition.Name.HtmlClassify()}\")\">\n    <label asp-for=\"Value\" class=\"ocat-label\">@T[\"Value\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"ocat-limited-wrapper\">\n            <div class=\"ocat-limited\">\n                <input asp-for=\"Value\" class=\"form-control\" />\n                <span asp-validation-for=\"Value\"></span>\n            </div>\n        </div>\n        <span class=\"hint\">@T[\"Use a compact editor width while preserving the field wrapper row.\"]</span>\n    </div>\n</div>\n```\n\n### Conditional Fields with JavaScript/Liquid Syntax Selector\n\nFor fields that support both JavaScript and Liquid syntax, use the syntax selector pattern with toggling `d-none`:\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Syntax\">\n    <label asp-for=\"Syntax\" class=\"ocat-label\">@T[\"Syntax\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Syntax\" class=\"form-select\">\n            <option value=\"@nameof(WorkflowScriptSyntax.JavaScript)\">@T[\"JavaScript\"]</option>\n            <option value=\"@nameof(WorkflowScriptSyntax.Liquid)\">@T[\"Liquid\"]</option>\n        </select>\n        <span asp-validation-for=\"Syntax\"></span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.Expression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.JavaScript ? \"\" : \"d-none\")\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.LiquidExpression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.Liquid ? \"\" : \"d-none\")\" asp-validation-class-for=\"LiquidExpression\">\n    <label asp-for=\"LiquidExpression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"LiquidExpression\" class=\"form-control code\" />\n        <span asp-validation-for=\"LiquidExpression\"></span>\n        <span class=\"hint\">@T[\"A Liquid expression. Example: {0}\", \"{{ Workflow.Properties.Value }}\"]</span>\n    </div>\n</div>\n\n<script at=\"Foot\">\n    document.addEventListener('DOMContentLoaded', () => {\n        const syntaxElement = document.getElementById('@Html.IdFor(m => m.Syntax)');\n        const javaScriptGroup = document.getElementById('@($\"{Html.IdFor(m => m.Expression)}_group\")');\n        const liquidGroup = document.getElementById('@($\"{Html.IdFor(m => m.LiquidExpression)}_group\")');\n\n        const toggleEditors = () => {\n            const useLiquid = syntaxElement.value === '@nameof(WorkflowScriptSyntax.Liquid)';\n            javaScriptGroup.classList.toggle('d-none', useLiquid);\n            liquidGroup.classList.toggle('d-none', !useLiquid);\n        };\n\n        syntaxElement.addEventListener('change', toggleEditors);\n        toggleEditors();\n    });\n</script>\n```\n\n## Display-Only Fields (Read-Only)\n\nFor displaying information without a form input:\n\n```html\n<div class=\"ocat-wrapper\">\n    <label class=\"ocat-label\">@T[\"Field Label\"]</label>\n    <div class=\"ocat-end\">\n        <span>@Model.FieldValue</span>\n    </div>\n</div>\n```\n\n## Migration: Old to New Classes\n\nWhen updating existing views, apply these replacements:\n\n| Old Pattern | New Pattern |\n|-------------|-------------|\n| `<div class=\"mb-3\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<div class=\"form-group\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<label ... class=\"form-label\">` | `<label ... class=\"ocat-label\">` |\n| Input/select/textarea directly inside wrapper | Wrap in `<div class=\"ocat-end\">` |\n| Checkbox with label inside wrapper | Wrap in `<div class=\"ocat-end-offset\"><div class=\"form-check\">` |\n| Standalone headline or alert directly in the form flow | Wrap in `<div class=\"ocat-wrapper\"><div class=\"ocat-end-offset\">...</div></div>` |\n| Mixing `ocat-end` and `ocat-end-offset` in one row | Choose one wrapper based on whether there is a left-column label |\n| `@Orchard.GetLimitedWidthWrapperClasses()` | `ocat-limited-wrapper` |\n| `@Orchard.GetLimitedWidthClasses()` | `ocat-limited` |\n| Limited-width content field row using `field-wrapper-*` | Keep outer `ocat-wrapper field-wrapper-*`, nest `ocat-limited-wrapper` inside `ocat-end` |\n\n## Complete Edit View Example\n\n```html\n@model MyModuleViewModel\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Title\">\n    <label asp-for=\"Title\" class=\"ocat-label ocat-label-required\">@T[\"Title\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"Title\" class=\"form-control\" autofocus=\"autofocus\" />\n        <span asp-validation-for=\"Title\"></span>\n        <span class=\"hint\">@T[\"The title of the item.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Description\">\n    <label asp-for=\"Description\" class=\"ocat-label\">@T[\"Description\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Description\" class=\"form-control\" rows=\"4\"></textarea>\n        <span asp-validation-for=\"Description\"></span>\n        <span class=\"hint\">@T[\"A brief description.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Category\">\n    <label asp-for=\"Category\" class=\"ocat-label\">@T[\"Category\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Category\" class=\"form-select\">\n            <option value=\"\">@T[\"-- Select --\"]</option>\n            <option value=\"A\">@T[\"Category A\"]</option>\n            <option value=\"B\">@T[\"Category B\"]</option>\n        </select>\n        <span asp-validation-for=\"Category\"></span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsPublished\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsPublished\" />\n            <label asp-for=\"IsPublished\" class=\"form-check-label\">@T[\"Published\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to publish this item.\"]</span>\n    </div>\n</div>\n```\n\n## References\n\n- `references/ocat-classes.md` - Detailed class reference with CSS context\n- Admin theme source: `src/OrchardCore.Themes/TheAdmin/`\n- Example usage: `src/OrchardCore.Modules/OrchardCore.Workflows/Views/Items/`\n- AGENTS.md (repo root) - Coding conventions and build commands\n",".agents/skills/orchardcore-admin-index-views/SKILL.md":"---\nname: orchardcore-admin-index-views\ndescription: Builds OrchardCore admin index (list) pages with the AdminList shape and the admin-list tag helpers — view-declared columns, row display drivers placing shapes for both the SummaryAdmin (List) and SummaryAdminGrid (Grid) display types, column providers, row actions, toolbar, search, pager and per-list alternates. Use for every new admin page that lists items, when converting a hand-written admin listing, adding or reordering columns of an admin list, extending another module's list, or changing how its rows and actions render.\n---\n\n# OrchardCore Admin Index Views\n\nEvery admin page that lists items (an \"index\" page: content items, users, queries, workflows, ...) renders its\nitems through the shared `AdminList` shape. Never hand-write a `<ul class=\"list-group\">`, a `<table>`, a toolbar,\na select-all checkbox, an item count, an empty state or a pager for an admin listing: the list renders all of\nthem, in the `List` or `Grid` layout the site and the user choose.\n\n## Ownership\n\n| Layer | Responsibility |\n| --- | --- |\n| Controller | Query the raw items and supply an `AdminListContext<TModel>` with the typed `Source`, pager, search, toolbar and other region shapes. Never build the list shape or the row shapes. |\n| View | Declare the list name, the grid columns, the CSS classes, the empty message and other presentation properties with `<admin-list>`, inside the page's form. |\n| Row display driver | Return ordinary `IDisplayResult` shapes, each with a `SummaryAdmin` location (List) and a `SummaryAdminGrid` location (Grid). Set optional row classes/attributes on `context.Shape`. |\n| Column provider | Optional. Extend the columns a view declares, e.g. from another module. |\n| `AdminList` | Resolve the layout at display time, build each row once with the display type of that layout, and render the whole listing. |\n\n`AdminListLayout` has exactly two values: `List` renders each `SummaryAdmin` row shape whole (its row template\ndecides the look); `Grid` renders the `SummaryAdminGrid` row zones directly, one cell per column, each cell\nrendering the zone named after its column. Layouts are not discovered or registered through templates.\nDo not add column or cell drivers, column metadata classes, or build both representations of a row.\n\n## Decide\n\n| Situation | Do this |\n| --- | --- |\n| New admin page listing items | Follow the workflow below. |\n| Rows already built with `BuildDisplayAsync(..., \"SummaryAdmin\")` and a hand-written list | Convert: move the row building out of the controller into `AdminListContext<TModel>.Source`, add the `SummaryAdminGrid` locations, then follow steps 3 to 6. |\n| Rows are hard-coded `<li>` markup in Razor | Write a display driver for the row model first, so a row is a zone-holding shape, then follow the workflow. |\n| Adding a column to another module's list | Step 5, plus a `SummaryAdminGrid` location in your driver. |\n| The page edits a single entity | Not this skill: use `orchardcore-admin-edit-views`. |\n| The page arranges items rather than listing them (a tree, a canvas, a media library, widgets by zone) | Not this skill: such pages keep their own presentation. |\n\n## Workflow\n\n### 1. Write the row display driver\n\nA row is the shape `IDisplayManager<TModel>` builds for the item. Its type is `typeof(TModel).Name` plus the\ndisplay type, e.g. `IndexProfile_SummaryAdmin`, so register the driver against a domain model named exactly what\nthe shape should be called, never a `*ViewModel`. Register it in the `Startup` of the feature that owns the list:\na driver registered by a neighbouring feature leaves every row empty on a site where that feature is off.\n\nGive every shape a location for each display type. The shape types and templates are the same; only the zone\ndiffers, a row zone for `List` and the zone of a column for `Grid`:\n\n```csharp\npublic override Task<IDisplayResult> DisplayAsync(IndexProfile profile, BuildDisplayContext context)\n{\n    // The row's classes and attributes are rendered on its row element, in both layouts.\n    if (context.DisplayType is OrchardCoreConstants.DisplayType.SummaryAdmin or OrchardCoreConstants.DisplayType.SummaryAdminGrid\n        && context.Shape is Shape rowShape)\n    {\n        rowShape.Classes.Add(\"item\");\n        rowShape.Attributes[\"data-filter-value\"] = profile.Name?.ToLowerInvariant() ?? string.Empty;\n    }\n\n    return CombineAsync(\n        View(\"IndexProfile_Checkbox_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Checkbox:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Select:1\"),\n        View(\"IndexProfile_Fields_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Content:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Name:1\"),\n        View(\"IndexProfile_DefaultTags_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Tags:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Source:5\"),\n        View(\"IndexProfile_Buttons_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Actions:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Actions:5\"),\n        View(\"IndexProfile_ActionsMenuItems_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"ActionsMenu:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"ActionsMenu:5\"));\n}\n```\n\n- Every visible piece of a row (checkbox, title, badges, dates, buttons, menu items) is its own shape in a zone.\n  Anything hard-coded in the row template is missing from the grid.\n- A location without a display type, `.Location(\"Tags:5\")`, applies to both display types, so in the grid the\n  shape lands in a `Tags` zone no column renders. Always pair a `SummaryAdmin` location with a `SummaryAdminGrid`\n  one, unless the zone name is also a column name (e.g. `Actions`).\n- Display types do not inherit placement: a shape placed only for `SummaryAdmin`, by a driver or by\n  `placement.json`, does not appear in the grid. A grid-only shape declares only a `SummaryAdminGrid` location.\n- `placement.json` rules filtered by `\"displayType\": \"SummaryAdminGrid\"` move a shape to another column zone\n  (`Source:10`), hide it (`-`), change its template or add wrappers and alternates.\n- Do not use editor `|Column` groupings for cells.\n\n### 2. Write the row template for the List layout\n\n`{Model}.SummaryAdmin.cshtml` renders the zones of the row; the grid does not use it. Every shipped row has the\nsame structure, so the lists look alike:\n\n```cshtml\n<div class=\"row g-0 align-items-center\">\n    <div class=\"col\">\n        <div class=\"title d-flex align-items-center\">\n            @* Checkbox, then <div class=\"summary d-flex flex-column flex-md-row list-item-search-text\"> with Content, Tags, Meta *@\n        </div>\n        @if (Model.Description != null)\n        {\n            @await DisplayAsync(Model.Description)\n        }\n    </div>\n    <div class=\"col-auto d-flex justify-content-end ps-2\">\n        @await DisplayAsync(await Factory.CreateAdminListActionsAsync((IShape)Model))\n    </div>\n</div>\n```\n\n| Zone | Renders as | Never |\n| --- | --- | --- |\n| `Content` | The title, a plain link to the edit page | A heading (`<h5>`) |\n| `Tags`, `Meta` | `<span class=\"badge ta-badge fw-normal\">` per fact, leading icon, `title` tooltip | Plain text or a `<span class=\"hint\">` |\n| `Description` | `<div class=\"admin-list-secondary\">`, its own line under the title | A second full-width row running under the actions |\n| `Actions`, `ActionsMenu` | Through `AdminListActions` (`CreateAdminListActionsAsync`), so the Buttons/Menu setting applies | A hand-written button group, or `ActionsMenu` items wrapped in `<li>` |\n\n### 3. Supply the items from the controller\n\n```csharp\nmodel.List = new AdminListContext<IndexProfile>\n{\n    Source = items,\n    Pager = pagerShape,\n};\n```\n\nThe view model exposes `public AdminListContext List { get; set; }`. `AdminListContext<TModel>` sets `ItemType`\nand exposes a typed `IEnumerable<TModel> Source`; its generic argument can select a registered base/interface\ndisplay manager, even for an empty collection of derived items. Items must be non-null instances of that type.\nThe list invokes the display manager after resolving its layout, so the controller has no row-rendering\ndependencies and never builds rows, row callbacks or prebuilt shapes.\n\nThe default `IAdminListItemDisplayManager<TModel>` adapter calls `IDisplayManager<TModel>`. Register a closed\nadapter for a specialized pipeline, e.g. content items use `IContentItemDisplayManager`.\n\nOther context properties, all optional:\n\n- `Header`: an options editor shape (`Summary` and `Actions` zones), as the content items and users pass.\n- `Toolbar`: a toolbar shape of the page's own. Otherwise the list builds `AdminListToolbar` from the rows, the\n  pager, `BulkActions`, `ShowSelectAll` and `ToolbarActions` (a shape at its end, e.g. filters).\n  `ShowToolbar = false` omits it.\n- `Search`, `Actions`: the search bar (e.g. the `AdminListSearch` shape) and the page buttons, in the action bar.\n- `PageSize`: a separate page size selector; set `ShowPageSizeSelector = false` on the pager when passing it.\n- `Data`: what a column provider may need, e.g. the content types the listing is filtered by.\n- `Layout`: forces a layout, only when the page can be rendered one way, e.g. while its rows are dragged.\n- `ShowLayoutSelector = false`: for a page that renders several lists or places one selector itself, by creating\n  an `AdminListLayoutSelector` shape with the shared `ListName`.\n\n### 4. Render the list in the view\n\nImport the tag helpers once per module, in `Views/_ViewImports.cshtml`:\n\n```cshtml\n@using OrchardCore.Admin\n@addTagHelper *, OrchardCore.Admin.Abstractions\n```\n\nThe view keeps the form, its hidden inputs and the action bar, and renders the list with `<admin-list>`:\n\n```cshtml\n<form asp-action=\"Index\" method=\"post\" class=\"no-multisubmit bulk-select-list\"\n      data-checkbox-name=\"itemIds\" data-bulk-action-name=\"Options.BulkAction\" data-selected-text=\"@T[\"selected\"]\">\n    <input type=\"submit\" name=\"submit.Filter\" id=\"submitFilter\" class=\"visually-hidden\" />\n    <input asp-for=\"Options.BulkAction\" type=\"hidden\" />\n    <input type=\"submit\" name=\"submit.BulkAction\" class=\"visually-hidden\" />\n\n    <admin-list name=\"Indexes\" context=\"Model.List\"\n                bulk-actions=\"Model.Options.BulkActions\"\n                empty-message=\"@T[\"<strong>Nothing here!</strong> There are no indexes at the moment.\"]\">\n        <admin-list-list item-css-class=\"list-group-item\" />\n        <admin-list-grid>\n            <admin-list-column type=\"Select\" />\n            <admin-list-column name=\"Name\" title=\"@T[\"Name\"]\" position=\"20\" />\n            <admin-list-column name=\"Source\" title=\"@T[\"Source\"]\" position=\"30\" width=\"auto\" />\n            <admin-list-column type=\"Actions\" title=\"@T[\"Actions\"]\" />\n        </admin-list-grid>\n    </admin-list>\n</form>\n```\n\n- `name` and `context` are both required. The name identifies the list for column providers, the user's layout\n  choice and the alternates. Use a string literal when only the view uses it; keep a module constant when it is\n  reused, e.g. `ContentsAdminList.Name`, and do not create a holder class for a single use.\n- The tag also takes `show-toolbar`, `show-select-all`, `show-layout-selector`, `layout`, `item-css-class`,\n  `empty-message`, `bulk-actions`, and `rows-*` attributes (or `rows-attributes`) for the rows container, e.g.\n  `rows-id` for a sortable list.\n- `admin-list-list` and `admin-list-grid` configure their layout's `rows-class` and `item-css-class` without\n  forcing it. Each can be declared once. List item classes replace the default; grid item classes are added to\n  the required grid row class. Children emit no HTML.\n- The hidden `submit.Filter` button stays first in the form: Enter in the search box triggers it. The search is an\n  input group ending with a magnifying-glass button, never an icon inside the input.\n- Use the shared `bulk-select-list` and `list-search-filter` components, wired by one small module per page\n  (`Assets/ts/*.ts`), and keep the `select-all`, `items`, `selected-items` and `actions` ids the toolbar renders.\n\n### 5. Declare the grid columns\n\nEach `<admin-list-column>` renders the row zone named after it; `zones=\"A B\"` renders several named zones instead.\nA zone no column renders is omitted from the grid.\n\n| Attribute | Meaning |\n| --- | --- |\n| `type` | `Select` (the `Select` zone, at position `10`) or `Actions` (the `Actions` and `ActionsMenu` zones, at position `end`); omit for a regular column. A preset keeps its defaults unless overridden. |\n| `name` | Required for a regular column, unique ignoring case; also the default zone and the `admin-list-column-{name}` CSS class. |\n| `title` | Localized header text, e.g. `@T[\"Name\"]`; it also labels the cells when a narrow list stacks them. |\n| `position` | Placement syntax (`10`, `25`, `end`); columns are sorted by it. |\n| `width` | Any CSS width, or `auto` to hug the content. No width shares the remaining space. |\n| `alignment`, `no-wrap`, `css-class` | Cell presentation. |\n\nTo change the columns of another module's list, implement `IAdminListColumnsProvider`:\n\n```csharp\npublic sealed class CultureColumnsProvider : IAdminListColumnsProvider\n{\n    private readonly IStringLocalizer S;\n\n    public CultureColumnsProvider(IStringLocalizer<CultureColumnsProvider> stringLocalizer)\n    {\n        S = stringLocalizer;\n    }\n\n    public Task PopulateAsync(AdminListColumnsContext context)\n    {\n        // Between \"Title\" (20) and \"Type\" (30), whatever order the providers run in.\n        context.Columns.Add(new AdminListColumn { Name = \"Culture\", Position = \"25\", Title = S[\"Culture\"], Width = AdminListColumn.AutoWidth });\n\n        return Task.CompletedTask;\n    }\n}\n```\n\n```csharp\nservices.AddAdminListColumnsProvider<CultureColumnsProvider>(ContentsAdminList.Name);\n```\n\n- Registered with a list name, a provider is only created and run for that list (keyed DI with an\n  `AdminListColumnsProviderKey`; names are case-sensitive). Several providers can target a list, and a provider\n  can target several lists. Duplicate registrations are ignored.\n- Without a name, `AddAdminListColumnsProvider<TProvider>()` registers a global provider for every list. Global\n  providers run first, then the providers of the list, each group in feature dependency order. A global provider\n  must tolerate different column sets: check `context.Find(name)` before changing an optional column.\n- `context.Columns.Add(...)`, `context.Find(name)` and `context.Remove(name)` change the columns.\n  `context.Data`, `TryGetData<T>()` and `GetData<T>()` read what the page passed, e.g.\n  `ContentsAdminList.ContentTypesKey` and `ContentsAdminList.StereotypesKey`.\n- A provider never builds cells: the content of its column comes from a display driver placing a shape in the\n  zone of that column for `SummaryAdminGrid`.\n\n### 6. Test and document it\n\n- Add the page's URL to `_lists` in `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs` when the\n  Blog recipe enables its feature (add an optional-list test otherwise). The test renders every list in the grid\n  and fails when a column renders nothing on any row, which catches a shape missing its `SummaryAdminGrid` location.\n- Check the page in both layouts and both actions layouts (Buttons and Menu), with rows and without, and in a\n  narrow window, where the grid stacks its cells.\n- Add the list to the table of lists in `src/docs/reference/modules/Admin/README.md`.\n\n## Regions and properties of the shape\n\nRows and regions inherit the list name and layout automatically, so their alternates work without a page passing\nthem. `Rows` is output built during display, never an input; `DisplayType` is the resolved row display type.\nCustom `AdminListCell` templates receive `Row` and `Column`. The Actions cell renders the row's `Actions` and\n`ActionsMenu` zones through `AdminListActions`; moving an action to another zone removes it from that cell.\n\n## Alternates\n\n| Shape | Alternates, least to most specific |\n| --- | --- |\n| `AdminList` | `AdminList__{Layout}`, `AdminList__{Name}`, `AdminList__{Name}__{Layout}` |\n| `AdminListCell` | `AdminListCell__{Column}`, `AdminListCell__{Name}__{Column}` |\n| `AdminListActions` | `AdminListActions__{Layout}`, `AdminListActions__{Name}`, `AdminListActions__{Name}__{Layout}`, where `{Layout}` is `Buttons` or `Menu` |\n| `AdminListToolbar`, `AdminListSearch` | `__{Layout}`, `__{Name}`, `__{Name}__{Layout}` |\n\n`AdminListActionsLayout` has exactly two values, `Buttons` and `Menu`; configuration and the nullable site\nsettings use the enum. Keep templates and alternates overridable, but do not add option shapes or custom modes.\n\n## Gotchas\n\n1. **Never name the rows `Items`**: `Shape` already exposes `Items`, its child shapes.\n2. **A location without a display type does not reach the grid column** (step 1). An empty grid column is the symptom.\n3. **Per-type row templates only apply to the List layout**: `Content-BlogPost.SummaryAdmin.cshtml` does not\n   affect the grid; customise it with `SummaryAdminGrid` placement or `AdminListCell-{List}-{Column}.cshtml`.\n4. **Never wrap `ActionsMenu` items in `<li>`**: the dropdown sits inside the `<li>` of a List row, and the parser\n   hoists a nested `<li>` out of it, so the menu renders empty and stray links appear under the list.\n5. **No viewport-based classes such as `d-md-none` in list Razor**: the grid stacks with a container query on the\n   list itself (`_admin-list.scss`), because the sidebar makes the viewport a poor proxy for the list width.\n6. **A sortable list drags the rows, never their container**: in the grid the rows container is\n   `display: contents`. Mark it with `rows-*` attributes, and send SortableJS `oldDraggableIndex`/`newDraggableIndex`,\n   which count only the rows.\n7. **Rows are built while the view renders**, from the `AdminList` displaying event: a driver's queries and\n   errors happen during rendering, not in the controller.\n\n## References\n\n- `references/worked-example.md`: the Indexes page, driver, controller and view.\n- `src/docs/reference/modules/Admin/README.md`: the user and developer documentation of admin lists.\n- `src/OrchardCore.Modules/OrchardCore.Admin/Views/AdminList*.cshtml`: the layout, cell, actions and toolbar templates.\n- `src/OrchardCore.Themes/TheAdmin/Assets/scss/components/_admin-list.scss`: the layout CSS and its container queries.\n- `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs`: renders every list in the grid.\n",".agents/skills/orchardcore-data-migration/SKILL.md":"---\nname: orchardcore-data-migration\ndescription: Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema.\n---\n\n# OrchardCore Data Migration\n\nThis skill guides you through writing OrchardCore data migrations following project conventions.\n\nA data migration is a class that inherits `DataMigration`. OrchardCore discovers its methods by reflection and runs them **sequentially**, per tenant, on application start. The chain starts at `CreateAsync()` and continues through `UpdateFrom1Async()`, `UpdateFrom2Async()`, … Each method returns the schema version number the **next** method must match.\n\n## When to use a migration\n\n- Define or alter content types / parts / fields (`IContentDefinitionManager`).\n- Create or alter SQL index tables (`SchemaBuilder`).\n- Patch existing content item data (query + `ISession.SaveAsync`).\n- Run a setup recipe at install/upgrade (`IRecipeMigrator`).\n- Seed roles, settings, or other documents.\n\n## The version-chain rule (read first)\n\nThe migration runner stores the **last returned number** per migration class per tenant. It then calls the method whose name matches: returned `4` → runs `UpdateFrom4Async()`. The method's return value is the new stored version.\n\n| Rule | Why |\n|------|-----|\n| `CreateAsync` runs only on a **fresh** install. | Existing tenants already passed it. |\n| `CreateAsync` returns the **current latest** version, skipping all `UpdateFromX`. | New sites get the final schema directly. |\n| `UpdateFromXAsync` runs only when stored version == `X`. | Sequential upgrade path. |\n| **Never renumber** an already-shipped method. | Other tenants store the old number; renumbering breaks their upgrade. |\n| Each `UpdateFromX` returns `X+1` (usually). | Skipping is allowed when a later step subsumes an earlier one (return a higher number to jump). |\n\n## Migration Workflow\n\n### Step 1: Create the Migrations class\n\nPlace `Migrations.cs` in the module root (or a `Migrations/` subfolder for multiple classes, e.g. `Migrations/PermissionMigrations.cs`).\n\n```csharp\nusing OrchardCore.ContentManagement.Metadata;\nusing OrchardCore.Data.Migration;\n\nnamespace YourModule;\n\npublic sealed class Migrations : DataMigration\n{\n    private readonly IContentDefinitionManager _contentDefinitionManager;\n\n    public Migrations(IContentDefinitionManager contentDefinitionManager)\n    {\n        _contentDefinitionManager = contentDefinitionManager;\n    }\n\n    public async Task<int> CreateAsync()\n    {\n        // ... define schema ...\n\n        // Return the latest version so new installs skip every UpdateFromX.\n        return 1;\n    }\n}\n```\n\n`SchemaBuilder` is injected by the base class — do **not** add it as a constructor parameter.\n\n### Step 2: Register the migration\n\nIn the module `Startup.cs`:\n\n```csharp\nservices.AddDataMigration<Migrations>();\n```\n\nRegister each migration class separately if the module has more than one.\n\n### Step 3: Write the initial `CreateAsync`\n\nReturn the highest version your module currently ships. Example (content part):\n\n```csharp\npublic async Task<int> CreateAsync()\n{\n    await _contentDefinitionManager.AlterPartDefinitionAsync(\"TitlePart\", builder => builder\n        .Attachable()\n        .WithDescription(\"Provides a Title for your content item.\"));\n\n    // Shortcut other migration steps on new content definition schemas.\n    return 2;\n}\n```\n\n### Step 4: Add `UpdateFromX` for each shipped change\n\nWhen you change the schema in a release, **append** a new `UpdateFromXAsync` — never edit a prior one. Bump the number `CreateAsync` returns to match.\n\n```csharp\n// This code can be removed in a later version.\npublic async Task<int> UpdateFrom1Async()\n{\n    await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table\n        .AddColumn<string>(\"BigUrl\", column => column.Nullable().Unlimited()));\n\n    return 2;\n}\n```\n\n### Step 5: Test the migration\n\n1. **Fresh install** — run on a clean tenant; only `CreateAsync` executes, schema lands at the latest version.\n2. **Upgrade** — start from a DB at the previous version; confirm each `UpdateFromX` runs in order.\n3. Test on every targeted provider (SQLite, SQL Server, MySQL, PostgreSQL). SQLite cannot drop columns — guard with `try/catch` (see `references/schema-builder.md`).\n\n## Method signatures\n\n| Method | Forms | Runs when |\n|--------|-------|-----------|\n| Create | `int Create()` · `Task<int> CreateAsync()` | fresh install |\n| Update | `int UpdateFromX()` · `Task<int> UpdateFromXAsync()` | stored version == X |\n| Uninstall | `void Uninstall()` · `Task UninstallAsync()` | feature uninstalled |\n\nStatic or instance, sync or async — all supported. Async (`...Async`) is the convention for new code.\n\n## Quick Reference\n\n### Common operations\n\n| Goal | API |\n|------|-----|\n| Define/alter content type | `_contentDefinitionManager.AlterTypeDefinitionAsync(...)` |\n| Define/alter part | `_contentDefinitionManager.AlterPartDefinitionAsync(...)` |\n| Remove part / type | `RemovePart(...)`, `DeletePartDefinitionAsync(...)` |\n| Create SQL index table | `SchemaBuilder.CreateMapIndexTableAsync<TIndex>(...)` |\n| Add/drop column or index | `SchemaBuilder.AlterIndexTableAsync<TIndex>(...)` |\n| Run a recipe | `_recipeMigrator.ExecuteAsync(\"init.recipe.json\", this)` |\n| Patch content items | `ISession.Query<ContentItem, ContentItemIndex>(...)` + `SaveAsync` |\n\n### Type definition builder methods\n\n| Method | Effect |\n|--------|--------|\n| `.Creatable()` | Appears in the New menu |\n| `.Listable()` | Shows in content list |\n| `.Draftable()` | Supports drafts |\n| `.Versionable()` | Keeps version history |\n| `.Securable()` | Per-type permissions |\n| `.WithPart(\"PartName\")` | Attaches a part |\n\n## Gotchas\n\n- Always set column **length** on string index columns (`.WithLength(n)`) or `.Unlimited()` — unlengthed columns fail on some providers.\n- `CreateAsync` should return the latest version, not `1`, once `UpdateFromX` methods exist.\n- Patch loops must page by `DocumentId` and call `FlushAsync()` periodically (see `references/patterns.md`).\n- Mark obsolete `UpdateFromX` methods with `// This code can be removed in a later version.` but keep them until you drop support for that upgrade path.\n\n## References\n\n- `references/patterns.md` — content definition changes, content-item data patching, recipe migrations\n- `references/schema-builder.md` — index tables, columns, indexes, provider quirks\n- `src/docs/reference/modules/Migrations/README.md` (repo) — official reference\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-display-management/SKILL.md":"---\nname: orchardcore-display-management\ndescription: Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or write a display driver.\n---\n\n# OrchardCore Display Management\n\nThis skill guides you through OrchardCore's display system following project conventions.\n\nOrchardCore renders content by building **shapes** (dynamic view models) from drivers, then placing each shape into a **zone** of the layout. **Drivers** decide *what* shapes exist and their default location; **`placement.json`** overrides *where* they go, *whether* they show, and *which* template renders them.\n\n## Mental model\n\n```\nContentItem ──drivers──▶ shapes ──placement.json──▶ zones ──templates──▶ HTML\n```\n\n- **Shape** — a dynamic object with a `ShapeType` and `Metadata`. The shape type (e.g. `TextField`, `Parts_Contents_Publish`, `ContentPart`) selects the template and the placement rule.\n- **Driver** — `DisplayDriver` / `ContentPartDisplayDriver` / `ContentFieldDisplayDriver`. Returns `IDisplayResult`s from `Display`, `Edit`, `UpdateAsync`, each with a default `.Location(...)`.\n- **Placement** — `placement.json` at the **root** of a module or theme. Filters shapes and rewrites their location/visibility/template.\n- **Zone** — named region of the layout (`Content`, `Header`, `Footer`, `Actions`, …) or an editor group (tab/card/column).\n\n## Decide: driver vs placement\n\n| Want to… | Use |\n|----------|-----|\n| Reposition / hide a shape someone else built | `placement.json` |\n| Move a shape to a layout zone | `placement.json` (`place` starts with `/`) | \n| Swap the template or add alternates | `placement.json` (`shape` / `alternates`) |\n| Lay out the admin editor (tabs/cards/columns) | `placement.json` groupings |\n| Produce a *new* shape from your part/field | display driver |\n| Read/write the editor model | display driver (`Edit` / `UpdateAsync`) |\n\nPrefer `placement.json` for layout — no rebuild needed, theme can override.\n\n## Workflow A: place a shape\n\n### Step 1: Find the shape type and differentiator\n\nThe placement property name is the **shape type**, not the part name. A part **without** its own driver renders as `ContentPart` (differentiator = part name). A field renders as its field type (`TextField`) with differentiator `{PartName}-{FieldName}`.\n\nSee `references/placement.md` for the full differentiator table.\n\n### Step 2: Add a rule to `placement.json`\n\n```json\n{\n  \"TextField\": [\n    {\n      \"differentiator\": \"Article-Subtitle\",\n      \"displayType\": \"Detail\",\n      \"place\": \"Content:2\"\n    }\n  ]\n}\n```\n\n### Step 3: Verify\n\nRun the site, view the content item. Use `?display` debug shapes or the template's shape tracing to confirm the shape type. Wrong type → no rule matches.\n\n## Workflow B: write a display driver\n\n### Step 1: Inherit the right base\n\n| Base class | For |\n|------------|-----|\n| `ContentPartDisplayDriver<TPart>` | a content part |\n| `ContentFieldDisplayDriver<TField>` | a content field |\n| `SectionDisplayDriver<TModel, TSection>` | settings sections |\n| `DisplayDriver<TModel>` | anything else |\n\n### Step 2: Implement Display / Edit / Update\n\n```csharp\npublic sealed class TextFieldDisplayDriver : ContentFieldDisplayDriver<TextField>\n{\n    public override IDisplayResult Display(TextField field, BuildFieldDisplayContext context)\n    {\n        return Initialize<DisplayTextFieldViewModel>(GetDisplayShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        })\n        .Location(OrchardCoreConstants.DisplayType.Detail, \"Content\")\n        .Location(OrchardCoreConstants.DisplayType.Summary, \"Content\");\n    }\n\n    public override IDisplayResult Edit(TextField field, BuildFieldEditorContext context)\n    {\n        return Initialize<EditTextFieldViewModel>(GetEditorShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        });\n    }\n\n    public override async Task<IDisplayResult> UpdateAsync(TextField field, UpdateFieldEditorContext context)\n    {\n        await context.Updater.TryUpdateModelAsync(field, Prefix, f => f.Text);\n        return Edit(field, context);\n    }\n}\n```\n\n### Step 3: Register the driver\n\nIn `Startup.cs`:\n\n```csharp\nservices.AddContentPartDisplayDriver<MyPart, MyPartDisplayDriver>();\n// or for a field:\nservices.AddContentFieldDisplayDriver<TextField, TextFieldDisplayDriver>();\n```\n\nSee `references/drivers.md` for shape-type helpers, the fluent `.Location` API, and registration variants.\n\n## Quick Reference\n\n### Placement rule keys\n\n| Key | Purpose |\n|-----|---------|\n| `place` | `Zone:Position`. `-` hides, `/Zone` targets a layout zone |\n| `displayType` | Match `Detail`, `Summary`, `SummaryAdmin`, `SummaryAdminGrid`, `DetailAdmin`, `Edit` |\n| `differentiator` | Distinguish reused shapes (field/part name) |\n| `contentType` | Match content type / stereotype (`Art*` wildcard, or array) |\n| `contentPart` | Match content items containing a part |\n| `path` | Match request path |\n| `alternates` | Add alternate shape types |\n| `wrappers` | Wrap the shape in other shapes |\n| `shape` | Substitute a different shape type |\n\n### Position values\n\n`before` < `0`/empty < `1` < `1.1` < `2` < `10` < `after`. Numeric, not alphabetic. Dot notation inserts between (`5.1` is between 5 and 6).\n\n### Editor grouping modifiers\n\n| Modifier | Group | Example |\n|----------|-------|---------|\n| `#` | Tab | `Parts:0#Media;1` |\n| `%` | Card | `Parts:0%Details;2` |\n| `\\|` | Column | `Parts:0\\|Left_9;1` (`_9` = width, `;1` = order) |\n\n`;N` after a group name = group order. `_N` after a column name = Bootstrap grid width (12-col).\n\n### Common zones\n\n| Zone | Purpose |\n|------|---------|\n| `Content` | Main content shapes |\n| `Header` / `Footer` | Layout regions |\n| `Actions` | Admin list row buttons (`SummaryAdmin` and `SummaryAdminGrid`) |\n| `Parts` | Editor parts container |\n| `Meta` | Metadata block |\n\n## Gotchas\n\n- Hiding a whole editor row → target `ContentPart_Edit` with differentiator `{ContentType}-{PartName}`, not the inner `XxxPart_Edit`.\n- Shape type ≠ part type. No driver → shape is `ContentPart`. Summary display of a dynamic part → `ContentPart_Summary`.\n- Field display modes are stricter: shape type needs `_Display`, differentiator is `{Part}-{Field}-{FieldType}_Display__{Mode}`.\n- Placement precedence: startup project → active theme → modules (by dependency). A theme can override any module's placement.\n- Admin list rows use `SummaryAdmin` in the List layout and `SummaryAdminGrid` in the Grid layout, where each zone is a column. Display types do not inherit placement, so a rule or a driver location for `SummaryAdmin` does not reach the grid; see `orchardcore-admin-index-views`.\n- Same position value → registration order decides; use distinct positions when order matters.\n\n## References\n\n- `references/placement.md` — placement.json filters, differentiators, layout zones, editor groupings\n- `references/drivers.md` — driver base classes, shape helpers, fluent Location API, registration\n- `src/docs/reference/modules/Placement/README.md` (repo) — official placement reference\n- `src/docs/reference/modules/DisplayManagement/README.md` (repo) — display management reference\n- `src/docs/reference/modules/Templates/README.md` (repo) — shape differentiators and alternates\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-breadcrumbs/SKILL.md":"---\nname: orchardcore-breadcrumbs\ndescription: Authors explicit breadcrumb titles, inline ancestors, and lightweight IBreadcrumbProvider postprocessors on OrchardCore admin screens and the front end. Use when declaring current-page titles, extending ancestors, resolving a parent entity, documenting hidden-trail processing, or theming breadcrumbs through name-based shape alternates.\n---\n\n# OrchardCore Breadcrumbs\n\nA breadcrumb is a **named trail with an explicit current-page title**. The parent `<breadcrumb>` requires an `IHtmlContent` title and normalizes it to text; every `<breadcrumb-item>` child is an ancestor. For visible trails, the view supplies dynamic ancestor links and localized text, and registered `IBreadcrumbProvider` implementations can postprocess only those ancestors. The helper preserves their final list order and appends the normalized title as the final, unlinked current node. Hidden admin trails use only the explicit title and skip children and providers entirely. On the admin, the trail appears above a heading containing that text; on the front end, no heading is rendered by default.\n\nKeep built-in views inline, with the current page in `title` rather than a child. Use lightweight providers for shared or cross-module ancestor changes, not a separate manager, builder, named-provider base class, or contextual-data contract. Customize the owning view or its partials for the title and screen-specific ancestors, and use shape alternates for presentation. `INavigationProvider` remains the separate extension point for ordinary navigation menus.\n\n**Keep all Show breadcrumb logic in the tag helper.** Views and partials must not read `AdminSettings.ShowBreadcrumb` or gate lookups, child declarations, or breadcrumb markup on it. Declare nodes and resolve needed data normally; the helper alone chooses visibility and the provider/title pipeline.\n\n## Mental model\n\n1. Require a stable `name` and a non-null `Microsoft.AspNetCore.Html.IHtmlContent` title, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text but still leaves a current node in a visible trail.\n2. Check visibility before any child or provider work. A hidden admin trail never calls `GetChildContentAsync`, resolves or enumerates providers, constructs or invokes them, or creates a `BreadcrumbContext` or any breadcrumb items, including the current node.\n3. For a hidden trail, only the explicit title matters. When heading or browser-title output is needed, call `WriteTo(writer, HtmlEncoder.Default)` once and HTML-decode once, then safely encode the text for output. With `heading=\"\"` and `page-title=\"false\"`, validate the non-null title but also skip `WriteTo`.\n4. For a visible trail, normalize the title, collect ancestor children, and await registered providers sequentially in DI registration order on the shared mutable ancestor list. Helper-created provider contexts always have `ShowTrail == true`.\n5. Preserve the providers' final ancestor list order without sorting, then unconditionally append the explicit title node with fixed `Id = \"Title\"`, last/current and never linked. Clearing or reordering ancestors cannot remove or displace it.\n6. Visible trails finalize ancestor links and render shapes. Hidden trails render only the requested explicit heading/browser title, with no URL generation, permission lookup, link authorization, or shapes. The admin setting does not affect front-end trails.\n\n## Naming\n\n- Use stable literal strings directly in `.cshtml`: `name=\"ContentsEdit\"` and ancestor `id=\"Contents\"`.\n- Use `name` for provider targeting, the trail's CSS class, shape alternates, and name-based deduplication, including Dashboard. The current node automatically has `Id = \"Title\"`; its name-specific alternate needs no separate attribute.\n- Reuse a screen's admin list name when it has one. The content items list uses `Contents`.\n- Prefer PascalCase names without separators so shape alternate file names remain readable.\n- Do not introduce breadcrumb constants classes, contextual-data keys, or a `data` attribute.\n- Ancestor IDs and the automatic current-node ID `Title` identify node shape alternates; they are not rendered as HTML `id` attributes.\n\n## Workflow A: add a breadcrumb to a view\n\n### Step 1: Import the tag helpers\n\nReference `OrchardCore.Navigation.Core` and add this to the module's `Views/_ViewImports.cshtml` if it is not already imported:\n\n```razor\n@addTagHelper *, OrchardCore.Navigation.Core\n```\n\nThe always-enabled `OrchardCore.Navigation` module supplies the shapes. Inline authoring needs no custom provider registration.\n\n### Step 2: Declare the explicit title and ancestor children\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nPrefer paired parent tags with inline ancestors. The parent supplies the current page through `title`; the helper appends it with ID `Title`. Do not add a duplicate current-page child.\n\nPass the normal Razor localizer result directly, as with `RenderTitleSegments`: `title=\"@T[\"Edit Record\"]\"` or `title=\"@T[\"Edit {0}\", record.Name]\"`. The helper renders the HTML-aware value when needed, including its formatting arguments. Do not extract `.Value` from `T[...]` or add caller conversions. Keep `@T[\"...\"]` for ancestor child text too.\n\nPlain strings require the existing safe wrapper `new OrchardCore.DisplayManagement.Html.HtmlContentString(value ?? string.Empty)`. For `Microsoft.Extensions.Localization.LocalizedString` and `OrchardCore.Localization.Data.DataLocalizedString` from `IDataLocalizer`, wrap `.Value ?? string.Empty`. Do not pass these non-HTML values directly, extract `T[...].Value`, or use `Html.Raw`: `HtmlContentString` encodes untrusted strings when written.\n\nA self-closing helper still requires both attributes, for example `<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />`. Providers may supply ancestors, but not the current title. With no ancestors, a visible trail still contains the appended current node.\n\n| Parent attribute | Behavior |\n|------------------|----------|\n| `name` | Required stable trail name used for provider targeting, CSS, shape alternates, and name-based ancestor deduplication. |\n| `title` | Required non-null `Microsoft.AspNetCore.Html.IHtmlContent`, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text, not the current node in a visible trail. |\n| `heading` | Defaults to `h1` on admin requests and no heading on the front end. `heading=\"\"` suppresses the heading. |\n| `page-title` | Defaults to `true`; adds the nonempty explicit title as a browser-title segment. |\n| `display-type` | Defaults to `DetailAdmin` on admin requests and `Detail` elsewhere. Supplies shape alternates. |\n\n### Title normalization\n\nWith `@using OrchardCore.DisplayManagement.Html`, prepare the view's values as follows:\n\n| Value in the view | Title expression |\n|-------------------|------------------|\n| HTML-localized label | `title=\"@T[\"Edit {0}\", record.Name]\"` |\n| Plain model string | `title=\"@(new HtmlContentString(Model.DisplayText ?? string.Empty))\"` |\n| `LocalizedString` | `title=\"@(new HtmlContentString(localizedTitle.Value ?? string.Empty))\"` |\n| `DataLocalizedString` | `title=\"@(new HtmlContentString(dataTitle.Value ?? string.Empty))\"` |\n\nUse `title=\"@Microsoft.AspNetCore.Html.HtmlString.Empty\"` for intentionally empty content. A null title is invalid, including on the no-output path.\n\nWhen needed, every title follows the same normalization: render its `IHtmlContent` once with `WriteTo(writer, HtmlEncoder.Default)`, then HTML-decode once into plain text. The helper safely encodes that string for the heading, current node, and browser title. Literal `&amp;` in a string wrapped with `HtmlContentString` stays literal `&amp;`, serialized as `&amp;amp;` in HTML. Do not pre-encode the wrapper's input. `IHtmlContent` does not enable arbitrary title markup: final title output remains text-only.\n\n### Step 3: Remove duplicate title output\n\nRemove the view's old page heading and `RenderTitleSegments` call. The helper supplies both by default on the admin. Keep the helper in the `Title` zone so it works with the admin theme's top-bar title setting.\n\nFor a trail that does not own the heading, use the `Breadcrumbs` zone and `heading=\"\"`. Also set `page-title=\"false\"` if the page registers its browser title separately.\n\n## Child attributes and link behavior\n\n| Attribute | Type | Behavior |\n|-----------|------|----------|\n| `id` | `string` | Stable node identifier for shape alternates. |\n| `action`, `controller`, `area` | `string` | MVC route target. Prefer these over hand-built action URLs. |\n| `route-*` | `string` | Additional action route values, such as `route-id=\"@record.Id\"`. |\n| `route-values` | `IDictionary<string, string>` | Additional action route values supplied as a dictionary. |\n| `url` | `string` | Direct link target; ignored when `action` is set. |\n| `permission` | `string` | Stored as `BreadcrumbItem.PermissionName`, resolved through `IPermissionService` during visible ancestor link processing after providers; unknown names are ignored. |\n| `resource` | `object` | Resource passed to authorization for the permission. |\n| `link-enabled` | `bool` | Defaults to `true`. Use `false` when the view has already determined that the link should be unavailable. |\n\nThe inner content is localized ancestor text, for example `@T[\"Records\"]`. Child content is HTML-decoded after Razor evaluates it, then safely encoded by the shape template. The parent's `IHtmlContent` title is rendered and decoded once as described above. Templates safely encode normalized title and provider-supplied text; do not emit them with `Html.Raw`.\n\nEvery child and provider item remains an ancestor, including the last item in the list. The helper appends a separate title node after all ancestors; only that node is current and **never linked**. Ancestors with denied permissions, `link-enabled=\"false\"`, or no link target remain visible as text. Do not omit ancestors solely because the user cannot follow their links.\n\nChildren and providers only store named permissions. The helper resolves `PermissionName` after providers and link-eligibility checks, so removed or unlinkable ancestors do not cause a name lookup. The explicit title node never needs link processing. Providers can also add `Permission` objects to `BreadcrumbItem.Permissions`; authorization evaluates applicable permissions against `Resource`. Hidden trails perform neither permission lookup nor authorization.\n\n## Workflow B: resolve a nested editor's parent\n\nUse the real parent's display name and route ID, obtained from the view model when available. For example, a model exposing `Parent` and `Record` can render:\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", Model.Record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n        <breadcrumb-item id=\"Parent\" action=\"Edit\" controller=\"Record\" area=\"My.Module\"\n                         route-id=\"@Model.Parent.Id\"\n                         permission=\"@MyPermissions.ManageRecords.Name\"\n                         resource=\"@Model.Parent\">@T[\"Edit {0}\", Model.Parent.Name]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nIf only the parent ID is available, inject an existing domain service into Razor, not a breadcrumb-specific service. A model with `ParentContentItemId`, for example, can use:\n\n```razor\n@inject OrchardCore.ContentManagement.IContentManager ContentManager\n@{\n    var parent = await ContentManager.GetAsync(Model.ParentContentItemId);\n}\n```\n\nUse the loaded parent's ID and display text in ancestor child items. Follow the screen's existing missing-parent handling and error reporting. Do not guard parent lookups on the breadcrumb visibility setting. Hidden trails always skip child content, but parent attribute expressions and Razor code or model lookups outside that child content still execute.\n\n## Workflow C: postprocess a trail with a provider\n\nImplement `OrchardCore.Navigation.IBreadcrumbProvider`:\n\n```csharp\npublic interface IBreadcrumbProvider\n{\n    ValueTask BuildBreadcrumbAsync(BreadcrumbContext context);\n}\n```\n\n`BreadcrumbContext` is also in `OrchardCore.Navigation`. Its public constructor is `BreadcrumbContext(string name, string title, List<BreadcrumbItem> items, ViewContext viewContext, bool showTrail)`, with `ViewContext` from `Microsoft.AspNetCore.Mvc.Rendering`. The helper passes the normalized plain-string title, not the original `IHtmlContent`. It exposes read-only properties:\n\n| Property | Type | Use |\n|----------|------|-----|\n| `Name` | `string` | Filter the trail's literal name explicitly. |\n| `Title` | `string` | Normalized plain-text explicit title, which providers cannot replace. |\n| `Items` | `List<BreadcrumbItem>` | Shared mutable ancestor list. Add, insert, remove, replace, reorder, or edit ancestors; do not replace the read-only list reference. The final list order is preserved. The explicit current node is not in the list. |\n| `ViewContext` | `Microsoft.AspNetCore.Mvc.Rendering.ViewContext` | Read request context, such as `HttpContext`, when filtering by admin/front-end request. |\n| `ShowTrail` | `bool` | Whether the trail is visible. Helper-created provider contexts always have `ShowTrail == true`; hidden trails create no context and invoke no providers. |\n\nProviders see all visible trails unless they explicitly filter the context. This example targets names starting with `Example`, excluding the `Examples` list itself by name rather than localized title text:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Localization;\nusing OrchardCore.Navigation;\n\npublic sealed class ExampleBreadcrumbProvider : IBreadcrumbProvider\n{\n    private readonly IStringLocalizer S;\n\n    public ExampleBreadcrumbProvider(IStringLocalizer<ExampleBreadcrumbProvider> localizer)\n    {\n        S = localizer;\n    }\n\n    public ValueTask BuildBreadcrumbAsync(BreadcrumbContext context)\n    {\n        if (!context.Name.StartsWith(\"Example\", StringComparison.Ordinal) || context.Name == \"Examples\" ||\n            context.Items.Any(item => item.Id == \"Examples\"))\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        context.Items.Insert(0, new BreadcrumbItem\n        {\n            Id = \"Examples\",\n            Text = S[\"Examples\"].Value,\n            Url = \"~/examples\",\n        });\n\n        return ValueTask.CompletedTask;\n    }\n}\n```\n\nRegister the provider in `Startup.ConfigureServices`:\n\n```csharp\nservices.AddBreadcrumbProvider<ExampleBreadcrumbProvider>();\n```\n\nRegistration is scoped and uses `TryAddEnumerable`. For visible trails, providers run in DI registration order after collecting ancestor children, before appending the explicit current node and finalizing `IsCurrent` and `Href`. Later providers observe earlier ancestor mutations, and the helper preserves the final list order.\n\nUse `context.Name` to target a trail, or `context.ViewContext` to target a request. An admin-only provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)`. Set `Text` to a plain string, not encoded HTML; use `Url` or `RouteValues`, `PermissionName` or the `Permissions` list, `Resource`, and `LinkEnabled` to describe links. Do not depend on incoming `IsCurrent` or `Href`, which the helper computes afterward.\n\nThe example also supplies an ancestor to a trail with no inline children. The view must still provide its title:\n\n```razor\n<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />\n```\n\nProviders cannot remove or replace the explicit title: `Title` is read-only, and `Items` contains only ancestors. The helper appends the title node with `Id = \"Title\"` after all providers have run, even if a provider cleared or reordered every ancestor.\n\nHidden admin trails bypass providers completely: the helper does not resolve, enumerate, construct, or invoke `IBreadcrumbProvider` implementations. It creates no `BreadcrumbContext`; the retained `ShowTrail` property and constructor parameter are always true in helper-created provider contexts. Only the explicit title supplies hidden heading/browser-title output.\n\n## Dashboard ancestor\n\nThe Admin Dashboard feature registers `OrchardCore.AdminDashboard.Services.DashboardBreadcrumbProvider` with `AddBreadcrumbProvider<DashboardBreadcrumbProvider>()`.\n\nThe provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)` and leaves front-end trails unchanged. It skips insertion if `context.Name == \"Dashboard\"` or an ancestor already has ID `Dashboard`; it does not compare localized title text. Otherwise it inserts a localized Dashboard ancestor at index `0`, with ID `Dashboard` and `Url = \"~/\" + adminOptions.AdminUrlPrefix`. Later providers can move it just like any other ancestor.\n\nIts `Permissions` list contains the static `OrchardCore.AdminDashboard.Permissions.AccessAdminDashboard` permission object, so no named-permission lookup is required. Denied access makes a visible Dashboard ancestor plain text. The Dashboard page uses `name=\"Dashboard\"` and `title=\"@T[\"Dashboard\"]\"` so only the explicit current node, with ID `Title`, represents Dashboard.\n\nFor visible admin trails, Dashboard can be added when there are no other ancestors, but the explicit current title is still appended afterward. Hidden trails skip this provider entirely; it never affects title text. Disabling the feature removes this provider contribution; there is no Dashboard option on the tag helper.\n\n## List order\n\nAncestor order comes only from the list. Inline children are collected in declaration order; providers can use `Add`, `Insert`, `RemoveAt`, or other list operations to change it. The helper preserves the final list order without sorting.\n\n`BreadcrumbItem` has no `Position` property and does not implement `IPositioned`. The child helper has no `position` attribute. List changes cannot displace the current page: the helper appends the explicit title after every ancestor.\n\n## Disabled admin trails\n\n**Show breadcrumb** under **Configuration > Settings > Admin** (`AdminSettings.ShowBreadcrumb`, default on) controls admin trail visibility, not front-end trails.\n\nWhen disabled, the helper **skips all providers and breadcrumb items**, regardless of provider registration. It never calls `GetChildContentAsync`, never resolves, enumerates, constructs, or invokes providers (including Dashboard), and creates no `BreadcrumbContext` or items, including the title node. Child text and links are not evaluated.\n\nOnly the explicit title supplies heading/browser-title text. When needed, it is formatted once through `WriteTo(writer, HtmlEncoder.Default)` and HTML-decoded once. The helper safely encodes the normalized string, renders the heading directly with `oc-breadcrumb-title`, and registers the browser title when `page-title` is true. It does not generate URLs, resolve permission names, authorize links, or create shapes. Breadcrumb templates and alternates do not run.\n\nWhen the trail is hidden and both `heading=\"\"` and `page-title=\"false\"` are set, the helper validates the non-null title but also skips `WriteTo`. Parent attribute expressions and Razor code or model lookups outside child content still run: `T[...]` results and `HtmlContentString` wrappers are created before the helper, even when it skips `WriteTo`. All child-only work is skipped for every hidden trail; do not add visibility guards to views. For a visible trail, these attributes suppress only heading/browser-title output, not ancestors, providers, or the explicit current node.\n\n## Theming and low-level shapes\n\nVisible trails use the existing `Breadcrumb` and `BreadcrumbItem` shapes and their name, ID, and display-type alternates:\n\n- Whole trail: `Breadcrumb.cshtml`, `Breadcrumb-ContentsEdit.cshtml`, `Breadcrumb.DetailAdmin.cshtml`, `Breadcrumb-ContentsEdit.DetailAdmin.cshtml`.\n- One node: `BreadcrumbItem.cshtml`, `BreadcrumbItem-Dashboard.cshtml`, `BreadcrumbItem-ContentsEdit-Title.DetailAdmin.cshtml`.\n\n`Breadcrumb.cshtml` owns both the visible trail and heading. Override it to rearrange them. Keep text safely encoded. See `references/theming.md` for alternate precedence, encoding, and the distinction between hidden admin trails and shape-level `ShowTrail`.\n\nThe helper assigns the current node `Id = \"Title\"`. The existing alternate factory provides `BreadcrumbItem-Title.cshtml`, `BreadcrumbItem-[Name]-Title.cshtml`, `BreadcrumbItem-Title.[DisplayType].cshtml`, and `BreadcrumbItem-[Name]-Title.[DisplayType].cshtml`; no extra attribute or shape type is needed. Ancestor IDs still come from child `id` or provider-supplied `Id`.\n\n`IShapeFactory.BreadcrumbAsync` is a low-level presentation API for prebuilt `BreadcrumbItem` objects. Its caller supplies the complete ordered sequence, including the terminal current node, with authorized `Href` values and `IsCurrent` already resolved. It does not append a title node, invoke providers, build trails from names, load parents, add Dashboard, or register the browser title. It is not the recommended authoring path for a view.\n\n## Gotchas\n\n- **Missing title is invalid.** Every helper, including a self-closing one, needs a non-null `IHtmlContent` title, even on the no-output path. `HtmlString.Empty` is valid and still leaves a current node in a visible trail.\n- **Wrap non-HTML title values safely.** Pass `@T[...]` directly, never its `.Value`. Use `new HtmlContentString(value ?? string.Empty)` for strings and `new HtmlContentString(localizedTitle.Value ?? string.Empty)` for non-HTML localizer results; never `Html.Raw`.\n- **HTML-aware input is still text-only output.** Do not render normalized title text as raw HTML or decode it again.\n- **Children are ancestors only.** Declare the current page in parent `title`; the helper appends it with ID `Title`, so do not duplicate it with a child.\n- **Providers cannot change the current title.** Clearing or reordering ancestors never removes or replaces the appended current node.\n- **Hidden means title-only.** The helper skips all provider resolution and execution, child evaluation, contexts, and items. Provider contexts created for visible trails always have `ShowTrail == true`.\n- **No visibility guards in views.** Do not read `AdminSettings.ShowBreadcrumb` or wrap lookups or markup in setting-dependent branches; visibility belongs to the helper.\n- **Named permissions are deferred.** Do not look them up while collecting or postprocessing ancestors; removed or unlinkable ancestors do not need that work.\n- **Second heading.** Remove the old title output when the helper owns the title.\n- **Wrong dictionary type.** `route-values` accepts `IDictionary<string, string>`, not an anonymous object or `RouteValueDictionary`.\n- **Link checks are not endpoint security.** Keep authorization on the target action; `permission` and `link-enabled` control only the rendered link.\n- **Renamed identifiers affect themes and providers.** Keep trail names and ancestor IDs stable. The automatic current-node ID is always `Title`; screen-specific title alternates use the trail name.\n- **Hidden trails bypass templates.** A breadcrumb shape override cannot change the helper's directly rendered disabled-state heading.\n\n## References\n\n- `references/theming.md` - alternate tables, display types, front-end usage, and heading behavior.\n- `src/OrchardCore/OrchardCore.Navigation.Core/` - `BreadcrumbTagHelper`, `BreadcrumbItemTagHelper`, `IBreadcrumbProvider`, `BreadcrumbContext`, `BreadcrumbItem`, and `ShapeFactoryExtensions`.\n- `src/OrchardCore.Modules/OrchardCore.AdminDashboard/Services/DashboardBreadcrumbProvider.cs` - feature-registered admin ancestor provider.\n- `src/OrchardCore.Modules/OrchardCore.Navigation/` - `BreadcrumbShapes`, `BreadcrumbAlternatesFactory`, and the breadcrumb views.\n- `src/docs/reference/modules/Navigation/README.md` - canonical breadcrumb reference.\n- `AGENTS.md` - project conventions.\n",".agents/skills/orchardcore-localization/SKILL.md":"---\nname: orchardcore-localization\ndescription: Localizes OrchardCore apps — UI strings (IStringLocalizer/IViewLocalizer S, T, H), LocalizationSource, updating and exporting checked-in POT templates, localization analyzers, PO files and pluralization, content translation (LocalizationPart, culture picker), and supported-culture configuration. Use when adding or changing localizable strings, updating POT files, fixing OCLOC diagnostics, translating strings, adding a culture, or making content multilingual.\n---\n\n# OrchardCore Localization\n\nThis skill guides you through OrchardCore localization following project conventions.\n\nThree layers, often confused:\n\n- **UI/code localization** — translate hard-coded strings in C#/Razor/Liquid via `IStringLocalizer` (`S`), `IViewLocalizer` (`T`), `IHtmlLocalizer` (`H`). Checked-in **POT templates** describe the source strings and are embedded in assemblies; translated **PO files** supply runtime translations.\n- **Content localization** — translate *content items* across cultures via `LocalizationPart` and the `OrchardCore.ContentLocalization` module.\n- **Culture configuration** — which cultures the site supports and how the request culture is chosen.\n\n## Decide which layer\n\n| Goal | Layer |\n|------|-------|\n| Translate a label/message in code or a view | UI localization (`S`/`T`/`H`) + PO file |\n| Pluralize (\"1 item\" / \"{0} items\") | `S.Plural(...)` + PO `msgid_plural` |\n| Add, change, remove, or move a source string | Update the owning project's checked-in POT template (Workflow D) |\n| Export source strings for Crowdin or another translation platform | Export POT templates (Workflow D), then deploy translated PO files |\n| Translate a blog post into French | Content localization (`LocalizationPart`) |\n| Add a supported language / set default | Localization settings (`LocalizationSettings`) |\n| Translate data-driven values (e.g. taxonomy terms) | Data Localization (`DataLocalization` module) |\n\n## Workflow A: localize UI strings\n\n### Step 1: Inject the right localizer\n\n| Localizer | Convention | Where |\n|-----------|-----------|-------|\n| `IStringLocalizer<T>` | field `S` | drivers, services, controllers |\n| `IViewLocalizer` | property `T` | Razor views (already on `RazorPage`) |\n| `IHtmlLocalizer<T>` | field `H` | when the translation contains HTML |\n\n```csharp\npublic sealed class AliasPartDisplayDriver : ContentPartDisplayDriver<AliasPart>\n{\n    internal readonly IStringLocalizer S;\n\n    public AliasPartDisplayDriver(IStringLocalizer<AliasPartDisplayDriver> localizer)\n        => S = localizer;\n}\n```\n\nIn Razor: `@T[\"Welcome to Orchard Core\"]`. In a class: `S[\"A value is required for {0}.\", name]`. Keep keys as compile-time constants and pass values as format arguments, not interpolated keys. In Liquid: `{{ \"Welcome to Orchard Core\" | t }}`.\n\n### Step 2: Pluralize when counting\n\n```csharp\nS.Plural(count, \"{0} book\", \"{0} books\");\n// IHtmlLocalizer: H.Plural(count, \"{0} book\", \"{0} books\")\n```\n\n### Step 3: Reuse shared sources for strings declared without a localizer\n\nStrings declared before a localizer is available, such as deployment step categories and titles, are `LocalizationSource` instances translated at display time with `IStringLocalizerFactory.Localize()`. Don't declare them in the class that uses them:\n\n- Use `CommonLocalizationSources` (`OrchardCore.Abstractions`, namespace `OrchardCore.Localization`) for text that several projects use with the same meaning, such as `CommonLocalizationSources.ContentManagement`, or that is general to every module, such as `CommonLocalizationSources.Settings`. Look for an existing member before adding one. Add a member only when another project uses the text too; a module name or a category used by one project goes in that project's class.\n- Declare the other strings in an `internal static class {Area}LocalizationSources` at the root of the module, for example `LuceneLocalizationSources`, with `LocalizationSource.Create(\"...\", typeof({Area}LocalizationSources))`, which extraction tools can find.\n- Keep permission descriptions in their `*Permissions` class, and strings specific to a view in the view, with `T`.\n\n```csharp\n[JsonIgnore] // Not inherited from the base property; LocalizationSource can't be serialized.\npublic override LocalizationSource Category => CommonLocalizationSources.Search;\n\n[JsonIgnore]\npublic override LocalizationSource Title => LuceneLocalizationSources.LuceneSearchIndexes;\n```\n\nThe PO context is the class that declares the source, for example `msgctxt \"OrchardCore.Localization.CommonLocalizationSources\"`.\n\nResolve a source at display time with `stringLocalizerFactory.Localize(source, args)` or `htmlLocalizerFactory.Localize(source, args)`, using the current tenant's factory. Pass arguments to the factory extension, not to `LocalizationSource.Create`; never cache a translated result in a static field. For plurals, use `stringLocalizerFactory.Plural(count, source, \"{0} items\")` (or the HTML factory equivalent), with a constant plural form. The singular source must have a context; `count` supplies `{0}` automatically.\n\n### Step 4: Provide PO translations\n\nPO files are discovered (in order) from each module's `Localization/`, a global `/Localization`, the tenant's `App_Data/Sites/{tenant}/Localization`, and culture folders. A PO entry:\n\n```po\nmsgctxt \"OrchardCore.Admin.Views.AdminDashboard\"\nmsgid \"Welcome to Orchard Core\"\nmsgstr \"Bienvenue sur Orchard Core\"\n```\n\n`msgctxt` = `{Namespace}.{Class}` (services) or `{Namespace}.{ViewPath}` (views). Plurals:\n\n```po\nmsgctxt \"OrchardCore.AdminMenu.Views.Menu.List\"\nmsgid \"1 item\"\nmsgid_plural \"{0} items\"\nmsgstr[0] \"{0} élément\"\nmsgstr[1] \"{0} éléments\"\n```\n\nSee `references/localization.md` for the full PO discovery order and headers.\n\n### Step 5: Update and commit the source templates\n\nAfter adding, changing, removing, or moving a localized string, update the affected project's POT file using Workflow D below. Context changes and moving shared sources can change translation identities even when the displayed text stays the same.\n\n## Workflow B: localize content\n\n### Step 1: Enable the feature, attach the part\n\nEnable `OrchardCore.ContentLocalization` (Content Localization). Attach `LocalizationPart` to the content type (admin or recipe). It carries:\n\n```csharp\npublic class LocalizationPart : ContentPart, ILocalizable\n{\n    public string LocalizationSet { get; set; } // links all translations\n    public string Culture { get; set; }         // this version's culture\n}\n```\n\nOn create, the part handler generates a unique `LocalizationSet` and sets the default culture automatically.\n\n### Step 2: Translate\n\nFrom the editor's Localization, \"Translate\" creates a sibling content item sharing the same `LocalizationSet` with a different `Culture`. Programmatically:\n\n```csharp\nvar translated = await _contentLocalizationManager.LocalizeAsync(contentItem, \"fr-FR\");\n```\n\n### Step 3: Surface translations / culture picker\n\n`IContentLocalizationManager` fetches across a set:\n\n```csharp\nawait _contentLocalizationManager.GetContentItemAsync(localizationSet, \"fr\");\nawait _contentLocalizationManager.GetItemsForSetAsync(localizationSet);\n```\n\nLiquid: `{{ Model.ContentItem.Content.LocalizationPart.LocalizationSet | localization_set: \"en\" }}`. The Content Culture Picker feature adds a front-end language switcher and a `ContentRequestCultureProvider` that derives the culture from the localized route.\n\n## Workflow C: configure cultures\n\nRecipe `settings` step:\n\n```json\n{\n  \"name\": \"settings\",\n  \"LocalizationSettings\": {\n    \"DefaultCulture\": \"en-US\",\n    \"SupportedCultures\": [ \"en-US\", \"fr-FR\", \"es-ES\" ],\n    \"FallBackToParentCulture\": true\n  }\n}\n```\n\nThe Localization module reads these at startup and calls `app.UseRequestLocalization(...)` with the configured default + supported cultures.\n\n## Workflow D: update and export POT templates\n\n### Step 1: Use the existing build integration\n\nIn this repository, `OrchardCore.Commons.props` already imports the localization targets for framework projects, modules, and themes. Do not add redundant imports or package references. Outside the repository, each project whose strings need extraction must reference `OrchardCore.Localization.Build` with `PrivateAssets=\"all\"` and a version matching Orchard Core.\n\nEach project's template is `<AssemblyName>.pot`, next to its project file. A **normal build only embeds an existing template** as `<AssemblyName>.Localization.pot`; it neither extracts strings nor refreshes a stale template. A project without a template embeds nothing. `GenerateLocalizationCatalog=true` enables this integration, not automatic extraction.\n\n### Step 2: Regenerate from source\n\nRun commands from the repository root. Prefer updating just the affected project:\n\n```powershell\ndotnet msbuild src\\OrchardCore.Modules\\OrchardCore.Admin\\OrchardCore.Admin.csproj -t:UpdateLocalizationCatalog\n```\n\nUse `-restore` with `dotnet msbuild` when dependencies need restoring. The target updates only that project's template, not those of its references. For an application and its project references, or the entire solution:\n\n```powershell\ndotnet build src\\OrchardCore.Cms.Web\\OrchardCore.Cms.Web.csproj -p:UpdateLocalizationCatalogs=true\ndotnet build OrchardCore.slnx -p:UpdateLocalizationCatalogs=true\n```\n\nChoose the appropriate scope, rather than running every command. Use the SDK selected by `global.json`: extraction loads that SDK's Roslyn and Razor compilers. The source-tree worker is built on demand; no separate PoExtractor installation is needed.\n\nUpdates create or rewrite the template, or remove it if no messages remain. Unchanged inputs reuse the cache under `obj\\`; unchanged output is not rewritten. Do not hand-edit generated entries, headers, or references. Templates use deterministic ordering, UTF-8 and LF, no timestamps, and file references without line numbers by default. Multi-targeted projects extract using their first target framework; `DEBUG` and `TRACE` are excluded during extraction.\n\n### Step 3: Review the result and diagnostics\n\n```powershell\ngit status --short -- '*.pot'\ngit --no-pager diff -- '*.pot'\n```\n\nReview new files too (a normal `git diff` does not show untracked templates). Check the expected `msgctxt`, `msgid`, `msgid_plural`, source references, format flags, and format-argument comments. Keep `msgstr` values empty in POT files; translations belong in culture-specific PO files. Commit added, modified, and deleted POT files with their source changes. CI updates the solution's templates and fails if any are uncommitted or out of date.\n\nExtraction covers C#, Razor, literal Liquid `t` inputs, context-bearing `LocalizationSource` declarations, and explicit constant DataAnnotations validation messages. It does not extract JavaScript/TypeScript directly: expose their strings through a C# `IJSLocalizer` implementation. Fix dynamic keys or unresolved contexts at the source instead of inserting guessed POT entries.\n\nIDE/build analyzers report `OCLOC101` (constant source missing a context), `OCLOC102` (different declaring context, informational), `OCLOC103` (nonconstant source value), and `OCLOC104` (nonconstant localizer key or plural forms). The update worker reports `OCLOC001`-`OCLOC009`; see the reference for their meanings. Extraction errors always fail; `-p:LocalizationStrict=true` also fails on extraction warnings.\n\nUse `[SkipLocalizationExtraction]` only on forwarding infrastructure whose keys are declared elsewhere; it excludes marked classes/methods and nested declarations from extraction and analyzer diagnostics, not application calls to those helpers. For an intentionally dynamic application call, locally suppress `OCLOC104` with an explanation of where its keys originate. Do not hide missing application strings with broad suppressions.\n\n### Step 4: Export templates when requested\n\nBuild with an **absolute**, dedicated output directory to collect existing templates without regenerating them:\n\n```powershell\n$output = Join-Path $PWD 'artifacts\\localization'\ndotnet build OrchardCore.slnx \"-p:LocalizationCatalogOutputPath=$output\"\n```\n\nFiles are copied to `<output>\\<TargetFramework>\\<AssemblyName>.pot`. Add `-p:UpdateLocalizationCatalogs=true` only when an update is also intended. Use an empty export directory for an exact current snapshot; export does not remove stale files. Only built projects contribute templates, not prebuilt NuGet dependencies. Upload POT files to the translation platform, preserve contexts/plurals/placeholders, and download translated PO files to a supported runtime localization location. Embedded POT files alone do not change runtime translations.\n\nFor configuration properties, custom Liquid inputs, extraction diagnostics, and library entry points, see `references/localization.md`.\n\n## Quick Reference\n\n### Localizers\n\n| Symbol | Type | Output |\n|--------|------|--------|\n| `S` | `IStringLocalizer<T>` | plain string |\n| `T` | `IViewLocalizer` | encoded view string |\n| `H` | `IHtmlLocalizer<T>` | trusted translation markup; format arguments are HTML-encoded |\n\n### Content localization APIs\n\n| Member | Purpose |\n|--------|---------|\n| `IContentLocalizationManager.LocalizeAsync(item, culture)` | create a translation |\n| `.GetContentItemAsync(set, culture)` | one translation |\n| `.GetItemsForSetAsync(set)` | all translations |\n| `.GetItemsForSetsAsync(sets, culture)` | batch |\n| `ILocalizationService.GetDefaultCultureAsync()` / `GetSupportedCulturesAsync()` | culture config |\n\n### Request culture providers\n\n| Provider | Picks culture from |\n|----------|--------------------|\n| `ContentRequestCultureProvider` | the localized content route (added first) |\n| `AdminCookieCultureProvider` | `admin_culture_*` cookie, admin paths only |\n| cookie / querystring / Accept-Language | ASP.NET Core defaults |\n\n### Liquid filters\n\n`localization_set: \"fr\"` — translated item; `switch_culture_url` — culture-switch URL.\n\n## Gotchas\n\n- `msgctxt` must exactly match the namespace+class (or namespace+view path). Wrong context → string stays untranslated.\n- `IStringLocalizer<T>` context comes from `T`'s type — inject the localizer typed to the class that owns the strings.\n- `LocalizationSet` links translations; never share one set across unrelated items, and don't overwrite it.\n- Add a culture to `SupportedCultures` before content can be translated into it.\n- DataAnnotations need `AddDataAnnotationsPortableObjectLocalization()` (the Localization module registers it).\n- Ordinary builds do not refresh POT templates; always explicitly update and commit them with string/context changes.\n- A gettext source plural has exactly two constant forms; translated PO files can have more forms, selected by the culture's plural rule.\n- Set `GenerateLocalizationCatalog=false` only when intentionally disabling both embedding and updating, not as a fix for extraction errors.\n\n## References\n\n- `references/localization.md` — POT configuration and diagnostics, extraction APIs, PO discovery order, contexts, plural forms, content localization internals, startup wiring\n- `src/docs/reference/modules/Localize/README.md` (repo)\n- `src/docs/reference/modules/ContentLocalization/README.md` (repo)\n- `src/docs/reference/modules/DataLocalization/README.md` (repo)\n- `src/docs/reference/modules/Localize/javascript-localization.md` (repo) — `IJSLocalizer` bridge; no direct JavaScript extraction\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-nswag-regenerate/SKILL.md":"---\nname: orchardcore-nswag-regenerate\ndescription: Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to \"regenerate the NSwag client(s)\", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration.\n---\n\n# OrchardCore NSwag Regenerate\n\nRegenerates the OpenApi module's NSwag-generated C#/TypeScript API clients and verifies the regeneration produces a stable, non-reshuffled diff.\n\n## Preferred: `tools/OpenApiClientGenerator` (automated, no browser/dev-server needed)\n\nA console project that does the whole pipeline headlessly:\n\n```bash\ndotnet run --project tools/OpenApiClientGenerator -c Release\nyarn build   # refreshes the Vue app's bundled JS against the new TS client\n```\n\nIt boots the CMS in-process on an ephemeral port, uses `OrchardCore.AutoSetup` with\n`src/OrchardCore.Cms.Web/Recipes/openapi-generation-setup.recipe.json` (`issetuprecipe: true`)\nto provision the **Default** tenant with the same feature set as `openapi-generation.recipe.json`\nbelow, fetches `swagger.json`, writes a scratch `.nswag` config pointing at that capture, and\nshells out to the `nswag` CLI (same prerequisite as the manual path: `dotnet tool install -g\nNSwag.ConsoleCore`). Source: `tools/OpenApiClientGenerator/Program.cs`.\n\n**Why Default tenant specifically**: `OrchardCore.Tenants` (and `.Distributed`,\n`.FeatureProfiles`) have `DefaultTenantOnly = true` in their `[Feature]` manifest attribute —\nthey can only be enabled on the Default/Host shell, never a secondary tenant. A Blog-recipe\nfunctional-test tenant cannot enable `OrchardCore.Tenants`, which is why this tool targets the\nDefault shell via `AutoSetup` rather than reusing the functional-test harness's tenant pattern.\n\nThis tool only regenerates the clients against whatever API surface the current source produces\n— it does not add a CI drift-check gate (that was scoped out; see `nswag-automation-plan.md` in\ngit stash history for the fuller original proposal, \"Part 1\" of which this tool implements).\n\nThe manual procedure below still applies if you need to regenerate against a real running dev\nserver (e.g. to inspect `swagger.json` interactively) or investigate the tool's own behavior.\n\n## Where things live\n\n- Config: `src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag` — **this file already exists**, do not assume it's missing. It defines both generators:\n  - `openApiToCSharpClient` → outputs `Services/OpenApiClient.cs` (relative to the module folder)\n  - `openApiToTypeScriptClient` → outputs `../../../.scripts/bloom/services/OpenApiClient.ts`\n- Source document: `documentGenerator.fromDocument.url` points at a running instance's Swashbuckle endpoint, `https://localhost:5001/swagger/v1/swagger.json`. NSwag also accepts a **local file path** in this same field instead of a URL — useful for reproducible/offline generation (see below).\n- Recipe: `src/OrchardCore.Modules/OrchardCore.OpenApi/Recipes/openapi-generation.recipe.json` (name `OpenApiGeneration`) enables every feature that exposes an API endpoint: `OrchardCore.Contents`, `OrchardCore.Queries`, `OrchardCore.Tenants`, `OrchardCore.Search.Lucene`, `OrchardCore.Search.Elasticsearch`, `OrchardCore.OpenApi`. It also contains a `Settings` step setting `OpenApiSettings.AllowAnonymousSchemaAccess: true` — anonymous schema access is **disabled by default** (the middleware returns 401 for unauthenticated `swagger.json` fetches), and both NSwag paths below fetch the schema anonymously. The setup recipe used by the generator tool (`openapi-generation-setup.recipe.json`) has the same step. Note the `Settings` step replaces the whole stored `OpenApiSettings` object, wiping any OAuth configuration on the tenant it runs on — fine for throwaway generation tenants, worth knowing on a configured one.\n  - It has `\"issetuprecipe\": false`, so it does **not** appear in the new-tenant/site-setup recipe dropdown. Run it against an already-set-up tenant instead, via **Admin ▸ Configuration ▸ Recipes**, click \"Run\" next to \"OpenApi Generation\", confirm the modal. (`/Admin/Recipes`, `AdminController.Execute` in `OrchardCore.Recipes`.)\n\n## Manual procedure (fallback)\n\n### NSwag CLI\n\nInstalled as a dotnet tool: `~/.dotnet/tools/nswag` (or `dotnet tool install -g NSwag.ConsoleCore` if missing). Run with:\n\n```bash\nnswag run src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag\n```\n\nThis requires a live server at the configured `url`. For a real regeneration that updates the committed files, run the actual local dev server, set it up with the `OpenApiGeneration` recipe, and run the command above unmodified.\n\n## Reproducible / offline generation (for testing determinism, without touching committed files)\n\n1. Fetch `swagger.json` from a running instance into a scratch file.\n2. Copy the `.nswag` config, and in the copy only change `documentGenerator.fromDocument.url` to the scratch file's path, and both generators' `output` to scratch paths. Do this with a small Python/jq one-liner rather than hand-editing — the config is plain JSON.\n3. `nswag run <scratch-config>`.\n\nThis lets you diff two independent generations without ever touching the real generated files or needing HTTPS/dev-cert setup.\n\n## Determinism: why regeneration used to reshuffle unrelated methods\n\nTwo independent root causes were found and fixed (skrypt/openapi branch):\n\n1. **Swashbuckle's operation order wasn't pinned.** Without an explicit sort, `swagger.json` operations come out in whatever order ASP.NET Core's action discovery enumerates them — which depends on assembly/feature load order in OrchardCore's modular architecture, not source order. Fixed in `OrchardCore.OpenApi/Startup.cs`'s `AddSwaggerGen` call:\n   ```csharp\n   c.OrderActionsBy(apiDesc => $\"{apiDesc.RelativePath}_{apiDesc.HttpMethod}\");\n   ```\n   Route path + verb is fixed at compile time and unique per operation, so this is stable regardless of load order.\n\n2. **A duplicate `operationId` across two operations.** `QueryApiController` used to have one action handling both `GET` and `POST` on `api/queries/{name}` under a single `[EndpointName(\"ApiExecuteQuery\")]`. OpenAPI requires `operationId` to be unique per operation — reusing one across two operations forced NSwag to invent a disambiguating suffix internally, and that suffix logic wasn't deterministic across runs (e.g. `ApiExecuteQueryPOSTAsync` vs `ApiExecuteQueryPOSTPOSTAsync`). Fixed by splitting into two actions with distinct names (`ApiExecuteQueryGet` / `ApiExecuteQueryPost`), delegating to a shared private method — the pattern already used by e.g. `ElasticsearchApiController` (`ApiGetElasticsearchContent`/`ApiPostElasticsearchContent`).\n\n**Lesson for any future controller/endpoint added to the OpenApi-exposed surface**: never reuse the same `[EndpointName]`/`operationId` across two different HTTP verbs on the same route. Give each verb its own action and its own name, even if they share implementation via a private helper.\n\n## Verifying stability empirically\n\nBoot two independently-provisioned tenants (each gets its own `ShellContext` and fresh feature/extension discovery — this is what actually varies, not wall-clock time), run the `OpenApiGeneration` recipe on each, fetch `swagger.json` from each, generate against both, and diff. A stable setup produces byte-identical output except for the tenant's own base-URL prefix embedded in the client's default `baseUrl` (an expected, real difference between distinct tenants — not a bug).\n\nThe existing functional test suite (`test/OrchardCore.Tests.Functional/Tests/Cms/OpenApiTests.cs`) already exercises the relevant plumbing (feature enablement, swagger.json access) if you need a template for scripting this via Playwright/`CmsTestBase`. Any throwaway verification test written for this should be deleted afterward — it's not meant to be a permanent part of the suite.\n",".agents/skills/orchardcore-query-indexing/SKILL.md":"---\nname: orchardcore-query-indexing\ndescription: Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps.\n---\n\n# OrchardCore Query & Indexing\n\nThis skill guides you through OrchardCore's query and indexing systems following project conventions.\n\nTwo distinct systems, often confused:\n\n- **Queries** (`OrchardCore.Queries`) — named, stored queries executed on demand (`SQL`, `Lucene`, `Elasticsearch` sources). Return raw rows or content items.\n- **Indexing** (`OrchardCore.Indexing` + providers) — build full-text **index profiles** that mirror content into Lucene/Elasticsearch/Azure AI Search for searching. Driven by **index handlers**.\n\nSeparate again from **YesSql SQL index tables** (`MapIndex` + `SchemaBuilder`) — those are relational indexes for fast DB queries, covered by the `orchardcore-data-migration` skill, not here.\n\n## Decide what you need\n\n| Goal | System |\n|------|--------|\n| Run a stored SQL/Lucene query, expose via GraphQL/API | Queries |\n| Make content searchable (full-text) | Indexing (index profile + provider) |\n| Index a custom part/field's data into search | `IContentPartIndexHandler` / `IContentFieldIndexHandler` |\n| Fast relational lookup by a column | YesSql `MapIndex` → see `orchardcore-data-migration` |\n| Run a search box over an index | Search (`ISearchService`) |\n\n## Workflow A: define and run a query\n\n### Step 1: Pick a source\n\n`Query` has `Name`, `Source` (`Sql`/`Lucene`/`Elasticsearch`), `ReturnContentItems`, and source-specific metadata (e.g. a SQL `Template`).\n\n### Step 2: Create via recipe or manager\n\nRecipe step:\n\n```json\n{\n  \"name\": \"queries\",\n  \"Queries\": [\n    {\n      \"Source\": \"Sql\",\n      \"Name\": \"RecentPosts\",\n      \"Template\": \"select DocumentId from ContentItemIndex where ContentType = 'BlogPost'\",\n      \"ReturnContentItems\": true\n    }\n  ]\n}\n```\n\nProgrammatically:\n\n```csharp\nvar query = await _queryManager.NewAsync(\"Sql\", data);\nawait _queryManager.SaveAsync(query);\n```\n\n### Step 3: Execute\n\n```csharp\nvar query = await _queryManager.GetQueryAsync(\"RecentPosts\");\nvar results = await _queryManager.ExecuteQueryAsync(query, new Dictionary<string, object>\n{\n    [\"maxResults\"] = 10,\n});\nforeach (var item in results.Items) { /* JsonObject or ContentItem */ }\n```\n\nSQL templates are Liquid-rendered (parameters become Liquid vars), parsed by `SqlParser`, then run via Dapper. `ReturnContentItems = true` loads `ContentItem`s by `DocumentId`; otherwise rows are returned as `JsonObject`.\n\n## Workflow B: index content for search\n\n### Step 1: Create an index profile\n\nA profile binds a provider (`Lucene`/`Elasticsearch`/`AzureAISearch`) + a type (`Content`) + which content types to index.\n\n```json\n{\n  \"name\": \"CreateOrUpdateIndexProfile\",\n  \"indexes\": [\n    {\n      \"Name\": \"BlogPostsLucene\",\n      \"IndexName\": \"blogposts\",\n      \"ProviderName\": \"Lucene\",\n      \"Type\": \"Content\",\n      \"Properties\": {\n        \"ContentIndexMetadata\": {\n          \"IndexLatest\": false,\n          \"IndexedContentTypes\": [ \"BlogPost\" ],\n          \"Culture\": \"any\"\n        },\n        \"LuceneIndexMetadata\": { \"AnalyzerName\": \"standard\", \"StoreSourceData\": true }\n      }\n    }\n  ]\n}\n```\n\nContent changes raise indexing tasks; `ContentIndexingService` resolves the content item and calls every part/field index handler to build a `DocumentIndex`, which the provider stores.\n\n### Step 2: Index custom part/field data\n\nFor a custom part, implement `ContentPartIndexHandler<TPart>`:\n\n```csharp\npublic class AliasPartIndexHandler : ContentPartIndexHandler<AliasPart>\n{\n    public override Task BuildIndexAsync(AliasPart part, BuildPartIndexContext context)\n    {\n        var options = DocumentIndexOptions.Keyword | DocumentIndexOptions.Store;\n        foreach (var key in context.Keys)\n        {\n            context.DocumentIndex.Set(key, part.Alias, options);\n        }\n        return Task.CompletedTask;\n    }\n}\n```\n\nFields use `ContentFieldIndexHandler<TField>` with `BuildFieldIndexContext`. `context.Keys` are the index field names to populate; `DocumentIndexOptions` flags control storage:\n\n| Flag | Effect |\n|------|--------|\n| `Store` | keep the value retrievable from the index |\n| `Keyword` | index as a non-analyzed keyword (exact match) |\n| `Sanitize` | strip HTML before indexing |\n| `None` | analyzed full-text (default text) |\n\n### Step 3: Register the handler\n\n```csharp\nservices.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();\n// field:\nservices.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();\n```\n\n### Step 4: Reset / rebuild\n\nRecipe steps `ResetIndex` (re-sync from last position) and `RebuildIndex` (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.\n\n## Quick Reference\n\n### Query APIs\n\n| Type | Member |\n|------|--------|\n| `IQueryManager` | `NewAsync`, `SaveAsync`, `GetQueryAsync`, `ExecuteQueryAsync`, `ListQueriesAsync` |\n| `IQuerySource` | `Name`, `ExecuteQueryAsync(query, parameters)` — implement to add a source |\n| `IQueryResults` | `IEnumerable<object> Items` |\n\n### Indexing APIs\n\n| Type | Member |\n|------|--------|\n| `IIndexProfileManager` | `NewAsync`, `CreateAsync`, `UpdateAsync`, `FindByNameAsync`, `SynchronizeAsync`, `ResetAsync` |\n| `IDocumentIndexHandler` | `BuildIndexAsync`, `DocumentsAddedOrUpdatedAsync`, `DocumentsDeletedAsync` |\n| `IContentPartIndexHandler` / `IContentFieldIndexHandler` | `BuildIndexAsync(...)` to populate `DocumentIndex` |\n| `IIndexManager` (per provider) | `CreateAsync`, `RebuildAsync`, `DeleteAsync`, `ExistsAsync` |\n| `IDocumentIndexManager` | `AddOrUpdateDocumentsAsync`, `DeleteDocumentsAsync` |\n| `DocumentIndex` | `Set(name, value, options)` overloads; `Types` enum |\n\n### Register an indexing source\n\n```csharp\nservices.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(\n    providerName, implementationType);\n// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);\n```\n\n### Search\n\n`ISearchService.SearchAsync(IndexProfile index, string term, int start, int size)` → `SearchResult`. Providers: `OrchardCore.Search.Lucene`, `.Elasticsearch`, `.AzureAI`.\n\n## Gotchas\n\n- \"Index\" is overloaded: full-text **index profiles** (this skill) vs YesSql **MapIndex tables** (`orchardcore-data-migration`). Don't mix them.\n- Query recipe step is `queries`; index profile step is `CreateOrUpdateIndexProfile`. Older `lucene-index` / `ElasticIndexSettings` steps are deprecated — prefer `CreateOrUpdateIndexProfile`.\n- `IndexLatest: true` indexes draft (latest) versions; `false` indexes published. Match to your search UI.\n- SQL query `Template` is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.\n\n## References\n\n- `references/indexing.md` — index profiles, handlers, DocumentIndex, providers, recipe steps\n- `src/docs/reference/modules/Queries/README.md` (repo)\n- `src/docs/reference/modules/Indexing/README.md` (repo)\n- `src/docs/reference/modules/{Lucene,Elasticsearch,SQLIndexing}/README.md` (repo)\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-asset-manager/SKILL.md":"---\nname: orchardcore-asset-manager\ndescription: Builds, watches, and manages frontend assets in OrchardCore. Use when modifying SCSS, JS, TS, or Vue files, adding new assets to a module/theme, or troubleshooting build failures. Covers all asset actions (vite, sass, min, copy, parcel, webpack, concat) and the three-tier package structure.\n---\n\n# OrchardCore Asset Manager\n\nHandles all frontend asset compilation for OrchardCore modules and themes. Assets are discovered via `Assets.json` files under `src/{OrchardCore.Modules,OrchardCore.Themes}/*/`.\n\n## Node.js Version\n\nThe repo requires **Node.js 24.x** (pinned in `.node-version`). The build script auto-detects a mismatch and prompts:\n\n```\n1) Continue anyway\n2) Abort\n3) Install via fnm, Node.js 24.x and build\n4) Install via Volta, Node.js 24.x and build\n```\n\nAlways select **option 1** (continue) or **option 3** (install via fnm). The allowed Bash commands for this project pre-approve both:\n\n```bash\necho \"1\" | yarn build          # continue with current Node.js\necho \"3\" | yarn build          # install Node 24 via fnm and build\n```\n\n## Build Commands\n\nAll commands run from the **repository root** (`/home/skrypt/repo/orchardcore`).\n\n```bash\n# Build all assets\necho \"1\" | yarn build\n\n# Build a specific asset by name\necho \"1\" | yarn build -n media-app\n\n# Build multiple assets\necho \"1\" | yarn build -n media-app,media-field\n\n# Build by tag\necho \"1\" | yarn build -t admin\n\n# Watch a specific asset (dev mode, auto-rebuilds on save)\necho \"1\" | yarn watch -n media-app\n\n# Host with bundler dev server (HMR)\necho \"1\" | yarn host -n media-app\n\n# Clean all build output\necho \"1\" | yarn clean\n```\n\nThe `-n` name maps to the `\"name\"` field in `Assets.json`.\n\n## Local Claude Code Setup (One-time, per developer)\n\n`.claude/` is gitignored. To avoid being prompted on every build command, create `.claude/settings.json` locally with these pre-approved commands:\n\n```json\n{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(echo \\\"1\\\" | yarn build*)\",\n      \"Bash(echo \\\"3\\\" | yarn build*)\",\n      \"Bash(fnm exec --using 24.14.1 -- corepack yarn install*)\",\n      \"Bash(yarn check*)\",\n      \"Bash(yarn lint*)\",\n      \"Bash(echo \\\"1\\\" | yarn dry-run*)\"\n    ]\n  }\n}\n```\n\nDo **not** commit this file or add a `.gitignore` exception for it — it is intentionally kept local to avoid silently pre-approving commands on other developers' machines.\n\n## Prerequisites / Installing Dependencies\n\nBefore building, if packages are missing:\n\n```bash\nfnm exec --using 24.14.1 -- corepack yarn install\n```\n\nAfter modifying any `package.json` (root, `.scripts/assets-manager/`, or a module's `Assets/`), re-run install.\n\n## Built Output\n\nBuilt files go to the module/theme's `wwwroot/` folder and **must be committed** to the repo. After any asset change, commit both the source file and the generated `wwwroot/` files.\n\n## Package Structure (Three-Tier)\n\n| Location | Purpose |\n|---|---|\n| Root `package.json` | Workspace orchestration, version resolutions |\n| `.scripts/assets-manager/package.json` | Build toolchain (Vite, Parcel, Sass, etc.) |\n| `src/.../Assets/package.json` | Runtime deps bundled into that module/theme |\n\nTo add a **runtime dependency** to a module:\n```bash\n# From the module's Assets/ folder\nyarn add some-library@1.2.3\n# Then rebuild\necho \"1\" | yarn build -n asset-name\n```\n\nTo add a **build tool** (affects all assets):\n- Edit `.scripts/assets-manager/package.json`\n- Run `fnm exec --using 24.14.1 -- corepack yarn install`\n\n## Assets.json\n\nEach module/theme with assets has an `Assets.json` at its root (e.g. `src/OrchardCore.Modules/OrchardCore.Media/Assets.json`). See `references/actions.md` for all supported actions.\n\nQuick example:\n```json\n[\n  {\n    \"action\": \"vite\",\n    \"name\": \"media-app\",\n    \"source\": \"Assets/media-app/\",\n    \"tags\": [\"admin\", \"js\"]\n  },\n  {\n    \"action\": \"sass\",\n    \"name\": \"media-styles\",\n    \"source\": \"Assets/scss/media.scss\",\n    \"tags\": [\"admin\", \"css\"]\n  }\n]\n```\n\n## Code Quality Commands\n\nThese do **not** go through the Node.js version prompt — run them directly.\n\n```bash\n# TypeScript type-check all Vue/TS files (vue-tsc --noEmit)\nyarn check\n\n# Lint all JS/TS/Vue files (ESLint)\nyarn lint\n\n# Preview which files would be built/copied without writing anything\necho \"1\" | yarn dry-run\necho \"1\" | yarn dry-run -n media-app   # scoped to one asset\n```\n\n- **`yarn check`** — runs `vue-tsc --noEmit`; catches type errors across all Vue/TS source files. Run before committing TS/Vue changes. Does **not** support `-n`; to check a specific module, point it at that module's tsconfig directly: `yarn vue-tsc --noEmit -p src/OrchardCore.Modules/OrchardCore.Media/Assets/media-app/tsconfig.json`\n- **`yarn lint`** — runs ESLint across the repo. Accepts a file or directory to scope it: `yarn lint src/OrchardCore.Modules/OrchardCore.Media/Assets/`. Fix any errors before committing.\n- **`yarn dry-run`** — logs what the build would do (copy targets, output paths) without touching any files. Useful when adding a new `Assets.json` entry to verify paths before the first real build.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `Cannot find package '@tailwindcss/vite'` | Run `fnm exec --using 24.14.1 -- corepack yarn install` |\n| Parcel cache stale after deleting output | Run `echo \"1\" \\| yarn clean` then rebuild |\n| Changes not reflected | Confirm the built `wwwroot/` files changed; rebuild if not |\n| Ran a full-repo build by accident (wrong `-n` value, e.g. package.json name instead of `Assets.json` \"name\") | `git status --short` immediately; `git checkout --` every changed path outside your target module/theme before committing |\n| Edited source but bug still repros in the running app | You forgot to rebuild. `stat -c '%y' <source>.ts` vs `stat -c '%y' wwwroot/Scripts/<bundle>.js` — if the bundle is older, run `yarn build -n <asset-name>` |\n\n## References\n\n- `references/actions.md` — All Assets.json action types with examples\n</content>\n</invoke>",".agents/skills/orchardcore-module-creator/SKILL.md":"---\nname: orchardcore-module-creator\ndescription: Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality.\n---\n\n# OrchardCore Module Creator\n\nThis skill guides you through creating new OrchardCore modules following project conventions.\n\n## Prerequisites\n\n- OrchardCore repository at `D:\\orchardcore`\n- .NET SDK 10.0+ installed\n\n## Module Creation Workflow\n\n### Step 1: Determine Module Type\n\n**What kind of module are you creating?**\n\n| Type | Description | Key Components |\n|------|-------------|----------------|\n| **Content Part** | Adds data/behavior to content items | Part, Driver, Views |\n| **Content Field** | Custom field type | Field, Driver, Views |\n| **Settings** | Site-wide configuration | SiteSettings, Driver |\n| **Admin Feature** | Admin pages/tools | Controller, Views, Menu |\n| **API** | REST endpoints | ApiController |\n| **Background Task** | Scheduled jobs | IBackgroundTask |\n\n### Step 2: Create Module Directory\n\n```bash\n# Create module folder\nmkdir src/OrchardCore.Modules/OrchardCore.YourModule\ncd src/OrchardCore.Modules/OrchardCore.YourModule\n```\n\n### Step 3: Create Required Files\n\nEvery module needs these three files:\n\n1. **Manifest.cs** - Module metadata\n2. **Startup.cs** - Service registration  \n3. **OrchardCore.YourModule.csproj** - Project file\n\nSee `references/module-structure.md` for templates.\n\n### Step 4: Add Components Based on Type\n\n**For Content Part modules:**\n```\nModels/YourPart.cs\nViewModels/YourPartViewModel.cs\nDrivers/YourPartDisplayDriver.cs\nViews/YourPart.cshtml\nViews/YourPart_Edit.cshtml\n```\n\n**For Admin modules:**\n```\nControllers/AdminController.cs\nViews/Admin/Index.cshtml\nAdminMenu.cs\nPermissionProvider.cs\n```\n\n**For Data-storing modules:**\n```\nMigrations.cs\nIndexes/YourIndex.cs\n```\n\nSee `references/patterns.md` for code templates.\n\n### Step 5: Register in Startup.cs\n\n```csharp\npublic override void ConfigureServices(IServiceCollection services)\n{\n    // Content part\n    services.AddContentPart<YourPart>()\n        .UseDisplayDriver<YourPartDisplayDriver>();\n    \n    // Services\n    services.AddScoped<IYourService, YourService>();\n    \n    // Migrations (if storing data)\n    services.AddDataMigration<Migrations>();\n    \n    // Permissions (if securing features)\n    services.AddPermissionProvider<PermissionProvider>();\n    \n    // Navigation (if adding admin menu)\n    services.AddNavigationProvider<AdminMenu>();\n}\n```\n\n### Step 6: Build and Test\n\n```bash\n# Build the module\ncd D:\\orchardcore\ndotnet build src/OrchardCore.Modules/OrchardCore.YourModule\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n\n# Enable the feature in Admin → Features\n```\n\n## Quick Reference\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Module folder | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Namespace | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Feature ID | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Content Part | `NamePart` | `RatingPart` |\n| Driver | `NamePartDisplayDriver` | `RatingPartDisplayDriver` |\n| View | `PartName.cshtml` | `RatingPart.cshtml` |\n| Edit View | `PartName_Edit.cshtml` | `RatingPart_Edit.cshtml` |\n\n### Common Dependencies\n\nAdd to `.csproj` as needed:\n\n```xml\n<!-- Core module support -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Module.Targets\\OrchardCore.Module.Targets.csproj\" />\n\n<!-- Content management -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.ContentManagement\\OrchardCore.ContentManagement.csproj\" />\n\n<!-- Admin UI -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Admin\\OrchardCore.Admin.csproj\" />\n```\n\n### Feature Categories\n\nUse in `Manifest.cs`:\n- `Content Management`\n- `Content`\n- `Navigation`\n- `Security`\n- `Infrastructure`\n- `Theming`\n- `Developer`\n\n## References\n\n- `references/module-structure.md` - Directory layout and file templates\n- `references/patterns.md` - Code patterns (parts, drivers, handlers, etc.)\n- `references/examples.md` - Complete module examples\n- `AGENTS.md` (repo root) - Coding conventions and build commands\n",".agents/skills/orchardcore-recipe-creator/SKILL.md":"---\nname: orchardcore-recipe-creator\ndescription: Creates OrchardCore setup recipes (.recipe.json) that provision a tenant on setup — enabling features, setting themes, defining content types, importing content, roles, and settings. Use when the user needs to create a setup recipe, a starter recipe, or automate tenant configuration.\n---\n\n# OrchardCore Setup Recipe Creator\n\nThis skill guides you through creating a **setup recipe** for OrchardCore. A setup recipe is a `.recipe.json` file that runs when a tenant is created, provisioning everything the site needs: enabled features, active themes, content definitions, content items, roles, and settings.\n\n## Prerequisites\n\n- OrchardCore repository (working directory)\n- A module or theme to host the recipe (recipes live in a `Recipes/` folder)\n- Basic knowledge of the features your site needs\n\n## What makes a recipe a *setup* recipe\n\nA setup recipe is a normal recipe with `\"issetuprecipe\": true` in its header. That flag makes it appear in the setup screen's recipe dropdown so a user can pick it when creating a tenant. Without it, the recipe is still executable from the admin (Configuration → Recipes) but won't show during setup.\n\n## Setup Recipe Workflow\n\n### Step 1: Choose the host and create the Recipes folder\n\nRecipes are discovered from a `Recipes/` folder inside any enabled module or theme:\n\n```bash\nmkdir src/OrchardCore.Themes/YourTheme/Recipes\n```\n\nThe file name must end with `.recipe.json`:\n\n```\nsrc/OrchardCore.Themes/YourTheme/Recipes/yoursite.recipe.json\n```\n\n### Step 2: Write the recipe header\n\n```json\n{\n  \"name\": \"YourSite\",\n  \"displayName\": \"Your Site\",\n  \"description\": \"Provisions a Your Site tenant with content and theme.\",\n  \"author\": \"The Orchard Core Team\",\n  \"website\": \"https://orchardcore.net\",\n  \"version\": \"1.0.0\",\n  \"issetuprecipe\": true,\n  \"categories\": [ \"default\" ],\n  \"tags\": [ \"yoursite\" ],\n  \"variables\": {\n    \"homeContentItemId\": \"[js:uuid()]\"\n  },\n  \"steps\": []\n}\n```\n\n`variables` are evaluated on first access and reused across steps — use them for content item ids so steps can reference the same item. Recipe JSON may contain `// comments`.\n\n### Step 3: Add the core steps in order\n\nSteps run **top to bottom**. A typical setup recipe order:\n\n1. `feature` — enable the modules and theme features the site needs.\n2. `themes` — set the active site and admin themes.\n3. `settings` — site-wide settings (home route, culture, time zone).\n4. `Roles` — roles and their permissions.\n5. `ContentDefinition` — content types and parts.\n6. `content` — the actual content items (pages, menus, widgets).\n7. `media`, `queries`, `layers`, `Templates`, `AdminMenu` — as needed.\n\nMinimum viable setup recipe:\n\n```json\n\"steps\": [\n  {\n    \"name\": \"feature\",\n    \"enable\": [\n      \"OrchardCore.Admin\",\n      \"OrchardCore.Contents\",\n      \"OrchardCore.ContentTypes\",\n      \"OrchardCore.Title\",\n      \"OrchardCore.Autoroute\",\n      \"OrchardCore.Alias\",\n      \"OrchardCore.Themes\",\n      \"OrchardCore.HomeRoute\",\n      \"TheTheme\",\n      \"YourTheme\"\n    ]\n  },\n  {\n    \"name\": \"themes\",\n    \"admin\": \"TheAdmin\",\n    \"site\": \"YourTheme\"\n  }\n]\n```\n\n!!! warning\n    Always enable your own theme's feature in the `feature` step. If you set it as the `site` theme without enabling it, the layout won't render after setup.\n\n### Step 4: Reference the full step catalog\n\nFor every built-in step (`feature`, `themes`, `settings`, `ContentDefinition`, `content`, `media`, `Roles`, `layers`, `queries`, `Templates`, `AdminMenu`, `WorkflowType`, `deployment`, `custom-settings`, `recipes`, search indexes), see `references/steps.md`.\n\n### Step 5: Use recipe helpers for dynamic values\n\nValues can embed expressions with `[helper:...]` syntax:\n\n| Helper | Example | Use |\n|--------|---------|-----|\n| `js` | `\"[js:variables('homeContentItemId')]\"` | Reference variables / JS expressions |\n| `uuid` | `\"[js:uuid()]\"` | Generate a new content item id |\n| `file` | `\"[file:text('Snippets/home.liquid')]\"` | Inline an external file's text |\n| `env` | `\"[env:MY_VAR]\"` | Read an environment variable |\n| `appsettings` | `\"[appsettings:OrchardCore:SiteName]\"` | Read appsettings config |\n| `localization` | `\"[localization:WelcomeTitle]\"` | Localized string |\n| `base64` / `gzip` | `\"[js:base64('...')]\"` | Decode embedded binary/text |\n\n### Step 6: Test the recipe\n\n```bash\n# Build the host module/theme\ndotnet build src/OrchardCore.Themes/YourTheme\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n```\n\nThen create a new tenant (or reset the default one) and pick **Your Site** in the setup screen's recipe list. Verify features, theme, and content provisioned correctly.\n\nTo re-run a non-setup recipe without a fresh tenant: Admin → Configuration → Recipes → Run.\n\n## Quick Reference\n\n### Header properties\n\n| Property | Purpose |\n|----------|---------|\n| `name` | Unique internal id (used by the `recipes` step to call it) |\n| `displayName` | Friendly name in setup/admin UI |\n| `description` | Short summary shown to the user |\n| `issetuprecipe` | `true` makes it selectable during tenant setup |\n| `categories` / `tags` | Grouping/filtering in the UI |\n| `variables` | Reusable values, evaluated once on first access |\n| `steps` | Ordered actions to execute |\n\n### Common feature ids\n\n| Area | Features |\n|------|----------|\n| Core | `OrchardCore.Admin`, `OrchardCore.HomeRoute`, `OrchardCore.Themes`, `OrchardCore.Settings`, `OrchardCore.Recipes` |\n| Content | `OrchardCore.Contents`, `OrchardCore.ContentTypes`, `OrchardCore.ContentFields`, `OrchardCore.Title`, `OrchardCore.Autoroute`, `OrchardCore.Alias`, `OrchardCore.Html`, `OrchardCore.Flows`, `OrchardCore.Lists`, `OrchardCore.Widgets` |\n| Media/Menu | `OrchardCore.Media`, `OrchardCore.Menu`, `OrchardCore.Layers` |\n| Security | `OrchardCore.Users`, `OrchardCore.Roles`, `OrchardCore.Security` |\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Recipe file | `name.recipe.json` (lowercase) | `blog.recipe.json` |\n| `name` property | PascalCase | `Blog` |\n| Location | `Recipes/` in a module or theme | `Themes/TheBlogTheme/Recipes/` |\n\n## References\n\n- `references/recipe-structure.md` - File layout, header, variables, helpers\n- `references/steps.md` - Every built-in recipe step with examples\n- `reference/modules/Recipes/README.md` (docs) - Full recipe documentation\n- Example recipes: `src/OrchardCore.Themes/*/Recipes/*.recipe.json`\n",".agents/skills/orchardcore-tenants/SKILL.md":"# orchardcore-tenants\n\nPath: `.agents/skills/orchardcore-tenants/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/SKILL.md)",".agents/skills/orchardcore-tester/SKILL.md":"# orchardcore-tester\n\nPath: `.agents/skills/orchardcore-tester/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/SKILL.md)",".agents/skills/orchardcore-theme-creator/SKILL.md":"# orchardcore-theme-creator\n\nPath: `.agents/skills/orchardcore-theme-creator/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/SKILL.md)",".agents/skills/orchardcore-unit-test/SKILL.md":"# orchardcore-unit-test\n\nPath: `.agents/skills/orchardcore-unit-test/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/SKILL.md)",".agents/skills/orchardcore-video-producer/SKILL.md":"# orchardcore-video-producer\n\nPath: `.agents/skills/orchardcore-video-producer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/SKILL.md)",".agents/skills/orchardcore-workflow-activity/SKILL.md":"# orchardcore-workflow-activity\n\nPath: `.agents/skills/orchardcore-workflow-activity/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/SKILL.md)","AGENTS.md":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/AGENTS.md)",".github/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.github/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.github/mcp.json)",".vscode/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.vscode/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.vscode/mcp.json)",".agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md)",".agents/skills/orchardcore-admin-index-views/references/worked-example.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-index-views/references/worked-example.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/references/worked-example.md)",".agents/skills/orchardcore-asset-manager/references/actions.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-asset-manager/references/actions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/references/actions.md)",".agents/skills/orchardcore-breadcrumbs/references/theming.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-breadcrumbs/references/theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/references/theming.md)",".agents/skills/orchardcore-data-migration/references/patterns.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/patterns.md)",".agents/skills/orchardcore-data-migration/references/schema-builder.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/schema-builder.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/schema-builder.md)",".agents/skills/orchardcore-display-management/references/drivers.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/drivers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/drivers.md)",".agents/skills/orchardcore-display-management/references/placement.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/placement.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/placement.md)",".agents/skills/orchardcore-docs-writer/references/docs-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-docs-writer/references/docs-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-docs-writer/references/docs-structure.md)",".agents/skills/orchardcore-localization/references/localization.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-localization/references/localization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/references/localization.md)",".agents/skills/orchardcore-module-creator/references/examples.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/examples.md)",".agents/skills/orchardcore-module-creator/references/module-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/module-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/module-structure.md)",".agents/skills/orchardcore-module-creator/references/patterns.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/patterns.md)",".agents/skills/orchardcore-query-indexing/references/indexing.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-query-indexing/references/indexing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/references/indexing.md)",".agents/skills/orchardcore-recipe-creator/references/recipe-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md)",".agents/skills/orchardcore-recipe-creator/references/steps.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/steps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/steps.md)",".agents/skills/orchardcore-tenants/references/shells.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tenants/references/shells.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/references/shells.md)",".agents/skills/orchardcore-tester/references/admin-navigation.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/admin-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/admin-navigation.md)",".agents/skills/orchardcore-tester/references/autosetup.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/autosetup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/autosetup.md)",".agents/skills/orchardcore-tester/references/common-features.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/common-features.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/common-features.md)",".agents/skills/orchardcore-tester/references/debugging.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/debugging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/debugging.md)",".agents/skills/orchardcore-tester/references/playwright-cli.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/playwright-cli.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/playwright-cli.md)",".agents/skills/orchardcore-tester/references/setup-wizard.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/setup-wizard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/setup-wizard.md)",".agents/skills/orchardcore-theme-creator/references/assets.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/assets.md)",".agents/skills/orchardcore-theme-creator/references/theme-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/theme-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/theme-structure.md)",".agents/skills/orchardcore-unit-test/references/testing.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-unit-test/references/testing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/references/testing.md)",".agents/skills/orchardcore-video-producer/references/recording.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/recording.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/recording.md)",".agents/skills/orchardcore-video-producer/references/specs.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/specs.md)",".agents/skills/orchardcore-video-producer/references/storyboard.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/storyboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/storyboard.md)",".agents/skills/orchardcore-workflow-activity/references/activities.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-workflow-activity/references/activities.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/references/activities.md)","src/docs/guides/integrate-facebook-plugins/README.md":"# integrate-facebook-plugins Documentation\n\nPath: `src/docs/guides/integrate-facebook-plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/guides/integrate-facebook-plugins/README.md)","src/docs/topics/local-nuget-packages/README.md":"# local-nuget-packages Documentation\n\nPath: `src/docs/topics/local-nuget-packages/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/topics/local-nuget-packages/README.md)",".agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json":"# Secrets-module Skill\n\nPath: `.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json)","src/docs/reference/modules/Rules/README.md":"# Rules Skill\n\nPath: `src/docs/reference/modules/Rules/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/reference/modules/Rules/README.md)"},"files":{".agents/skills/orchardcore-admin-edit-views/SKILL.md":"---\nname: orchardcore-admin-edit-views\ndescription: Creates and updates OrchardCore admin edit views using the ocat-* CSS class conventions. Use when creating or modifying Edit view files (*.Edit.cshtml, *.Fields.Edit.cshtml) to ensure correct layout and styling.\nstatus: completed\n---\n\n# OrchardCore Admin Edit Views\n\nThis skill guides you through creating and updating admin edit view files using the `ocat-*` CSS class conventions required by the OrchardCore admin theme.\n\n## The `ocat-*` CSS Class System\n\nThe `ocat-*` CSS classes provide a standardized two-column layout for admin form fields. Every form field uses a consistent structure:\n\n- **Left column** – The field label (`ocat-label`)\n- **Right column** – The input, validation message, and hint (`ocat-end`)\n\n### Core Classes\n\n| Class | Element | Purpose |\n|-------|---------|---------|\n| `ocat-wrapper` | `<div>` | Outer container for a form field row (replaces `mb-3`/`form-group`) |\n| `ocat-label` | `<label>` | Styles the field label in the left column (replaces `form-label`) |\n| `ocat-label-required` | `<label>` | Add alongside `ocat-label` to mark a field as required |\n| `ocat-end` | `<div>` | Wraps the input, validation, and hint in the right column |\n| `ocat-end-offset` | `<div>` | Right-column content with no left label (e.g., checkboxes) |\n| `ocat-limited-wrapper` | `<div>` | Outer row for limited-width controls such as short text, numbers, selects, or small settings inputs |\n| `ocat-limited` | `<div>` | Constrains the control width inside `ocat-limited-wrapper` |\n\n## Choosing the Right Wrapper\n\nUse this decision guide when creating or updating admin edit views:\n\n| Scenario | Preferred Pattern |\n|----------|-------------------|\n| Standard full-width field row | `ocat-wrapper` + `ocat-label` + `ocat-end` |\n| Standalone checkbox/toggle row with no left label | `ocat-wrapper` + `ocat-end-offset` |\n| Standalone alert, notice, legend, or section headline in the form flow | `ocat-wrapper` + `ocat-end-offset` |\n| Top-level limited-width field row (small text, number, select, token, path, port, etc.) | `ocat-limited-wrapper` + `ocat-label` + `ocat-limited` |\n| Content part / content field editor that already needs wrapper classes like `field-wrapper-*` or `content-part-wrapper-*` | Keep the outer `ocat-wrapper`, then place `ocat-limited-wrapper` inside `ocat-end` |\n\n### Wrapper Selection Rules\n\n1. Use `ocat-wrapper` for the default admin edit-view row structure.\n2. Use `ocat-limited-wrapper` when the field should stay visually narrow instead of stretching the full editor width.\n3. If the outer row also needs Orchard-specific wrapper classes such as `field-wrapper-*` or `content-part-wrapper-*`, do **not** replace that outer row with `ocat-limited-wrapper`; keep the outer `ocat-wrapper` and nest `ocat-limited-wrapper` inside `ocat-end`.\n4. Do **not** use legacy helpers or Bootstrap-only layout classes like `mb-3`, `form-group`, `form-label`, `@Orchard.GetWrapperClasses()`, or `@Orchard.GetLimitedWidthWrapperClasses()`.\n5. Alerts, notices, `<legend>`, and section headings that are part of the edit form but have no left-column label should use their own `ocat-wrapper` + `ocat-end-offset` row.\n6. A single row should use **either** `ocat-end` **or** `ocat-end-offset`, never both.\n\n### Standard Field Structure\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label Text\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Descriptive hint text.\"]</span>\n    </div>\n</div>\n```\n\n## Field Type Templates\n\n### Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Required Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label ocat-label-required\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Code/Script Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n```\n\n### Textarea\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Content\">\n    <label asp-for=\"Content\" class=\"ocat-label\">@T[\"Content\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Content\" class=\"form-control\" rows=\"5\"></textarea>\n        <span asp-validation-for=\"Content\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Select / Dropdown\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"FieldName\" class=\"form-select\">\n            <option value=\"Option1\">@T[\"Option 1\"]</option>\n            <option value=\"Option2\">@T[\"Option 2\"]</option>\n        </select>\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Checkbox (No Left Label)\n\nUse `ocat-end-offset` when there is no label in the left column (e.g., for checkboxes):\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsEnabled\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsEnabled\" />\n            <label asp-for=\"IsEnabled\" class=\"form-check-label\">@T[\"Enable feature\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to enable this feature.\"]</span>\n    </div>\n</div>\n```\n\n### Headline / Alert Row (No Left Label)\n\nUse this for standalone form guidance, warnings, legends, or section headers:\n\n```html\n<div class=\"ocat-wrapper\">\n    <div class=\"ocat-end-offset\">\n        <h5>@T[\"Section heading\"]</h5>\n        <div class=\"alert alert-warning\" role=\"alert\">\n            @T[\"Important information for editors.\"]\n        </div>\n    </div>\n</div>\n```\n\n### Input Group (with prefix/suffix)\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Url\">\n    <label asp-for=\"Url\" class=\"ocat-label\">@T[\"URL\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"input-group\">\n            <span class=\"input-group-text\">https://</span>\n            <input asp-for=\"Url\" type=\"text\" class=\"form-control\" />\n        </div>\n        <span asp-validation-for=\"Url\"></span>\n        <span class=\"hint\">@T[\"The full URL.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Field Row\n\nUse this when the whole row is a compact admin field, such as a page size, port, callback path, or select list:\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"PageSize\">\n    <label asp-for=\"PageSize\" class=\"ocat-label\">@T[\"Page size\"]</label>\n    <div class=\"ocat-limited\">\n        <input asp-for=\"PageSize\" type=\"number\" class=\"form-control\" />\n        <span asp-validation-for=\"PageSize\"></span>\n        <span class=\"hint\">@T[\"The default page size.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Select Row\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"TimeZone\">\n    <label asp-for=\"TimeZone\" class=\"ocat-label\">@T[\"Default Time Zone\"]</label>\n    <div class=\"ocat-limited\">\n        <select asp-for=\"TimeZone\" class=\"form-select\">\n            <option value=\"\">@T[\"Local to server\"]</option>\n        </select>\n        <span asp-validation-for=\"TimeZone\"></span>\n        <span class=\"hint\">@T[\"Determines the default time zone.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Control Inside a Field Wrapper\n\nUse this when the editor already needs a content field or content part wrapper on the outer row:\n\n```html\n<div class=\"@($\"ocat-wrapper field-wrapper field-wrapper-{Model.PartFieldDefinition.PartDefinition.Name.HtmlClassify()}-{Model.PartFieldDefinition.Name.HtmlClassify()}\")\">\n    <label asp-for=\"Value\" class=\"ocat-label\">@T[\"Value\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"ocat-limited-wrapper\">\n            <div class=\"ocat-limited\">\n                <input asp-for=\"Value\" class=\"form-control\" />\n                <span asp-validation-for=\"Value\"></span>\n            </div>\n        </div>\n        <span class=\"hint\">@T[\"Use a compact editor width while preserving the field wrapper row.\"]</span>\n    </div>\n</div>\n```\n\n### Conditional Fields with JavaScript/Liquid Syntax Selector\n\nFor fields that support both JavaScript and Liquid syntax, use the syntax selector pattern with toggling `d-none`:\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Syntax\">\n    <label asp-for=\"Syntax\" class=\"ocat-label\">@T[\"Syntax\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Syntax\" class=\"form-select\">\n            <option value=\"@nameof(WorkflowScriptSyntax.JavaScript)\">@T[\"JavaScript\"]</option>\n            <option value=\"@nameof(WorkflowScriptSyntax.Liquid)\">@T[\"Liquid\"]</option>\n        </select>\n        <span asp-validation-for=\"Syntax\"></span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.Expression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.JavaScript ? \"\" : \"d-none\")\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.LiquidExpression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.Liquid ? \"\" : \"d-none\")\" asp-validation-class-for=\"LiquidExpression\">\n    <label asp-for=\"LiquidExpression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"LiquidExpression\" class=\"form-control code\" />\n        <span asp-validation-for=\"LiquidExpression\"></span>\n        <span class=\"hint\">@T[\"A Liquid expression. Example: {0}\", \"{{ Workflow.Properties.Value }}\"]</span>\n    </div>\n</div>\n\n<script at=\"Foot\">\n    document.addEventListener('DOMContentLoaded', () => {\n        const syntaxElement = document.getElementById('@Html.IdFor(m => m.Syntax)');\n        const javaScriptGroup = document.getElementById('@($\"{Html.IdFor(m => m.Expression)}_group\")');\n        const liquidGroup = document.getElementById('@($\"{Html.IdFor(m => m.LiquidExpression)}_group\")');\n\n        const toggleEditors = () => {\n            const useLiquid = syntaxElement.value === '@nameof(WorkflowScriptSyntax.Liquid)';\n            javaScriptGroup.classList.toggle('d-none', useLiquid);\n            liquidGroup.classList.toggle('d-none', !useLiquid);\n        };\n\n        syntaxElement.addEventListener('change', toggleEditors);\n        toggleEditors();\n    });\n</script>\n```\n\n## Display-Only Fields (Read-Only)\n\nFor displaying information without a form input:\n\n```html\n<div class=\"ocat-wrapper\">\n    <label class=\"ocat-label\">@T[\"Field Label\"]</label>\n    <div class=\"ocat-end\">\n        <span>@Model.FieldValue</span>\n    </div>\n</div>\n```\n\n## Migration: Old to New Classes\n\nWhen updating existing views, apply these replacements:\n\n| Old Pattern | New Pattern |\n|-------------|-------------|\n| `<div class=\"mb-3\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<div class=\"form-group\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<label ... class=\"form-label\">` | `<label ... class=\"ocat-label\">` |\n| Input/select/textarea directly inside wrapper | Wrap in `<div class=\"ocat-end\">` |\n| Checkbox with label inside wrapper | Wrap in `<div class=\"ocat-end-offset\"><div class=\"form-check\">` |\n| Standalone headline or alert directly in the form flow | Wrap in `<div class=\"ocat-wrapper\"><div class=\"ocat-end-offset\">...</div></div>` |\n| Mixing `ocat-end` and `ocat-end-offset` in one row | Choose one wrapper based on whether there is a left-column label |\n| `@Orchard.GetLimitedWidthWrapperClasses()` | `ocat-limited-wrapper` |\n| `@Orchard.GetLimitedWidthClasses()` | `ocat-limited` |\n| Limited-width content field row using `field-wrapper-*` | Keep outer `ocat-wrapper field-wrapper-*`, nest `ocat-limited-wrapper` inside `ocat-end` |\n\n## Complete Edit View Example\n\n```html\n@model MyModuleViewModel\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Title\">\n    <label asp-for=\"Title\" class=\"ocat-label ocat-label-required\">@T[\"Title\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"Title\" class=\"form-control\" autofocus=\"autofocus\" />\n        <span asp-validation-for=\"Title\"></span>\n        <span class=\"hint\">@T[\"The title of the item.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Description\">\n    <label asp-for=\"Description\" class=\"ocat-label\">@T[\"Description\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Description\" class=\"form-control\" rows=\"4\"></textarea>\n        <span asp-validation-for=\"Description\"></span>\n        <span class=\"hint\">@T[\"A brief description.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Category\">\n    <label asp-for=\"Category\" class=\"ocat-label\">@T[\"Category\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Category\" class=\"form-select\">\n            <option value=\"\">@T[\"-- Select --\"]</option>\n            <option value=\"A\">@T[\"Category A\"]</option>\n            <option value=\"B\">@T[\"Category B\"]</option>\n        </select>\n        <span asp-validation-for=\"Category\"></span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsPublished\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsPublished\" />\n            <label asp-for=\"IsPublished\" class=\"form-check-label\">@T[\"Published\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to publish this item.\"]</span>\n    </div>\n</div>\n```\n\n## References\n\n- `references/ocat-classes.md` - Detailed class reference with CSS context\n- Admin theme source: `src/OrchardCore.Themes/TheAdmin/`\n- Example usage: `src/OrchardCore.Modules/OrchardCore.Workflows/Views/Items/`\n- AGENTS.md (repo root) - Coding conventions and build commands\n",".agents/skills/orchardcore-admin-index-views/SKILL.md":"---\nname: orchardcore-admin-index-views\ndescription: Builds OrchardCore admin index (list) pages with the AdminList shape and the admin-list tag helpers — view-declared columns, row display drivers placing shapes for both the SummaryAdmin (List) and SummaryAdminGrid (Grid) display types, column providers, row actions, toolbar, search, pager and per-list alternates. Use for every new admin page that lists items, when converting a hand-written admin listing, adding or reordering columns of an admin list, extending another module's list, or changing how its rows and actions render.\n---\n\n# OrchardCore Admin Index Views\n\nEvery admin page that lists items (an \"index\" page: content items, users, queries, workflows, ...) renders its\nitems through the shared `AdminList` shape. Never hand-write a `<ul class=\"list-group\">`, a `<table>`, a toolbar,\na select-all checkbox, an item count, an empty state or a pager for an admin listing: the list renders all of\nthem, in the `List` or `Grid` layout the site and the user choose.\n\n## Ownership\n\n| Layer | Responsibility |\n| --- | --- |\n| Controller | Query the raw items and supply an `AdminListContext<TModel>` with the typed `Source`, pager, search, toolbar and other region shapes. Never build the list shape or the row shapes. |\n| View | Declare the list name, the grid columns, the CSS classes, the empty message and other presentation properties with `<admin-list>`, inside the page's form. |\n| Row display driver | Return ordinary `IDisplayResult` shapes, each with a `SummaryAdmin` location (List) and a `SummaryAdminGrid` location (Grid). Set optional row classes/attributes on `context.Shape`. |\n| Column provider | Optional. Extend the columns a view declares, e.g. from another module. |\n| `AdminList` | Resolve the layout at display time, build each row once with the display type of that layout, and render the whole listing. |\n\n`AdminListLayout` has exactly two values: `List` renders each `SummaryAdmin` row shape whole (its row template\ndecides the look); `Grid` renders the `SummaryAdminGrid` row zones directly, one cell per column, each cell\nrendering the zone named after its column. Layouts are not discovered or registered through templates.\nDo not add column or cell drivers, column metadata classes, or build both representations of a row.\n\n## Decide\n\n| Situation | Do this |\n| --- | --- |\n| New admin page listing items | Follow the workflow below. |\n| Rows already built with `BuildDisplayAsync(..., \"SummaryAdmin\")` and a hand-written list | Convert: move the row building out of the controller into `AdminListContext<TModel>.Source`, add the `SummaryAdminGrid` locations, then follow steps 3 to 6. |\n| Rows are hard-coded `<li>` markup in Razor | Write a display driver for the row model first, so a row is a zone-holding shape, then follow the workflow. |\n| Adding a column to another module's list | Step 5, plus a `SummaryAdminGrid` location in your driver. |\n| The page edits a single entity | Not this skill: use `orchardcore-admin-edit-views`. |\n| The page arranges items rather than listing them (a tree, a canvas, a media library, widgets by zone) | Not this skill: such pages keep their own presentation. |\n\n## Workflow\n\n### 1. Write the row display driver\n\nA row is the shape `IDisplayManager<TModel>` builds for the item. Its type is `typeof(TModel).Name` plus the\ndisplay type, e.g. `IndexProfile_SummaryAdmin`, so register the driver against a domain model named exactly what\nthe shape should be called, never a `*ViewModel`. Register it in the `Startup` of the feature that owns the list:\na driver registered by a neighbouring feature leaves every row empty on a site where that feature is off.\n\nGive every shape a location for each display type. The shape types and templates are the same; only the zone\ndiffers, a row zone for `List` and the zone of a column for `Grid`:\n\n```csharp\npublic override Task<IDisplayResult> DisplayAsync(IndexProfile profile, BuildDisplayContext context)\n{\n    // The row's classes and attributes are rendered on its row element, in both layouts.\n    if (context.DisplayType is OrchardCoreConstants.DisplayType.SummaryAdmin or OrchardCoreConstants.DisplayType.SummaryAdminGrid\n        && context.Shape is Shape rowShape)\n    {\n        rowShape.Classes.Add(\"item\");\n        rowShape.Attributes[\"data-filter-value\"] = profile.Name?.ToLowerInvariant() ?? string.Empty;\n    }\n\n    return CombineAsync(\n        View(\"IndexProfile_Checkbox_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Checkbox:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Select:1\"),\n        View(\"IndexProfile_Fields_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Content:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Name:1\"),\n        View(\"IndexProfile_DefaultTags_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Tags:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Source:5\"),\n        View(\"IndexProfile_Buttons_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Actions:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Actions:5\"),\n        View(\"IndexProfile_ActionsMenuItems_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"ActionsMenu:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"ActionsMenu:5\"));\n}\n```\n\n- Every visible piece of a row (checkbox, title, badges, dates, buttons, menu items) is its own shape in a zone.\n  Anything hard-coded in the row template is missing from the grid.\n- A location without a display type, `.Location(\"Tags:5\")`, applies to both display types, so in the grid the\n  shape lands in a `Tags` zone no column renders. Always pair a `SummaryAdmin` location with a `SummaryAdminGrid`\n  one, unless the zone name is also a column name (e.g. `Actions`).\n- Display types do not inherit placement: a shape placed only for `SummaryAdmin`, by a driver or by\n  `placement.json`, does not appear in the grid. A grid-only shape declares only a `SummaryAdminGrid` location.\n- `placement.json` rules filtered by `\"displayType\": \"SummaryAdminGrid\"` move a shape to another column zone\n  (`Source:10`), hide it (`-`), change its template or add wrappers and alternates.\n- Do not use editor `|Column` groupings for cells.\n\n### 2. Write the row template for the List layout\n\n`{Model}.SummaryAdmin.cshtml` renders the zones of the row; the grid does not use it. Every shipped row has the\nsame structure, so the lists look alike:\n\n```cshtml\n<div class=\"row g-0 align-items-center\">\n    <div class=\"col\">\n        <div class=\"title d-flex align-items-center\">\n            @* Checkbox, then <div class=\"summary d-flex flex-column flex-md-row list-item-search-text\"> with Content, Tags, Meta *@\n        </div>\n        @if (Model.Description != null)\n        {\n            @await DisplayAsync(Model.Description)\n        }\n    </div>\n    <div class=\"col-auto d-flex justify-content-end ps-2\">\n        @await DisplayAsync(await Factory.CreateAdminListActionsAsync((IShape)Model))\n    </div>\n</div>\n```\n\n| Zone | Renders as | Never |\n| --- | --- | --- |\n| `Content` | The title, a plain link to the edit page | A heading (`<h5>`) |\n| `Tags`, `Meta` | `<span class=\"badge ta-badge fw-normal\">` per fact, leading icon, `title` tooltip | Plain text or a `<span class=\"hint\">` |\n| `Description` | `<div class=\"admin-list-secondary\">`, its own line under the title | A second full-width row running under the actions |\n| `Actions`, `ActionsMenu` | Through `AdminListActions` (`CreateAdminListActionsAsync`), so the Buttons/Menu setting applies | A hand-written button group, or `ActionsMenu` items wrapped in `<li>` |\n\n### 3. Supply the items from the controller\n\n```csharp\nmodel.List = new AdminListContext<IndexProfile>\n{\n    Source = items,\n    Pager = pagerShape,\n};\n```\n\nThe view model exposes `public AdminListContext List { get; set; }`. `AdminListContext<TModel>` sets `ItemType`\nand exposes a typed `IEnumerable<TModel> Source`; its generic argument can select a registered base/interface\ndisplay manager, even for an empty collection of derived items. Items must be non-null instances of that type.\nThe list invokes the display manager after resolving its layout, so the controller has no row-rendering\ndependencies and never builds rows, row callbacks or prebuilt shapes.\n\nThe default `IAdminListItemDisplayManager<TModel>` adapter calls `IDisplayManager<TModel>`. Register a closed\nadapter for a specialized pipeline, e.g. content items use `IContentItemDisplayManager`.\n\nOther context properties, all optional:\n\n- `Header`: an options editor shape (`Summary` and `Actions` zones), as the content items and users pass.\n- `Toolbar`: a toolbar shape of the page's own. Otherwise the list builds `AdminListToolbar` from the rows, the\n  pager, `BulkActions`, `ShowSelectAll` and `ToolbarActions` (a shape at its end, e.g. filters).\n  `ShowToolbar = false` omits it.\n- `Search`, `Actions`: the search bar (e.g. the `AdminListSearch` shape) and the page buttons, in the action bar.\n- `PageSize`: a separate page size selector; set `ShowPageSizeSelector = false` on the pager when passing it.\n- `Data`: what a column provider may need, e.g. the content types the listing is filtered by.\n- `Layout`: forces a layout, only when the page can be rendered one way, e.g. while its rows are dragged.\n- `ShowLayoutSelector = false`: for a page that renders several lists or places one selector itself, by creating\n  an `AdminListLayoutSelector` shape with the shared `ListName`.\n\n### 4. Render the list in the view\n\nImport the tag helpers once per module, in `Views/_ViewImports.cshtml`:\n\n```cshtml\n@using OrchardCore.Admin\n@addTagHelper *, OrchardCore.Admin.Abstractions\n```\n\nThe view keeps the form, its hidden inputs and the action bar, and renders the list with `<admin-list>`:\n\n```cshtml\n<form asp-action=\"Index\" method=\"post\" class=\"no-multisubmit bulk-select-list\"\n      data-checkbox-name=\"itemIds\" data-bulk-action-name=\"Options.BulkAction\" data-selected-text=\"@T[\"selected\"]\">\n    <input type=\"submit\" name=\"submit.Filter\" id=\"submitFilter\" class=\"visually-hidden\" />\n    <input asp-for=\"Options.BulkAction\" type=\"hidden\" />\n    <input type=\"submit\" name=\"submit.BulkAction\" class=\"visually-hidden\" />\n\n    <admin-list name=\"Indexes\" context=\"Model.List\"\n                bulk-actions=\"Model.Options.BulkActions\"\n                empty-message=\"@T[\"<strong>Nothing here!</strong> There are no indexes at the moment.\"]\">\n        <admin-list-list item-css-class=\"list-group-item\" />\n        <admin-list-grid>\n            <admin-list-column type=\"Select\" />\n            <admin-list-column name=\"Name\" title=\"@T[\"Name\"]\" position=\"20\" />\n            <admin-list-column name=\"Source\" title=\"@T[\"Source\"]\" position=\"30\" width=\"auto\" />\n            <admin-list-column type=\"Actions\" title=\"@T[\"Actions\"]\" />\n        </admin-list-grid>\n    </admin-list>\n</form>\n```\n\n- `name` and `context` are both required. The name identifies the list for column providers, the user's layout\n  choice and the alternates. Use a string literal when only the view uses it; keep a module constant when it is\n  reused, e.g. `ContentsAdminList.Name`, and do not create a holder class for a single use.\n- The tag also takes `show-toolbar`, `show-select-all`, `show-layout-selector`, `layout`, `item-css-class`,\n  `empty-message`, `bulk-actions`, and `rows-*` attributes (or `rows-attributes`) for the rows container, e.g.\n  `rows-id` for a sortable list.\n- `admin-list-list` and `admin-list-grid` configure their layout's `rows-class` and `item-css-class` without\n  forcing it. Each can be declared once. List item classes replace the default; grid item classes are added to\n  the required grid row class. Children emit no HTML.\n- The hidden `submit.Filter` button stays first in the form: Enter in the search box triggers it. The search is an\n  input group ending with a magnifying-glass button, never an icon inside the input.\n- Use the shared `bulk-select-list` and `list-search-filter` components, wired by one small module per page\n  (`Assets/ts/*.ts`), and keep the `select-all`, `items`, `selected-items` and `actions` ids the toolbar renders.\n\n### 5. Declare the grid columns\n\nEach `<admin-list-column>` renders the row zone named after it; `zones=\"A B\"` renders several named zones instead.\nA zone no column renders is omitted from the grid.\n\n| Attribute | Meaning |\n| --- | --- |\n| `type` | `Select` (the `Select` zone, at position `10`) or `Actions` (the `Actions` and `ActionsMenu` zones, at position `end`); omit for a regular column. A preset keeps its defaults unless overridden. |\n| `name` | Required for a regular column, unique ignoring case; also the default zone and the `admin-list-column-{name}` CSS class. |\n| `title` | Localized header text, e.g. `@T[\"Name\"]`; it also labels the cells when a narrow list stacks them. |\n| `position` | Placement syntax (`10`, `25`, `end`); columns are sorted by it. |\n| `width` | Any CSS width, or `auto` to hug the content. No width shares the remaining space. |\n| `alignment`, `no-wrap`, `css-class` | Cell presentation. |\n\nTo change the columns of another module's list, implement `IAdminListColumnsProvider`:\n\n```csharp\npublic sealed class CultureColumnsProvider : IAdminListColumnsProvider\n{\n    private readonly IStringLocalizer S;\n\n    public CultureColumnsProvider(IStringLocalizer<CultureColumnsProvider> stringLocalizer)\n    {\n        S = stringLocalizer;\n    }\n\n    public Task PopulateAsync(AdminListColumnsContext context)\n    {\n        // Between \"Title\" (20) and \"Type\" (30), whatever order the providers run in.\n        context.Columns.Add(new AdminListColumn { Name = \"Culture\", Position = \"25\", Title = S[\"Culture\"], Width = AdminListColumn.AutoWidth });\n\n        return Task.CompletedTask;\n    }\n}\n```\n\n```csharp\nservices.AddAdminListColumnsProvider<CultureColumnsProvider>(ContentsAdminList.Name);\n```\n\n- Registered with a list name, a provider is only created and run for that list (keyed DI with an\n  `AdminListColumnsProviderKey`; names are case-sensitive). Several providers can target a list, and a provider\n  can target several lists. Duplicate registrations are ignored.\n- Without a name, `AddAdminListColumnsProvider<TProvider>()` registers a global provider for every list. Global\n  providers run first, then the providers of the list, each group in feature dependency order. A global provider\n  must tolerate different column sets: check `context.Find(name)` before changing an optional column.\n- `context.Columns.Add(...)`, `context.Find(name)` and `context.Remove(name)` change the columns.\n  `context.Data`, `TryGetData<T>()` and `GetData<T>()` read what the page passed, e.g.\n  `ContentsAdminList.ContentTypesKey` and `ContentsAdminList.StereotypesKey`.\n- A provider never builds cells: the content of its column comes from a display driver placing a shape in the\n  zone of that column for `SummaryAdminGrid`.\n\n### 6. Test and document it\n\n- Add the page's URL to `_lists` in `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs` when the\n  Blog recipe enables its feature (add an optional-list test otherwise). The test renders every list in the grid\n  and fails when a column renders nothing on any row, which catches a shape missing its `SummaryAdminGrid` location.\n- Check the page in both layouts and both actions layouts (Buttons and Menu), with rows and without, and in a\n  narrow window, where the grid stacks its cells.\n- Add the list to the table of lists in `src/docs/reference/modules/Admin/README.md`.\n\n## Regions and properties of the shape\n\nRows and regions inherit the list name and layout automatically, so their alternates work without a page passing\nthem. `Rows` is output built during display, never an input; `DisplayType` is the resolved row display type.\nCustom `AdminListCell` templates receive `Row` and `Column`. The Actions cell renders the row's `Actions` and\n`ActionsMenu` zones through `AdminListActions`; moving an action to another zone removes it from that cell.\n\n## Alternates\n\n| Shape | Alternates, least to most specific |\n| --- | --- |\n| `AdminList` | `AdminList__{Layout}`, `AdminList__{Name}`, `AdminList__{Name}__{Layout}` |\n| `AdminListCell` | `AdminListCell__{Column}`, `AdminListCell__{Name}__{Column}` |\n| `AdminListActions` | `AdminListActions__{Layout}`, `AdminListActions__{Name}`, `AdminListActions__{Name}__{Layout}`, where `{Layout}` is `Buttons` or `Menu` |\n| `AdminListToolbar`, `AdminListSearch` | `__{Layout}`, `__{Name}`, `__{Name}__{Layout}` |\n\n`AdminListActionsLayout` has exactly two values, `Buttons` and `Menu`; configuration and the nullable site\nsettings use the enum. Keep templates and alternates overridable, but do not add option shapes or custom modes.\n\n## Gotchas\n\n1. **Never name the rows `Items`**: `Shape` already exposes `Items`, its child shapes.\n2. **A location without a display type does not reach the grid column** (step 1). An empty grid column is the symptom.\n3. **Per-type row templates only apply to the List layout**: `Content-BlogPost.SummaryAdmin.cshtml` does not\n   affect the grid; customise it with `SummaryAdminGrid` placement or `AdminListCell-{List}-{Column}.cshtml`.\n4. **Never wrap `ActionsMenu` items in `<li>`**: the dropdown sits inside the `<li>` of a List row, and the parser\n   hoists a nested `<li>` out of it, so the menu renders empty and stray links appear under the list.\n5. **No viewport-based classes such as `d-md-none` in list Razor**: the grid stacks with a container query on the\n   list itself (`_admin-list.scss`), because the sidebar makes the viewport a poor proxy for the list width.\n6. **A sortable list drags the rows, never their container**: in the grid the rows container is\n   `display: contents`. Mark it with `rows-*` attributes, and send SortableJS `oldDraggableIndex`/`newDraggableIndex`,\n   which count only the rows.\n7. **Rows are built while the view renders**, from the `AdminList` displaying event: a driver's queries and\n   errors happen during rendering, not in the controller.\n\n## References\n\n- `references/worked-example.md`: the Indexes page, driver, controller and view.\n- `src/docs/reference/modules/Admin/README.md`: the user and developer documentation of admin lists.\n- `src/OrchardCore.Modules/OrchardCore.Admin/Views/AdminList*.cshtml`: the layout, cell, actions and toolbar templates.\n- `src/OrchardCore.Themes/TheAdmin/Assets/scss/components/_admin-list.scss`: the layout CSS and its container queries.\n- `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs`: renders every list in the grid.\n",".agents/skills/orchardcore-data-migration/SKILL.md":"---\nname: orchardcore-data-migration\ndescription: Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema.\n---\n\n# OrchardCore Data Migration\n\nThis skill guides you through writing OrchardCore data migrations following project conventions.\n\nA data migration is a class that inherits `DataMigration`. OrchardCore discovers its methods by reflection and runs them **sequentially**, per tenant, on application start. The chain starts at `CreateAsync()` and continues through `UpdateFrom1Async()`, `UpdateFrom2Async()`, … Each method returns the schema version number the **next** method must match.\n\n## When to use a migration\n\n- Define or alter content types / parts / fields (`IContentDefinitionManager`).\n- Create or alter SQL index tables (`SchemaBuilder`).\n- Patch existing content item data (query + `ISession.SaveAsync`).\n- Run a setup recipe at install/upgrade (`IRecipeMigrator`).\n- Seed roles, settings, or other documents.\n\n## The version-chain rule (read first)\n\nThe migration runner stores the **last returned number** per migration class per tenant. It then calls the method whose name matches: returned `4` → runs `UpdateFrom4Async()`. The method's return value is the new stored version.\n\n| Rule | Why |\n|------|-----|\n| `CreateAsync` runs only on a **fresh** install. | Existing tenants already passed it. |\n| `CreateAsync` returns the **current latest** version, skipping all `UpdateFromX`. | New sites get the final schema directly. |\n| `UpdateFromXAsync` runs only when stored version == `X`. | Sequential upgrade path. |\n| **Never renumber** an already-shipped method. | Other tenants store the old number; renumbering breaks their upgrade. |\n| Each `UpdateFromX` returns `X+1` (usually). | Skipping is allowed when a later step subsumes an earlier one (return a higher number to jump). |\n\n## Migration Workflow\n\n### Step 1: Create the Migrations class\n\nPlace `Migrations.cs` in the module root (or a `Migrations/` subfolder for multiple classes, e.g. `Migrations/PermissionMigrations.cs`).\n\n```csharp\nusing OrchardCore.ContentManagement.Metadata;\nusing OrchardCore.Data.Migration;\n\nnamespace YourModule;\n\npublic sealed class Migrations : DataMigration\n{\n    private readonly IContentDefinitionManager _contentDefinitionManager;\n\n    public Migrations(IContentDefinitionManager contentDefinitionManager)\n    {\n        _contentDefinitionManager = contentDefinitionManager;\n    }\n\n    public async Task<int> CreateAsync()\n    {\n        // ... define schema ...\n\n        // Return the latest version so new installs skip every UpdateFromX.\n        return 1;\n    }\n}\n```\n\n`SchemaBuilder` is injected by the base class — do **not** add it as a constructor parameter.\n\n### Step 2: Register the migration\n\nIn the module `Startup.cs`:\n\n```csharp\nservices.AddDataMigration<Migrations>();\n```\n\nRegister each migration class separately if the module has more than one.\n\n### Step 3: Write the initial `CreateAsync`\n\nReturn the highest version your module currently ships. Example (content part):\n\n```csharp\npublic async Task<int> CreateAsync()\n{\n    await _contentDefinitionManager.AlterPartDefinitionAsync(\"TitlePart\", builder => builder\n        .Attachable()\n        .WithDescription(\"Provides a Title for your content item.\"));\n\n    // Shortcut other migration steps on new content definition schemas.\n    return 2;\n}\n```\n\n### Step 4: Add `UpdateFromX` for each shipped change\n\nWhen you change the schema in a release, **append** a new `UpdateFromXAsync` — never edit a prior one. Bump the number `CreateAsync` returns to match.\n\n```csharp\n// This code can be removed in a later version.\npublic async Task<int> UpdateFrom1Async()\n{\n    await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table\n        .AddColumn<string>(\"BigUrl\", column => column.Nullable().Unlimited()));\n\n    return 2;\n}\n```\n\n### Step 5: Test the migration\n\n1. **Fresh install** — run on a clean tenant; only `CreateAsync` executes, schema lands at the latest version.\n2. **Upgrade** — start from a DB at the previous version; confirm each `UpdateFromX` runs in order.\n3. Test on every targeted provider (SQLite, SQL Server, MySQL, PostgreSQL). SQLite cannot drop columns — guard with `try/catch` (see `references/schema-builder.md`).\n\n## Method signatures\n\n| Method | Forms | Runs when |\n|--------|-------|-----------|\n| Create | `int Create()` · `Task<int> CreateAsync()` | fresh install |\n| Update | `int UpdateFromX()` · `Task<int> UpdateFromXAsync()` | stored version == X |\n| Uninstall | `void Uninstall()` · `Task UninstallAsync()` | feature uninstalled |\n\nStatic or instance, sync or async — all supported. Async (`...Async`) is the convention for new code.\n\n## Quick Reference\n\n### Common operations\n\n| Goal | API |\n|------|-----|\n| Define/alter content type | `_contentDefinitionManager.AlterTypeDefinitionAsync(...)` |\n| Define/alter part | `_contentDefinitionManager.AlterPartDefinitionAsync(...)` |\n| Remove part / type | `RemovePart(...)`, `DeletePartDefinitionAsync(...)` |\n| Create SQL index table | `SchemaBuilder.CreateMapIndexTableAsync<TIndex>(...)` |\n| Add/drop column or index | `SchemaBuilder.AlterIndexTableAsync<TIndex>(...)` |\n| Run a recipe | `_recipeMigrator.ExecuteAsync(\"init.recipe.json\", this)` |\n| Patch content items | `ISession.Query<ContentItem, ContentItemIndex>(...)` + `SaveAsync` |\n\n### Type definition builder methods\n\n| Method | Effect |\n|--------|--------|\n| `.Creatable()` | Appears in the New menu |\n| `.Listable()` | Shows in content list |\n| `.Draftable()` | Supports drafts |\n| `.Versionable()` | Keeps version history |\n| `.Securable()` | Per-type permissions |\n| `.WithPart(\"PartName\")` | Attaches a part |\n\n## Gotchas\n\n- Always set column **length** on string index columns (`.WithLength(n)`) or `.Unlimited()` — unlengthed columns fail on some providers.\n- `CreateAsync` should return the latest version, not `1`, once `UpdateFromX` methods exist.\n- Patch loops must page by `DocumentId` and call `FlushAsync()` periodically (see `references/patterns.md`).\n- Mark obsolete `UpdateFromX` methods with `// This code can be removed in a later version.` but keep them until you drop support for that upgrade path.\n\n## References\n\n- `references/patterns.md` — content definition changes, content-item data patching, recipe migrations\n- `references/schema-builder.md` — index tables, columns, indexes, provider quirks\n- `src/docs/reference/modules/Migrations/README.md` (repo) — official reference\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-display-management/SKILL.md":"---\nname: orchardcore-display-management\ndescription: Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or write a display driver.\n---\n\n# OrchardCore Display Management\n\nThis skill guides you through OrchardCore's display system following project conventions.\n\nOrchardCore renders content by building **shapes** (dynamic view models) from drivers, then placing each shape into a **zone** of the layout. **Drivers** decide *what* shapes exist and their default location; **`placement.json`** overrides *where* they go, *whether* they show, and *which* template renders them.\n\n## Mental model\n\n```\nContentItem ──drivers──▶ shapes ──placement.json──▶ zones ──templates──▶ HTML\n```\n\n- **Shape** — a dynamic object with a `ShapeType` and `Metadata`. The shape type (e.g. `TextField`, `Parts_Contents_Publish`, `ContentPart`) selects the template and the placement rule.\n- **Driver** — `DisplayDriver` / `ContentPartDisplayDriver` / `ContentFieldDisplayDriver`. Returns `IDisplayResult`s from `Display`, `Edit`, `UpdateAsync`, each with a default `.Location(...)`.\n- **Placement** — `placement.json` at the **root** of a module or theme. Filters shapes and rewrites their location/visibility/template.\n- **Zone** — named region of the layout (`Content`, `Header`, `Footer`, `Actions`, …) or an editor group (tab/card/column).\n\n## Decide: driver vs placement\n\n| Want to… | Use |\n|----------|-----|\n| Reposition / hide a shape someone else built | `placement.json` |\n| Move a shape to a layout zone | `placement.json` (`place` starts with `/`) | \n| Swap the template or add alternates | `placement.json` (`shape` / `alternates`) |\n| Lay out the admin editor (tabs/cards/columns) | `placement.json` groupings |\n| Produce a *new* shape from your part/field | display driver |\n| Read/write the editor model | display driver (`Edit` / `UpdateAsync`) |\n\nPrefer `placement.json` for layout — no rebuild needed, theme can override.\n\n## Workflow A: place a shape\n\n### Step 1: Find the shape type and differentiator\n\nThe placement property name is the **shape type**, not the part name. A part **without** its own driver renders as `ContentPart` (differentiator = part name). A field renders as its field type (`TextField`) with differentiator `{PartName}-{FieldName}`.\n\nSee `references/placement.md` for the full differentiator table.\n\n### Step 2: Add a rule to `placement.json`\n\n```json\n{\n  \"TextField\": [\n    {\n      \"differentiator\": \"Article-Subtitle\",\n      \"displayType\": \"Detail\",\n      \"place\": \"Content:2\"\n    }\n  ]\n}\n```\n\n### Step 3: Verify\n\nRun the site, view the content item. Use `?display` debug shapes or the template's shape tracing to confirm the shape type. Wrong type → no rule matches.\n\n## Workflow B: write a display driver\n\n### Step 1: Inherit the right base\n\n| Base class | For |\n|------------|-----|\n| `ContentPartDisplayDriver<TPart>` | a content part |\n| `ContentFieldDisplayDriver<TField>` | a content field |\n| `SectionDisplayDriver<TModel, TSection>` | settings sections |\n| `DisplayDriver<TModel>` | anything else |\n\n### Step 2: Implement Display / Edit / Update\n\n```csharp\npublic sealed class TextFieldDisplayDriver : ContentFieldDisplayDriver<TextField>\n{\n    public override IDisplayResult Display(TextField field, BuildFieldDisplayContext context)\n    {\n        return Initialize<DisplayTextFieldViewModel>(GetDisplayShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        })\n        .Location(OrchardCoreConstants.DisplayType.Detail, \"Content\")\n        .Location(OrchardCoreConstants.DisplayType.Summary, \"Content\");\n    }\n\n    public override IDisplayResult Edit(TextField field, BuildFieldEditorContext context)\n    {\n        return Initialize<EditTextFieldViewModel>(GetEditorShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        });\n    }\n\n    public override async Task<IDisplayResult> UpdateAsync(TextField field, UpdateFieldEditorContext context)\n    {\n        await context.Updater.TryUpdateModelAsync(field, Prefix, f => f.Text);\n        return Edit(field, context);\n    }\n}\n```\n\n### Step 3: Register the driver\n\nIn `Startup.cs`:\n\n```csharp\nservices.AddContentPartDisplayDriver<MyPart, MyPartDisplayDriver>();\n// or for a field:\nservices.AddContentFieldDisplayDriver<TextField, TextFieldDisplayDriver>();\n```\n\nSee `references/drivers.md` for shape-type helpers, the fluent `.Location` API, and registration variants.\n\n## Quick Reference\n\n### Placement rule keys\n\n| Key | Purpose |\n|-----|---------|\n| `place` | `Zone:Position`. `-` hides, `/Zone` targets a layout zone |\n| `displayType` | Match `Detail`, `Summary`, `SummaryAdmin`, `SummaryAdminGrid`, `DetailAdmin`, `Edit` |\n| `differentiator` | Distinguish reused shapes (field/part name) |\n| `contentType` | Match content type / stereotype (`Art*` wildcard, or array) |\n| `contentPart` | Match content items containing a part |\n| `path` | Match request path |\n| `alternates` | Add alternate shape types |\n| `wrappers` | Wrap the shape in other shapes |\n| `shape` | Substitute a different shape type |\n\n### Position values\n\n`before` < `0`/empty < `1` < `1.1` < `2` < `10` < `after`. Numeric, not alphabetic. Dot notation inserts between (`5.1` is between 5 and 6).\n\n### Editor grouping modifiers\n\n| Modifier | Group | Example |\n|----------|-------|---------|\n| `#` | Tab | `Parts:0#Media;1` |\n| `%` | Card | `Parts:0%Details;2` |\n| `\\|` | Column | `Parts:0\\|Left_9;1` (`_9` = width, `;1` = order) |\n\n`;N` after a group name = group order. `_N` after a column name = Bootstrap grid width (12-col).\n\n### Common zones\n\n| Zone | Purpose |\n|------|---------|\n| `Content` | Main content shapes |\n| `Header` / `Footer` | Layout regions |\n| `Actions` | Admin list row buttons (`SummaryAdmin` and `SummaryAdminGrid`) |\n| `Parts` | Editor parts container |\n| `Meta` | Metadata block |\n\n## Gotchas\n\n- Hiding a whole editor row → target `ContentPart_Edit` with differentiator `{ContentType}-{PartName}`, not the inner `XxxPart_Edit`.\n- Shape type ≠ part type. No driver → shape is `ContentPart`. Summary display of a dynamic part → `ContentPart_Summary`.\n- Field display modes are stricter: shape type needs `_Display`, differentiator is `{Part}-{Field}-{FieldType}_Display__{Mode}`.\n- Placement precedence: startup project → active theme → modules (by dependency). A theme can override any module's placement.\n- Admin list rows use `SummaryAdmin` in the List layout and `SummaryAdminGrid` in the Grid layout, where each zone is a column. Display types do not inherit placement, so a rule or a driver location for `SummaryAdmin` does not reach the grid; see `orchardcore-admin-index-views`.\n- Same position value → registration order decides; use distinct positions when order matters.\n\n## References\n\n- `references/placement.md` — placement.json filters, differentiators, layout zones, editor groupings\n- `references/drivers.md` — driver base classes, shape helpers, fluent Location API, registration\n- `src/docs/reference/modules/Placement/README.md` (repo) — official placement reference\n- `src/docs/reference/modules/DisplayManagement/README.md` (repo) — display management reference\n- `src/docs/reference/modules/Templates/README.md` (repo) — shape differentiators and alternates\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-breadcrumbs/SKILL.md":"---\nname: orchardcore-breadcrumbs\ndescription: Authors explicit breadcrumb titles, inline ancestors, and lightweight IBreadcrumbProvider postprocessors on OrchardCore admin screens and the front end. Use when declaring current-page titles, extending ancestors, resolving a parent entity, documenting hidden-trail processing, or theming breadcrumbs through name-based shape alternates.\n---\n\n# OrchardCore Breadcrumbs\n\nA breadcrumb is a **named trail with an explicit current-page title**. The parent `<breadcrumb>` requires an `IHtmlContent` title and normalizes it to text; every `<breadcrumb-item>` child is an ancestor. For visible trails, the view supplies dynamic ancestor links and localized text, and registered `IBreadcrumbProvider` implementations can postprocess only those ancestors. The helper preserves their final list order and appends the normalized title as the final, unlinked current node. Hidden admin trails use only the explicit title and skip children and providers entirely. On the admin, the trail appears above a heading containing that text; on the front end, no heading is rendered by default.\n\nKeep built-in views inline, with the current page in `title` rather than a child. Use lightweight providers for shared or cross-module ancestor changes, not a separate manager, builder, named-provider base class, or contextual-data contract. Customize the owning view or its partials for the title and screen-specific ancestors, and use shape alternates for presentation. `INavigationProvider` remains the separate extension point for ordinary navigation menus.\n\n**Keep all Show breadcrumb logic in the tag helper.** Views and partials must not read `AdminSettings.ShowBreadcrumb` or gate lookups, child declarations, or breadcrumb markup on it. Declare nodes and resolve needed data normally; the helper alone chooses visibility and the provider/title pipeline.\n\n## Mental model\n\n1. Require a stable `name` and a non-null `Microsoft.AspNetCore.Html.IHtmlContent` title, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text but still leaves a current node in a visible trail.\n2. Check visibility before any child or provider work. A hidden admin trail never calls `GetChildContentAsync`, resolves or enumerates providers, constructs or invokes them, or creates a `BreadcrumbContext` or any breadcrumb items, including the current node.\n3. For a hidden trail, only the explicit title matters. When heading or browser-title output is needed, call `WriteTo(writer, HtmlEncoder.Default)` once and HTML-decode once, then safely encode the text for output. With `heading=\"\"` and `page-title=\"false\"`, validate the non-null title but also skip `WriteTo`.\n4. For a visible trail, normalize the title, collect ancestor children, and await registered providers sequentially in DI registration order on the shared mutable ancestor list. Helper-created provider contexts always have `ShowTrail == true`.\n5. Preserve the providers' final ancestor list order without sorting, then unconditionally append the explicit title node with fixed `Id = \"Title\"`, last/current and never linked. Clearing or reordering ancestors cannot remove or displace it.\n6. Visible trails finalize ancestor links and render shapes. Hidden trails render only the requested explicit heading/browser title, with no URL generation, permission lookup, link authorization, or shapes. The admin setting does not affect front-end trails.\n\n## Naming\n\n- Use stable literal strings directly in `.cshtml`: `name=\"ContentsEdit\"` and ancestor `id=\"Contents\"`.\n- Use `name` for provider targeting, the trail's CSS class, shape alternates, and name-based deduplication, including Dashboard. The current node automatically has `Id = \"Title\"`; its name-specific alternate needs no separate attribute.\n- Reuse a screen's admin list name when it has one. The content items list uses `Contents`.\n- Prefer PascalCase names without separators so shape alternate file names remain readable.\n- Do not introduce breadcrumb constants classes, contextual-data keys, or a `data` attribute.\n- Ancestor IDs and the automatic current-node ID `Title` identify node shape alternates; they are not rendered as HTML `id` attributes.\n\n## Workflow A: add a breadcrumb to a view\n\n### Step 1: Import the tag helpers\n\nReference `OrchardCore.Navigation.Core` and add this to the module's `Views/_ViewImports.cshtml` if it is not already imported:\n\n```razor\n@addTagHelper *, OrchardCore.Navigation.Core\n```\n\nThe always-enabled `OrchardCore.Navigation` module supplies the shapes. Inline authoring needs no custom provider registration.\n\n### Step 2: Declare the explicit title and ancestor children\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nPrefer paired parent tags with inline ancestors. The parent supplies the current page through `title`; the helper appends it with ID `Title`. Do not add a duplicate current-page child.\n\nPass the normal Razor localizer result directly, as with `RenderTitleSegments`: `title=\"@T[\"Edit Record\"]\"` or `title=\"@T[\"Edit {0}\", record.Name]\"`. The helper renders the HTML-aware value when needed, including its formatting arguments. Do not extract `.Value` from `T[...]` or add caller conversions. Keep `@T[\"...\"]` for ancestor child text too.\n\nPlain strings require the existing safe wrapper `new OrchardCore.DisplayManagement.Html.HtmlContentString(value ?? string.Empty)`. For `Microsoft.Extensions.Localization.LocalizedString` and `OrchardCore.Localization.Data.DataLocalizedString` from `IDataLocalizer`, wrap `.Value ?? string.Empty`. Do not pass these non-HTML values directly, extract `T[...].Value`, or use `Html.Raw`: `HtmlContentString` encodes untrusted strings when written.\n\nA self-closing helper still requires both attributes, for example `<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />`. Providers may supply ancestors, but not the current title. With no ancestors, a visible trail still contains the appended current node.\n\n| Parent attribute | Behavior |\n|------------------|----------|\n| `name` | Required stable trail name used for provider targeting, CSS, shape alternates, and name-based ancestor deduplication. |\n| `title` | Required non-null `Microsoft.AspNetCore.Html.IHtmlContent`, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text, not the current node in a visible trail. |\n| `heading` | Defaults to `h1` on admin requests and no heading on the front end. `heading=\"\"` suppresses the heading. |\n| `page-title` | Defaults to `true`; adds the nonempty explicit title as a browser-title segment. |\n| `display-type` | Defaults to `DetailAdmin` on admin requests and `Detail` elsewhere. Supplies shape alternates. |\n\n### Title normalization\n\nWith `@using OrchardCore.DisplayManagement.Html`, prepare the view's values as follows:\n\n| Value in the view | Title expression |\n|-------------------|------------------|\n| HTML-localized label | `title=\"@T[\"Edit {0}\", record.Name]\"` |\n| Plain model string | `title=\"@(new HtmlContentString(Model.DisplayText ?? string.Empty))\"` |\n| `LocalizedString` | `title=\"@(new HtmlContentString(localizedTitle.Value ?? string.Empty))\"` |\n| `DataLocalizedString` | `title=\"@(new HtmlContentString(dataTitle.Value ?? string.Empty))\"` |\n\nUse `title=\"@Microsoft.AspNetCore.Html.HtmlString.Empty\"` for intentionally empty content. A null title is invalid, including on the no-output path.\n\nWhen needed, every title follows the same normalization: render its `IHtmlContent` once with `WriteTo(writer, HtmlEncoder.Default)`, then HTML-decode once into plain text. The helper safely encodes that string for the heading, current node, and browser title. Literal `&amp;` in a string wrapped with `HtmlContentString` stays literal `&amp;`, serialized as `&amp;amp;` in HTML. Do not pre-encode the wrapper's input. `IHtmlContent` does not enable arbitrary title markup: final title output remains text-only.\n\n### Step 3: Remove duplicate title output\n\nRemove the view's old page heading and `RenderTitleSegments` call. The helper supplies both by default on the admin. Keep the helper in the `Title` zone so it works with the admin theme's top-bar title setting.\n\nFor a trail that does not own the heading, use the `Breadcrumbs` zone and `heading=\"\"`. Also set `page-title=\"false\"` if the page registers its browser title separately.\n\n## Child attributes and link behavior\n\n| Attribute | Type | Behavior |\n|-----------|------|----------|\n| `id` | `string` | Stable node identifier for shape alternates. |\n| `action`, `controller`, `area` | `string` | MVC route target. Prefer these over hand-built action URLs. |\n| `route-*` | `string` | Additional action route values, such as `route-id=\"@record.Id\"`. |\n| `route-values` | `IDictionary<string, string>` | Additional action route values supplied as a dictionary. |\n| `url` | `string` | Direct link target; ignored when `action` is set. |\n| `permission` | `string` | Stored as `BreadcrumbItem.PermissionName`, resolved through `IPermissionService` during visible ancestor link processing after providers; unknown names are ignored. |\n| `resource` | `object` | Resource passed to authorization for the permission. |\n| `link-enabled` | `bool` | Defaults to `true`. Use `false` when the view has already determined that the link should be unavailable. |\n\nThe inner content is localized ancestor text, for example `@T[\"Records\"]`. Child content is HTML-decoded after Razor evaluates it, then safely encoded by the shape template. The parent's `IHtmlContent` title is rendered and decoded once as described above. Templates safely encode normalized title and provider-supplied text; do not emit them with `Html.Raw`.\n\nEvery child and provider item remains an ancestor, including the last item in the list. The helper appends a separate title node after all ancestors; only that node is current and **never linked**. Ancestors with denied permissions, `link-enabled=\"false\"`, or no link target remain visible as text. Do not omit ancestors solely because the user cannot follow their links.\n\nChildren and providers only store named permissions. The helper resolves `PermissionName` after providers and link-eligibility checks, so removed or unlinkable ancestors do not cause a name lookup. The explicit title node never needs link processing. Providers can also add `Permission` objects to `BreadcrumbItem.Permissions`; authorization evaluates applicable permissions against `Resource`. Hidden trails perform neither permission lookup nor authorization.\n\n## Workflow B: resolve a nested editor's parent\n\nUse the real parent's display name and route ID, obtained from the view model when available. For example, a model exposing `Parent` and `Record` can render:\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", Model.Record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n        <breadcrumb-item id=\"Parent\" action=\"Edit\" controller=\"Record\" area=\"My.Module\"\n                         route-id=\"@Model.Parent.Id\"\n                         permission=\"@MyPermissions.ManageRecords.Name\"\n                         resource=\"@Model.Parent\">@T[\"Edit {0}\", Model.Parent.Name]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nIf only the parent ID is available, inject an existing domain service into Razor, not a breadcrumb-specific service. A model with `ParentContentItemId`, for example, can use:\n\n```razor\n@inject OrchardCore.ContentManagement.IContentManager ContentManager\n@{\n    var parent = await ContentManager.GetAsync(Model.ParentContentItemId);\n}\n```\n\nUse the loaded parent's ID and display text in ancestor child items. Follow the screen's existing missing-parent handling and error reporting. Do not guard parent lookups on the breadcrumb visibility setting. Hidden trails always skip child content, but parent attribute expressions and Razor code or model lookups outside that child content still execute.\n\n## Workflow C: postprocess a trail with a provider\n\nImplement `OrchardCore.Navigation.IBreadcrumbProvider`:\n\n```csharp\npublic interface IBreadcrumbProvider\n{\n    ValueTask BuildBreadcrumbAsync(BreadcrumbContext context);\n}\n```\n\n`BreadcrumbContext` is also in `OrchardCore.Navigation`. Its public constructor is `BreadcrumbContext(string name, string title, List<BreadcrumbItem> items, ViewContext viewContext, bool showTrail)`, with `ViewContext` from `Microsoft.AspNetCore.Mvc.Rendering`. The helper passes the normalized plain-string title, not the original `IHtmlContent`. It exposes read-only properties:\n\n| Property | Type | Use |\n|----------|------|-----|\n| `Name` | `string` | Filter the trail's literal name explicitly. |\n| `Title` | `string` | Normalized plain-text explicit title, which providers cannot replace. |\n| `Items` | `List<BreadcrumbItem>` | Shared mutable ancestor list. Add, insert, remove, replace, reorder, or edit ancestors; do not replace the read-only list reference. The final list order is preserved. The explicit current node is not in the list. |\n| `ViewContext` | `Microsoft.AspNetCore.Mvc.Rendering.ViewContext` | Read request context, such as `HttpContext`, when filtering by admin/front-end request. |\n| `ShowTrail` | `bool` | Whether the trail is visible. Helper-created provider contexts always have `ShowTrail == true`; hidden trails create no context and invoke no providers. |\n\nProviders see all visible trails unless they explicitly filter the context. This example targets names starting with `Example`, excluding the `Examples` list itself by name rather than localized title text:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Localization;\nusing OrchardCore.Navigation;\n\npublic sealed class ExampleBreadcrumbProvider : IBreadcrumbProvider\n{\n    private readonly IStringLocalizer S;\n\n    public ExampleBreadcrumbProvider(IStringLocalizer<ExampleBreadcrumbProvider> localizer)\n    {\n        S = localizer;\n    }\n\n    public ValueTask BuildBreadcrumbAsync(BreadcrumbContext context)\n    {\n        if (!context.Name.StartsWith(\"Example\", StringComparison.Ordinal) || context.Name == \"Examples\" ||\n            context.Items.Any(item => item.Id == \"Examples\"))\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        context.Items.Insert(0, new BreadcrumbItem\n        {\n            Id = \"Examples\",\n            Text = S[\"Examples\"].Value,\n            Url = \"~/examples\",\n        });\n\n        return ValueTask.CompletedTask;\n    }\n}\n```\n\nRegister the provider in `Startup.ConfigureServices`:\n\n```csharp\nservices.AddBreadcrumbProvider<ExampleBreadcrumbProvider>();\n```\n\nRegistration is scoped and uses `TryAddEnumerable`. For visible trails, providers run in DI registration order after collecting ancestor children, before appending the explicit current node and finalizing `IsCurrent` and `Href`. Later providers observe earlier ancestor mutations, and the helper preserves the final list order.\n\nUse `context.Name` to target a trail, or `context.ViewContext` to target a request. An admin-only provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)`. Set `Text` to a plain string, not encoded HTML; use `Url` or `RouteValues`, `PermissionName` or the `Permissions` list, `Resource`, and `LinkEnabled` to describe links. Do not depend on incoming `IsCurrent` or `Href`, which the helper computes afterward.\n\nThe example also supplies an ancestor to a trail with no inline children. The view must still provide its title:\n\n```razor\n<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />\n```\n\nProviders cannot remove or replace the explicit title: `Title` is read-only, and `Items` contains only ancestors. The helper appends the title node with `Id = \"Title\"` after all providers have run, even if a provider cleared or reordered every ancestor.\n\nHidden admin trails bypass providers completely: the helper does not resolve, enumerate, construct, or invoke `IBreadcrumbProvider` implementations. It creates no `BreadcrumbContext`; the retained `ShowTrail` property and constructor parameter are always true in helper-created provider contexts. Only the explicit title supplies hidden heading/browser-title output.\n\n## Dashboard ancestor\n\nThe Admin Dashboard feature registers `OrchardCore.AdminDashboard.Services.DashboardBreadcrumbProvider` with `AddBreadcrumbProvider<DashboardBreadcrumbProvider>()`.\n\nThe provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)` and leaves front-end trails unchanged. It skips insertion if `context.Name == \"Dashboard\"` or an ancestor already has ID `Dashboard`; it does not compare localized title text. Otherwise it inserts a localized Dashboard ancestor at index `0`, with ID `Dashboard` and `Url = \"~/\" + adminOptions.AdminUrlPrefix`. Later providers can move it just like any other ancestor.\n\nIts `Permissions` list contains the static `OrchardCore.AdminDashboard.Permissions.AccessAdminDashboard` permission object, so no named-permission lookup is required. Denied access makes a visible Dashboard ancestor plain text. The Dashboard page uses `name=\"Dashboard\"` and `title=\"@T[\"Dashboard\"]\"` so only the explicit current node, with ID `Title`, represents Dashboard.\n\nFor visible admin trails, Dashboard can be added when there are no other ancestors, but the explicit current title is still appended afterward. Hidden trails skip this provider entirely; it never affects title text. Disabling the feature removes this provider contribution; there is no Dashboard option on the tag helper.\n\n## List order\n\nAncestor order comes only from the list. Inline children are collected in declaration order; providers can use `Add`, `Insert`, `RemoveAt`, or other list operations to change it. The helper preserves the final list order without sorting.\n\n`BreadcrumbItem` has no `Position` property and does not implement `IPositioned`. The child helper has no `position` attribute. List changes cannot displace the current page: the helper appends the explicit title after every ancestor.\n\n## Disabled admin trails\n\n**Show breadcrumb** under **Configuration > Settings > Admin** (`AdminSettings.ShowBreadcrumb`, default on) controls admin trail visibility, not front-end trails.\n\nWhen disabled, the helper **skips all providers and breadcrumb items**, regardless of provider registration. It never calls `GetChildContentAsync`, never resolves, enumerates, constructs, or invokes providers (including Dashboard), and creates no `BreadcrumbContext` or items, including the title node. Child text and links are not evaluated.\n\nOnly the explicit title supplies heading/browser-title text. When needed, it is formatted once through `WriteTo(writer, HtmlEncoder.Default)` and HTML-decoded once. The helper safely encodes the normalized string, renders the heading directly with `oc-breadcrumb-title`, and registers the browser title when `page-title` is true. It does not generate URLs, resolve permission names, authorize links, or create shapes. Breadcrumb templates and alternates do not run.\n\nWhen the trail is hidden and both `heading=\"\"` and `page-title=\"false\"` are set, the helper validates the non-null title but also skips `WriteTo`. Parent attribute expressions and Razor code or model lookups outside child content still run: `T[...]` results and `HtmlContentString` wrappers are created before the helper, even when it skips `WriteTo`. All child-only work is skipped for every hidden trail; do not add visibility guards to views. For a visible trail, these attributes suppress only heading/browser-title output, not ancestors, providers, or the explicit current node.\n\n## Theming and low-level shapes\n\nVisible trails use the existing `Breadcrumb` and `BreadcrumbItem` shapes and their name, ID, and display-type alternates:\n\n- Whole trail: `Breadcrumb.cshtml`, `Breadcrumb-ContentsEdit.cshtml`, `Breadcrumb.DetailAdmin.cshtml`, `Breadcrumb-ContentsEdit.DetailAdmin.cshtml`.\n- One node: `BreadcrumbItem.cshtml`, `BreadcrumbItem-Dashboard.cshtml`, `BreadcrumbItem-ContentsEdit-Title.DetailAdmin.cshtml`.\n\n`Breadcrumb.cshtml` owns both the visible trail and heading. Override it to rearrange them. Keep text safely encoded. See `references/theming.md` for alternate precedence, encoding, and the distinction between hidden admin trails and shape-level `ShowTrail`.\n\nThe helper assigns the current node `Id = \"Title\"`. The existing alternate factory provides `BreadcrumbItem-Title.cshtml`, `BreadcrumbItem-[Name]-Title.cshtml`, `BreadcrumbItem-Title.[DisplayType].cshtml`, and `BreadcrumbItem-[Name]-Title.[DisplayType].cshtml`; no extra attribute or shape type is needed. Ancestor IDs still come from child `id` or provider-supplied `Id`.\n\n`IShapeFactory.BreadcrumbAsync` is a low-level presentation API for prebuilt `BreadcrumbItem` objects. Its caller supplies the complete ordered sequence, including the terminal current node, with authorized `Href` values and `IsCurrent` already resolved. It does not append a title node, invoke providers, build trails from names, load parents, add Dashboard, or register the browser title. It is not the recommended authoring path for a view.\n\n## Gotchas\n\n- **Missing title is invalid.** Every helper, including a self-closing one, needs a non-null `IHtmlContent` title, even on the no-output path. `HtmlString.Empty` is valid and still leaves a current node in a visible trail.\n- **Wrap non-HTML title values safely.** Pass `@T[...]` directly, never its `.Value`. Use `new HtmlContentString(value ?? string.Empty)` for strings and `new HtmlContentString(localizedTitle.Value ?? string.Empty)` for non-HTML localizer results; never `Html.Raw`.\n- **HTML-aware input is still text-only output.** Do not render normalized title text as raw HTML or decode it again.\n- **Children are ancestors only.** Declare the current page in parent `title`; the helper appends it with ID `Title`, so do not duplicate it with a child.\n- **Providers cannot change the current title.** Clearing or reordering ancestors never removes or replaces the appended current node.\n- **Hidden means title-only.** The helper skips all provider resolution and execution, child evaluation, contexts, and items. Provider contexts created for visible trails always have `ShowTrail == true`.\n- **No visibility guards in views.** Do not read `AdminSettings.ShowBreadcrumb` or wrap lookups or markup in setting-dependent branches; visibility belongs to the helper.\n- **Named permissions are deferred.** Do not look them up while collecting or postprocessing ancestors; removed or unlinkable ancestors do not need that work.\n- **Second heading.** Remove the old title output when the helper owns the title.\n- **Wrong dictionary type.** `route-values` accepts `IDictionary<string, string>`, not an anonymous object or `RouteValueDictionary`.\n- **Link checks are not endpoint security.** Keep authorization on the target action; `permission` and `link-enabled` control only the rendered link.\n- **Renamed identifiers affect themes and providers.** Keep trail names and ancestor IDs stable. The automatic current-node ID is always `Title`; screen-specific title alternates use the trail name.\n- **Hidden trails bypass templates.** A breadcrumb shape override cannot change the helper's directly rendered disabled-state heading.\n\n## References\n\n- `references/theming.md` - alternate tables, display types, front-end usage, and heading behavior.\n- `src/OrchardCore/OrchardCore.Navigation.Core/` - `BreadcrumbTagHelper`, `BreadcrumbItemTagHelper`, `IBreadcrumbProvider`, `BreadcrumbContext`, `BreadcrumbItem`, and `ShapeFactoryExtensions`.\n- `src/OrchardCore.Modules/OrchardCore.AdminDashboard/Services/DashboardBreadcrumbProvider.cs` - feature-registered admin ancestor provider.\n- `src/OrchardCore.Modules/OrchardCore.Navigation/` - `BreadcrumbShapes`, `BreadcrumbAlternatesFactory`, and the breadcrumb views.\n- `src/docs/reference/modules/Navigation/README.md` - canonical breadcrumb reference.\n- `AGENTS.md` - project conventions.\n",".agents/skills/orchardcore-localization/SKILL.md":"---\nname: orchardcore-localization\ndescription: Localizes OrchardCore apps — UI strings (IStringLocalizer/IViewLocalizer S, T, H), LocalizationSource, updating and exporting checked-in POT templates, localization analyzers, PO files and pluralization, content translation (LocalizationPart, culture picker), and supported-culture configuration. Use when adding or changing localizable strings, updating POT files, fixing OCLOC diagnostics, translating strings, adding a culture, or making content multilingual.\n---\n\n# OrchardCore Localization\n\nThis skill guides you through OrchardCore localization following project conventions.\n\nThree layers, often confused:\n\n- **UI/code localization** — translate hard-coded strings in C#/Razor/Liquid via `IStringLocalizer` (`S`), `IViewLocalizer` (`T`), `IHtmlLocalizer` (`H`). Checked-in **POT templates** describe the source strings and are embedded in assemblies; translated **PO files** supply runtime translations.\n- **Content localization** — translate *content items* across cultures via `LocalizationPart` and the `OrchardCore.ContentLocalization` module.\n- **Culture configuration** — which cultures the site supports and how the request culture is chosen.\n\n## Decide which layer\n\n| Goal | Layer |\n|------|-------|\n| Translate a label/message in code or a view | UI localization (`S`/`T`/`H`) + PO file |\n| Pluralize (\"1 item\" / \"{0} items\") | `S.Plural(...)` + PO `msgid_plural` |\n| Add, change, remove, or move a source string | Update the owning project's checked-in POT template (Workflow D) |\n| Export source strings for Crowdin or another translation platform | Export POT templates (Workflow D), then deploy translated PO files |\n| Translate a blog post into French | Content localization (`LocalizationPart`) |\n| Add a supported language / set default | Localization settings (`LocalizationSettings`) |\n| Translate data-driven values (e.g. taxonomy terms) | Data Localization (`DataLocalization` module) |\n\n## Workflow A: localize UI strings\n\n### Step 1: Inject the right localizer\n\n| Localizer | Convention | Where |\n|-----------|-----------|-------|\n| `IStringLocalizer<T>` | field `S` | drivers, services, controllers |\n| `IViewLocalizer` | property `T` | Razor views (already on `RazorPage`) |\n| `IHtmlLocalizer<T>` | field `H` | when the translation contains HTML |\n\n```csharp\npublic sealed class AliasPartDisplayDriver : ContentPartDisplayDriver<AliasPart>\n{\n    internal readonly IStringLocalizer S;\n\n    public AliasPartDisplayDriver(IStringLocalizer<AliasPartDisplayDriver> localizer)\n        => S = localizer;\n}\n```\n\nIn Razor: `@T[\"Welcome to Orchard Core\"]`. In a class: `S[\"A value is required for {0}.\", name]`. Keep keys as compile-time constants and pass values as format arguments, not interpolated keys. In Liquid: `{{ \"Welcome to Orchard Core\" | t }}`.\n\n### Step 2: Pluralize when counting\n\n```csharp\nS.Plural(count, \"{0} book\", \"{0} books\");\n// IHtmlLocalizer: H.Plural(count, \"{0} book\", \"{0} books\")\n```\n\n### Step 3: Reuse shared sources for strings declared without a localizer\n\nStrings declared before a localizer is available, such as deployment step categories and titles, are `LocalizationSource` instances translated at display time with `IStringLocalizerFactory.Localize()`. Don't declare them in the class that uses them:\n\n- Use `CommonLocalizationSources` (`OrchardCore.Abstractions`, namespace `OrchardCore.Localization`) for text that several projects use with the same meaning, such as `CommonLocalizationSources.ContentManagement`, or that is general to every module, such as `CommonLocalizationSources.Settings`. Look for an existing member before adding one. Add a member only when another project uses the text too; a module name or a category used by one project goes in that project's class.\n- Declare the other strings in an `internal static class {Area}LocalizationSources` at the root of the module, for example `LuceneLocalizationSources`, with `LocalizationSource.Create(\"...\", typeof({Area}LocalizationSources))`, which extraction tools can find.\n- Keep permission descriptions in their `*Permissions` class, and strings specific to a view in the view, with `T`.\n\n```csharp\n[JsonIgnore] // Not inherited from the base property; LocalizationSource can't be serialized.\npublic override LocalizationSource Category => CommonLocalizationSources.Search;\n\n[JsonIgnore]\npublic override LocalizationSource Title => LuceneLocalizationSources.LuceneSearchIndexes;\n```\n\nThe PO context is the class that declares the source, for example `msgctxt \"OrchardCore.Localization.CommonLocalizationSources\"`.\n\nResolve a source at display time with `stringLocalizerFactory.Localize(source, args)` or `htmlLocalizerFactory.Localize(source, args)`, using the current tenant's factory. Pass arguments to the factory extension, not to `LocalizationSource.Create`; never cache a translated result in a static field. For plurals, use `stringLocalizerFactory.Plural(count, source, \"{0} items\")` (or the HTML factory equivalent), with a constant plural form. The singular source must have a context; `count` supplies `{0}` automatically.\n\n### Step 4: Provide PO translations\n\nPO files are discovered (in order) from each module's `Localization/`, a global `/Localization`, the tenant's `App_Data/Sites/{tenant}/Localization`, and culture folders. A PO entry:\n\n```po\nmsgctxt \"OrchardCore.Admin.Views.AdminDashboard\"\nmsgid \"Welcome to Orchard Core\"\nmsgstr \"Bienvenue sur Orchard Core\"\n```\n\n`msgctxt` = `{Namespace}.{Class}` (services) or `{Namespace}.{ViewPath}` (views). Plurals:\n\n```po\nmsgctxt \"OrchardCore.AdminMenu.Views.Menu.List\"\nmsgid \"1 item\"\nmsgid_plural \"{0} items\"\nmsgstr[0] \"{0} élément\"\nmsgstr[1] \"{0} éléments\"\n```\n\nSee `references/localization.md` for the full PO discovery order and headers.\n\n### Step 5: Update and commit the source templates\n\nAfter adding, changing, removing, or moving a localized string, update the affected project's POT file using Workflow D below. Context changes and moving shared sources can change translation identities even when the displayed text stays the same.\n\n## Workflow B: localize content\n\n### Step 1: Enable the feature, attach the part\n\nEnable `OrchardCore.ContentLocalization` (Content Localization). Attach `LocalizationPart` to the content type (admin or recipe). It carries:\n\n```csharp\npublic class LocalizationPart : ContentPart, ILocalizable\n{\n    public string LocalizationSet { get; set; } // links all translations\n    public string Culture { get; set; }         // this version's culture\n}\n```\n\nOn create, the part handler generates a unique `LocalizationSet` and sets the default culture automatically.\n\n### Step 2: Translate\n\nFrom the editor's Localization, \"Translate\" creates a sibling content item sharing the same `LocalizationSet` with a different `Culture`. Programmatically:\n\n```csharp\nvar translated = await _contentLocalizationManager.LocalizeAsync(contentItem, \"fr-FR\");\n```\n\n### Step 3: Surface translations / culture picker\n\n`IContentLocalizationManager` fetches across a set:\n\n```csharp\nawait _contentLocalizationManager.GetContentItemAsync(localizationSet, \"fr\");\nawait _contentLocalizationManager.GetItemsForSetAsync(localizationSet);\n```\n\nLiquid: `{{ Model.ContentItem.Content.LocalizationPart.LocalizationSet | localization_set: \"en\" }}`. The Content Culture Picker feature adds a front-end language switcher and a `ContentRequestCultureProvider` that derives the culture from the localized route.\n\n## Workflow C: configure cultures\n\nRecipe `settings` step:\n\n```json\n{\n  \"name\": \"settings\",\n  \"LocalizationSettings\": {\n    \"DefaultCulture\": \"en-US\",\n    \"SupportedCultures\": [ \"en-US\", \"fr-FR\", \"es-ES\" ],\n    \"FallBackToParentCulture\": true\n  }\n}\n```\n\nThe Localization module reads these at startup and calls `app.UseRequestLocalization(...)` with the configured default + supported cultures.\n\n## Workflow D: update and export POT templates\n\n### Step 1: Use the existing build integration\n\nIn this repository, `OrchardCore.Commons.props` already imports the localization targets for framework projects, modules, and themes. Do not add redundant imports or package references. Outside the repository, each project whose strings need extraction must reference `OrchardCore.Localization.Build` with `PrivateAssets=\"all\"` and a version matching Orchard Core.\n\nEach project's template is `<AssemblyName>.pot`, next to its project file. A **normal build only embeds an existing template** as `<AssemblyName>.Localization.pot`; it neither extracts strings nor refreshes a stale template. A project without a template embeds nothing. `GenerateLocalizationCatalog=true` enables this integration, not automatic extraction.\n\n### Step 2: Regenerate from source\n\nRun commands from the repository root. Prefer updating just the affected project:\n\n```powershell\ndotnet msbuild src\\OrchardCore.Modules\\OrchardCore.Admin\\OrchardCore.Admin.csproj -t:UpdateLocalizationCatalog\n```\n\nUse `-restore` with `dotnet msbuild` when dependencies need restoring. The target updates only that project's template, not those of its references. For an application and its project references, or the entire solution:\n\n```powershell\ndotnet build src\\OrchardCore.Cms.Web\\OrchardCore.Cms.Web.csproj -p:UpdateLocalizationCatalogs=true\ndotnet build OrchardCore.slnx -p:UpdateLocalizationCatalogs=true\n```\n\nChoose the appropriate scope, rather than running every command. Use the SDK selected by `global.json`: extraction loads that SDK's Roslyn and Razor compilers. The source-tree worker is built on demand; no separate PoExtractor installation is needed.\n\nUpdates create or rewrite the template, or remove it if no messages remain. Unchanged inputs reuse the cache under `obj\\`; unchanged output is not rewritten. Do not hand-edit generated entries, headers, or references. Templates use deterministic ordering, UTF-8 and LF, no timestamps, and file references without line numbers by default. Multi-targeted projects extract using their first target framework; `DEBUG` and `TRACE` are excluded during extraction.\n\n### Step 3: Review the result and diagnostics\n\n```powershell\ngit status --short -- '*.pot'\ngit --no-pager diff -- '*.pot'\n```\n\nReview new files too (a normal `git diff` does not show untracked templates). Check the expected `msgctxt`, `msgid`, `msgid_plural`, source references, format flags, and format-argument comments. Keep `msgstr` values empty in POT files; translations belong in culture-specific PO files. Commit added, modified, and deleted POT files with their source changes. CI updates the solution's templates and fails if any are uncommitted or out of date.\n\nExtraction covers C#, Razor, literal Liquid `t` inputs, context-bearing `LocalizationSource` declarations, and explicit constant DataAnnotations validation messages. It does not extract JavaScript/TypeScript directly: expose their strings through a C# `IJSLocalizer` implementation. Fix dynamic keys or unresolved contexts at the source instead of inserting guessed POT entries.\n\nIDE/build analyzers report `OCLOC101` (constant source missing a context), `OCLOC102` (different declaring context, informational), `OCLOC103` (nonconstant source value), and `OCLOC104` (nonconstant localizer key or plural forms). The update worker reports `OCLOC001`-`OCLOC009`; see the reference for their meanings. Extraction errors always fail; `-p:LocalizationStrict=true` also fails on extraction warnings.\n\nUse `[SkipLocalizationExtraction]` only on forwarding infrastructure whose keys are declared elsewhere; it excludes marked classes/methods and nested declarations from extraction and analyzer diagnostics, not application calls to those helpers. For an intentionally dynamic application call, locally suppress `OCLOC104` with an explanation of where its keys originate. Do not hide missing application strings with broad suppressions.\n\n### Step 4: Export templates when requested\n\nBuild with an **absolute**, dedicated output directory to collect existing templates without regenerating them:\n\n```powershell\n$output = Join-Path $PWD 'artifacts\\localization'\ndotnet build OrchardCore.slnx \"-p:LocalizationCatalogOutputPath=$output\"\n```\n\nFiles are copied to `<output>\\<TargetFramework>\\<AssemblyName>.pot`. Add `-p:UpdateLocalizationCatalogs=true` only when an update is also intended. Use an empty export directory for an exact current snapshot; export does not remove stale files. Only built projects contribute templates, not prebuilt NuGet dependencies. Upload POT files to the translation platform, preserve contexts/plurals/placeholders, and download translated PO files to a supported runtime localization location. Embedded POT files alone do not change runtime translations.\n\nFor configuration properties, custom Liquid inputs, extraction diagnostics, and library entry points, see `references/localization.md`.\n\n## Quick Reference\n\n### Localizers\n\n| Symbol | Type | Output |\n|--------|------|--------|\n| `S` | `IStringLocalizer<T>` | plain string |\n| `T` | `IViewLocalizer` | encoded view string |\n| `H` | `IHtmlLocalizer<T>` | trusted translation markup; format arguments are HTML-encoded |\n\n### Content localization APIs\n\n| Member | Purpose |\n|--------|---------|\n| `IContentLocalizationManager.LocalizeAsync(item, culture)` | create a translation |\n| `.GetContentItemAsync(set, culture)` | one translation |\n| `.GetItemsForSetAsync(set)` | all translations |\n| `.GetItemsForSetsAsync(sets, culture)` | batch |\n| `ILocalizationService.GetDefaultCultureAsync()` / `GetSupportedCulturesAsync()` | culture config |\n\n### Request culture providers\n\n| Provider | Picks culture from |\n|----------|--------------------|\n| `ContentRequestCultureProvider` | the localized content route (added first) |\n| `AdminCookieCultureProvider` | `admin_culture_*` cookie, admin paths only |\n| cookie / querystring / Accept-Language | ASP.NET Core defaults |\n\n### Liquid filters\n\n`localization_set: \"fr\"` — translated item; `switch_culture_url` — culture-switch URL.\n\n## Gotchas\n\n- `msgctxt` must exactly match the namespace+class (or namespace+view path). Wrong context → string stays untranslated.\n- `IStringLocalizer<T>` context comes from `T`'s type — inject the localizer typed to the class that owns the strings.\n- `LocalizationSet` links translations; never share one set across unrelated items, and don't overwrite it.\n- Add a culture to `SupportedCultures` before content can be translated into it.\n- DataAnnotations need `AddDataAnnotationsPortableObjectLocalization()` (the Localization module registers it).\n- Ordinary builds do not refresh POT templates; always explicitly update and commit them with string/context changes.\n- A gettext source plural has exactly two constant forms; translated PO files can have more forms, selected by the culture's plural rule.\n- Set `GenerateLocalizationCatalog=false` only when intentionally disabling both embedding and updating, not as a fix for extraction errors.\n\n## References\n\n- `references/localization.md` — POT configuration and diagnostics, extraction APIs, PO discovery order, contexts, plural forms, content localization internals, startup wiring\n- `src/docs/reference/modules/Localize/README.md` (repo)\n- `src/docs/reference/modules/ContentLocalization/README.md` (repo)\n- `src/docs/reference/modules/DataLocalization/README.md` (repo)\n- `src/docs/reference/modules/Localize/javascript-localization.md` (repo) — `IJSLocalizer` bridge; no direct JavaScript extraction\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-nswag-regenerate/SKILL.md":"---\nname: orchardcore-nswag-regenerate\ndescription: Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to \"regenerate the NSwag client(s)\", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration.\n---\n\n# OrchardCore NSwag Regenerate\n\nRegenerates the OpenApi module's NSwag-generated C#/TypeScript API clients and verifies the regeneration produces a stable, non-reshuffled diff.\n\n## Preferred: `tools/OpenApiClientGenerator` (automated, no browser/dev-server needed)\n\nA console project that does the whole pipeline headlessly:\n\n```bash\ndotnet run --project tools/OpenApiClientGenerator -c Release\nyarn build   # refreshes the Vue app's bundled JS against the new TS client\n```\n\nIt boots the CMS in-process on an ephemeral port, uses `OrchardCore.AutoSetup` with\n`src/OrchardCore.Cms.Web/Recipes/openapi-generation-setup.recipe.json` (`issetuprecipe: true`)\nto provision the **Default** tenant with the same feature set as `openapi-generation.recipe.json`\nbelow, fetches `swagger.json`, writes a scratch `.nswag` config pointing at that capture, and\nshells out to the `nswag` CLI (same prerequisite as the manual path: `dotnet tool install -g\nNSwag.ConsoleCore`). Source: `tools/OpenApiClientGenerator/Program.cs`.\n\n**Why Default tenant specifically**: `OrchardCore.Tenants` (and `.Distributed`,\n`.FeatureProfiles`) have `DefaultTenantOnly = true` in their `[Feature]` manifest attribute —\nthey can only be enabled on the Default/Host shell, never a secondary tenant. A Blog-recipe\nfunctional-test tenant cannot enable `OrchardCore.Tenants`, which is why this tool targets the\nDefault shell via `AutoSetup` rather than reusing the functional-test harness's tenant pattern.\n\nThis tool only regenerates the clients against whatever API surface the current source produces\n— it does not add a CI drift-check gate (that was scoped out; see `nswag-automation-plan.md` in\ngit stash history for the fuller original proposal, \"Part 1\" of which this tool implements).\n\nThe manual procedure below still applies if you need to regenerate against a real running dev\nserver (e.g. to inspect `swagger.json` interactively) or investigate the tool's own behavior.\n\n## Where things live\n\n- Config: `src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag` — **this file already exists**, do not assume it's missing. It defines both generators:\n  - `openApiToCSharpClient` → outputs `Services/OpenApiClient.cs` (relative to the module folder)\n  - `openApiToTypeScriptClient` → outputs `../../../.scripts/bloom/services/OpenApiClient.ts`\n- Source document: `documentGenerator.fromDocument.url` points at a running instance's Swashbuckle endpoint, `https://localhost:5001/swagger/v1/swagger.json`. NSwag also accepts a **local file path** in this same field instead of a URL — useful for reproducible/offline generation (see below).\n- Recipe: `src/OrchardCore.Modules/OrchardCore.OpenApi/Recipes/openapi-generation.recipe.json` (name `OpenApiGeneration`) enables every feature that exposes an API endpoint: `OrchardCore.Contents`, `OrchardCore.Queries`, `OrchardCore.Tenants`, `OrchardCore.Search.Lucene`, `OrchardCore.Search.Elasticsearch`, `OrchardCore.OpenApi`. It also contains a `Settings` step setting `OpenApiSettings.AllowAnonymousSchemaAccess: true` — anonymous schema access is **disabled by default** (the middleware returns 401 for unauthenticated `swagger.json` fetches), and both NSwag paths below fetch the schema anonymously. The setup recipe used by the generator tool (`openapi-generation-setup.recipe.json`) has the same step. Note the `Settings` step replaces the whole stored `OpenApiSettings` object, wiping any OAuth configuration on the tenant it runs on — fine for throwaway generation tenants, worth knowing on a configured one.\n  - It has `\"issetuprecipe\": false`, so it does **not** appear in the new-tenant/site-setup recipe dropdown. Run it against an already-set-up tenant instead, via **Admin ▸ Configuration ▸ Recipes**, click \"Run\" next to \"OpenApi Generation\", confirm the modal. (`/Admin/Recipes`, `AdminController.Execute` in `OrchardCore.Recipes`.)\n\n## Manual procedure (fallback)\n\n### NSwag CLI\n\nInstalled as a dotnet tool: `~/.dotnet/tools/nswag` (or `dotnet tool install -g NSwag.ConsoleCore` if missing). Run with:\n\n```bash\nnswag run src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag\n```\n\nThis requires a live server at the configured `url`. For a real regeneration that updates the committed files, run the actual local dev server, set it up with the `OpenApiGeneration` recipe, and run the command above unmodified.\n\n## Reproducible / offline generation (for testing determinism, without touching committed files)\n\n1. Fetch `swagger.json` from a running instance into a scratch file.\n2. Copy the `.nswag` config, and in the copy only change `documentGenerator.fromDocument.url` to the scratch file's path, and both generators' `output` to scratch paths. Do this with a small Python/jq one-liner rather than hand-editing — the config is plain JSON.\n3. `nswag run <scratch-config>`.\n\nThis lets you diff two independent generations without ever touching the real generated files or needing HTTPS/dev-cert setup.\n\n## Determinism: why regeneration used to reshuffle unrelated methods\n\nTwo independent root causes were found and fixed (skrypt/openapi branch):\n\n1. **Swashbuckle's operation order wasn't pinned.** Without an explicit sort, `swagger.json` operations come out in whatever order ASP.NET Core's action discovery enumerates them — which depends on assembly/feature load order in OrchardCore's modular architecture, not source order. Fixed in `OrchardCore.OpenApi/Startup.cs`'s `AddSwaggerGen` call:\n   ```csharp\n   c.OrderActionsBy(apiDesc => $\"{apiDesc.RelativePath}_{apiDesc.HttpMethod}\");\n   ```\n   Route path + verb is fixed at compile time and unique per operation, so this is stable regardless of load order.\n\n2. **A duplicate `operationId` across two operations.** `QueryApiController` used to have one action handling both `GET` and `POST` on `api/queries/{name}` under a single `[EndpointName(\"ApiExecuteQuery\")]`. OpenAPI requires `operationId` to be unique per operation — reusing one across two operations forced NSwag to invent a disambiguating suffix internally, and that suffix logic wasn't deterministic across runs (e.g. `ApiExecuteQueryPOSTAsync` vs `ApiExecuteQueryPOSTPOSTAsync`). Fixed by splitting into two actions with distinct names (`ApiExecuteQueryGet` / `ApiExecuteQueryPost`), delegating to a shared private method — the pattern already used by e.g. `ElasticsearchApiController` (`ApiGetElasticsearchContent`/`ApiPostElasticsearchContent`).\n\n**Lesson for any future controller/endpoint added to the OpenApi-exposed surface**: never reuse the same `[EndpointName]`/`operationId` across two different HTTP verbs on the same route. Give each verb its own action and its own name, even if they share implementation via a private helper.\n\n## Verifying stability empirically\n\nBoot two independently-provisioned tenants (each gets its own `ShellContext` and fresh feature/extension discovery — this is what actually varies, not wall-clock time), run the `OpenApiGeneration` recipe on each, fetch `swagger.json` from each, generate against both, and diff. A stable setup produces byte-identical output except for the tenant's own base-URL prefix embedded in the client's default `baseUrl` (an expected, real difference between distinct tenants — not a bug).\n\nThe existing functional test suite (`test/OrchardCore.Tests.Functional/Tests/Cms/OpenApiTests.cs`) already exercises the relevant plumbing (feature enablement, swagger.json access) if you need a template for scripting this via Playwright/`CmsTestBase`. Any throwaway verification test written for this should be deleted afterward — it's not meant to be a permanent part of the suite.\n",".agents/skills/orchardcore-query-indexing/SKILL.md":"---\nname: orchardcore-query-indexing\ndescription: Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps.\n---\n\n# OrchardCore Query & Indexing\n\nThis skill guides you through OrchardCore's query and indexing systems following project conventions.\n\nTwo distinct systems, often confused:\n\n- **Queries** (`OrchardCore.Queries`) — named, stored queries executed on demand (`SQL`, `Lucene`, `Elasticsearch` sources). Return raw rows or content items.\n- **Indexing** (`OrchardCore.Indexing` + providers) — build full-text **index profiles** that mirror content into Lucene/Elasticsearch/Azure AI Search for searching. Driven by **index handlers**.\n\nSeparate again from **YesSql SQL index tables** (`MapIndex` + `SchemaBuilder`) — those are relational indexes for fast DB queries, covered by the `orchardcore-data-migration` skill, not here.\n\n## Decide what you need\n\n| Goal | System |\n|------|--------|\n| Run a stored SQL/Lucene query, expose via GraphQL/API | Queries |\n| Make content searchable (full-text) | Indexing (index profile + provider) |\n| Index a custom part/field's data into search | `IContentPartIndexHandler` / `IContentFieldIndexHandler` |\n| Fast relational lookup by a column | YesSql `MapIndex` → see `orchardcore-data-migration` |\n| Run a search box over an index | Search (`ISearchService`) |\n\n## Workflow A: define and run a query\n\n### Step 1: Pick a source\n\n`Query` has `Name`, `Source` (`Sql`/`Lucene`/`Elasticsearch`), `ReturnContentItems`, and source-specific metadata (e.g. a SQL `Template`).\n\n### Step 2: Create via recipe or manager\n\nRecipe step:\n\n```json\n{\n  \"name\": \"queries\",\n  \"Queries\": [\n    {\n      \"Source\": \"Sql\",\n      \"Name\": \"RecentPosts\",\n      \"Template\": \"select DocumentId from ContentItemIndex where ContentType = 'BlogPost'\",\n      \"ReturnContentItems\": true\n    }\n  ]\n}\n```\n\nProgrammatically:\n\n```csharp\nvar query = await _queryManager.NewAsync(\"Sql\", data);\nawait _queryManager.SaveAsync(query);\n```\n\n### Step 3: Execute\n\n```csharp\nvar query = await _queryManager.GetQueryAsync(\"RecentPosts\");\nvar results = await _queryManager.ExecuteQueryAsync(query, new Dictionary<string, object>\n{\n    [\"maxResults\"] = 10,\n});\nforeach (var item in results.Items) { /* JsonObject or ContentItem */ }\n```\n\nSQL templates are Liquid-rendered (parameters become Liquid vars), parsed by `SqlParser`, then run via Dapper. `ReturnContentItems = true` loads `ContentItem`s by `DocumentId`; otherwise rows are returned as `JsonObject`.\n\n## Workflow B: index content for search\n\n### Step 1: Create an index profile\n\nA profile binds a provider (`Lucene`/`Elasticsearch`/`AzureAISearch`) + a type (`Content`) + which content types to index.\n\n```json\n{\n  \"name\": \"CreateOrUpdateIndexProfile\",\n  \"indexes\": [\n    {\n      \"Name\": \"BlogPostsLucene\",\n      \"IndexName\": \"blogposts\",\n      \"ProviderName\": \"Lucene\",\n      \"Type\": \"Content\",\n      \"Properties\": {\n        \"ContentIndexMetadata\": {\n          \"IndexLatest\": false,\n          \"IndexedContentTypes\": [ \"BlogPost\" ],\n          \"Culture\": \"any\"\n        },\n        \"LuceneIndexMetadata\": { \"AnalyzerName\": \"standard\", \"StoreSourceData\": true }\n      }\n    }\n  ]\n}\n```\n\nContent changes raise indexing tasks; `ContentIndexingService` resolves the content item and calls every part/field index handler to build a `DocumentIndex`, which the provider stores.\n\n### Step 2: Index custom part/field data\n\nFor a custom part, implement `ContentPartIndexHandler<TPart>`:\n\n```csharp\npublic class AliasPartIndexHandler : ContentPartIndexHandler<AliasPart>\n{\n    public override Task BuildIndexAsync(AliasPart part, BuildPartIndexContext context)\n    {\n        var options = DocumentIndexOptions.Keyword | DocumentIndexOptions.Store;\n        foreach (var key in context.Keys)\n        {\n            context.DocumentIndex.Set(key, part.Alias, options);\n        }\n        return Task.CompletedTask;\n    }\n}\n```\n\nFields use `ContentFieldIndexHandler<TField>` with `BuildFieldIndexContext`. `context.Keys` are the index field names to populate; `DocumentIndexOptions` flags control storage:\n\n| Flag | Effect |\n|------|--------|\n| `Store` | keep the value retrievable from the index |\n| `Keyword` | index as a non-analyzed keyword (exact match) |\n| `Sanitize` | strip HTML before indexing |\n| `None` | analyzed full-text (default text) |\n\n### Step 3: Register the handler\n\n```csharp\nservices.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();\n// field:\nservices.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();\n```\n\n### Step 4: Reset / rebuild\n\nRecipe steps `ResetIndex` (re-sync from last position) and `RebuildIndex` (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.\n\n## Quick Reference\n\n### Query APIs\n\n| Type | Member |\n|------|--------|\n| `IQueryManager` | `NewAsync`, `SaveAsync`, `GetQueryAsync`, `ExecuteQueryAsync`, `ListQueriesAsync` |\n| `IQuerySource` | `Name`, `ExecuteQueryAsync(query, parameters)` — implement to add a source |\n| `IQueryResults` | `IEnumerable<object> Items` |\n\n### Indexing APIs\n\n| Type | Member |\n|------|--------|\n| `IIndexProfileManager` | `NewAsync`, `CreateAsync`, `UpdateAsync`, `FindByNameAsync`, `SynchronizeAsync`, `ResetAsync` |\n| `IDocumentIndexHandler` | `BuildIndexAsync`, `DocumentsAddedOrUpdatedAsync`, `DocumentsDeletedAsync` |\n| `IContentPartIndexHandler` / `IContentFieldIndexHandler` | `BuildIndexAsync(...)` to populate `DocumentIndex` |\n| `IIndexManager` (per provider) | `CreateAsync`, `RebuildAsync`, `DeleteAsync`, `ExistsAsync` |\n| `IDocumentIndexManager` | `AddOrUpdateDocumentsAsync`, `DeleteDocumentsAsync` |\n| `DocumentIndex` | `Set(name, value, options)` overloads; `Types` enum |\n\n### Register an indexing source\n\n```csharp\nservices.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(\n    providerName, implementationType);\n// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);\n```\n\n### Search\n\n`ISearchService.SearchAsync(IndexProfile index, string term, int start, int size)` → `SearchResult`. Providers: `OrchardCore.Search.Lucene`, `.Elasticsearch`, `.AzureAI`.\n\n## Gotchas\n\n- \"Index\" is overloaded: full-text **index profiles** (this skill) vs YesSql **MapIndex tables** (`orchardcore-data-migration`). Don't mix them.\n- Query recipe step is `queries`; index profile step is `CreateOrUpdateIndexProfile`. Older `lucene-index` / `ElasticIndexSettings` steps are deprecated — prefer `CreateOrUpdateIndexProfile`.\n- `IndexLatest: true` indexes draft (latest) versions; `false` indexes published. Match to your search UI.\n- SQL query `Template` is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.\n\n## References\n\n- `references/indexing.md` — index profiles, handlers, DocumentIndex, providers, recipe steps\n- `src/docs/reference/modules/Queries/README.md` (repo)\n- `src/docs/reference/modules/Indexing/README.md` (repo)\n- `src/docs/reference/modules/{Lucene,Elasticsearch,SQLIndexing}/README.md` (repo)\n- `AGENTS.md` (repo root) — build commands\n",".agents/skills/orchardcore-asset-manager/SKILL.md":"---\nname: orchardcore-asset-manager\ndescription: Builds, watches, and manages frontend assets in OrchardCore. Use when modifying SCSS, JS, TS, or Vue files, adding new assets to a module/theme, or troubleshooting build failures. Covers all asset actions (vite, sass, min, copy, parcel, webpack, concat) and the three-tier package structure.\n---\n\n# OrchardCore Asset Manager\n\nHandles all frontend asset compilation for OrchardCore modules and themes. Assets are discovered via `Assets.json` files under `src/{OrchardCore.Modules,OrchardCore.Themes}/*/`.\n\n## Node.js Version\n\nThe repo requires **Node.js 24.x** (pinned in `.node-version`). The build script auto-detects a mismatch and prompts:\n\n```\n1) Continue anyway\n2) Abort\n3) Install via fnm, Node.js 24.x and build\n4) Install via Volta, Node.js 24.x and build\n```\n\nAlways select **option 1** (continue) or **option 3** (install via fnm). The allowed Bash commands for this project pre-approve both:\n\n```bash\necho \"1\" | yarn build          # continue with current Node.js\necho \"3\" | yarn build          # install Node 24 via fnm and build\n```\n\n## Build Commands\n\nAll commands run from the **repository root** (`/home/skrypt/repo/orchardcore`).\n\n```bash\n# Build all assets\necho \"1\" | yarn build\n\n# Build a specific asset by name\necho \"1\" | yarn build -n media-app\n\n# Build multiple assets\necho \"1\" | yarn build -n media-app,media-field\n\n# Build by tag\necho \"1\" | yarn build -t admin\n\n# Watch a specific asset (dev mode, auto-rebuilds on save)\necho \"1\" | yarn watch -n media-app\n\n# Host with bundler dev server (HMR)\necho \"1\" | yarn host -n media-app\n\n# Clean all build output\necho \"1\" | yarn clean\n```\n\nThe `-n` name maps to the `\"name\"` field in `Assets.json`.\n\n## Local Claude Code Setup (One-time, per developer)\n\n`.claude/` is gitignored. To avoid being prompted on every build command, create `.claude/settings.json` locally with these pre-approved commands:\n\n```json\n{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(echo \\\"1\\\" | yarn build*)\",\n      \"Bash(echo \\\"3\\\" | yarn build*)\",\n      \"Bash(fnm exec --using 24.14.1 -- corepack yarn install*)\",\n      \"Bash(yarn check*)\",\n      \"Bash(yarn lint*)\",\n      \"Bash(echo \\\"1\\\" | yarn dry-run*)\"\n    ]\n  }\n}\n```\n\nDo **not** commit this file or add a `.gitignore` exception for it — it is intentionally kept local to avoid silently pre-approving commands on other developers' machines.\n\n## Prerequisites / Installing Dependencies\n\nBefore building, if packages are missing:\n\n```bash\nfnm exec --using 24.14.1 -- corepack yarn install\n```\n\nAfter modifying any `package.json` (root, `.scripts/assets-manager/`, or a module's `Assets/`), re-run install.\n\n## Built Output\n\nBuilt files go to the module/theme's `wwwroot/` folder and **must be committed** to the repo. After any asset change, commit both the source file and the generated `wwwroot/` files.\n\n## Package Structure (Three-Tier)\n\n| Location | Purpose |\n|---|---|\n| Root `package.json` | Workspace orchestration, version resolutions |\n| `.scripts/assets-manager/package.json` | Build toolchain (Vite, Parcel, Sass, etc.) |\n| `src/.../Assets/package.json` | Runtime deps bundled into that module/theme |\n\nTo add a **runtime dependency** to a module:\n```bash\n# From the module's Assets/ folder\nyarn add some-library@1.2.3\n# Then rebuild\necho \"1\" | yarn build -n asset-name\n```\n\nTo add a **build tool** (affects all assets):\n- Edit `.scripts/assets-manager/package.json`\n- Run `fnm exec --using 24.14.1 -- corepack yarn install`\n\n## Assets.json\n\nEach module/theme with assets has an `Assets.json` at its root (e.g. `src/OrchardCore.Modules/OrchardCore.Media/Assets.json`). See `references/actions.md` for all supported actions.\n\nQuick example:\n```json\n[\n  {\n    \"action\": \"vite\",\n    \"name\": \"media-app\",\n    \"source\": \"Assets/media-app/\",\n    \"tags\": [\"admin\", \"js\"]\n  },\n  {\n    \"action\": \"sass\",\n    \"name\": \"media-styles\",\n    \"source\": \"Assets/scss/media.scss\",\n    \"tags\": [\"admin\", \"css\"]\n  }\n]\n```\n\n## Code Quality Commands\n\nThese do **not** go through the Node.js version prompt — run them directly.\n\n```bash\n# TypeScript type-check all Vue/TS files (vue-tsc --noEmit)\nyarn check\n\n# Lint all JS/TS/Vue files (ESLint)\nyarn lint\n\n# Preview which files would be built/copied without writing anything\necho \"1\" | yarn dry-run\necho \"1\" | yarn dry-run -n media-app   # scoped to one asset\n```\n\n- **`yarn check`** — runs `vue-tsc --noEmit`; catches type errors across all Vue/TS source files. Run before committing TS/Vue changes. Does **not** support `-n`; to check a specific module, point it at that module's tsconfig directly: `yarn vue-tsc --noEmit -p src/OrchardCore.Modules/OrchardCore.Media/Assets/media-app/tsconfig.json`\n- **`yarn lint`** — runs ESLint across the repo. Accepts a file or directory to scope it: `yarn lint src/OrchardCore.Modules/OrchardCore.Media/Assets/`. Fix any errors before committing.\n- **`yarn dry-run`** — logs what the build would do (copy targets, output paths) without touching any files. Useful when adding a new `Assets.json` entry to verify paths before the first real build.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `Cannot find package '@tailwindcss/vite'` | Run `fnm exec --using 24.14.1 -- corepack yarn install` |\n| Parcel cache stale after deleting output | Run `echo \"1\" \\| yarn clean` then rebuild |\n| Changes not reflected | Confirm the built `wwwroot/` files changed; rebuild if not |\n| Ran a full-repo build by accident (wrong `-n` value, e.g. package.json name instead of `Assets.json` \"name\") | `git status --short` immediately; `git checkout --` every changed path outside your target module/theme before committing |\n| Edited source but bug still repros in the running app | You forgot to rebuild. `stat -c '%y' <source>.ts` vs `stat -c '%y' wwwroot/Scripts/<bundle>.js` — if the bundle is older, run `yarn build -n <asset-name>` |\n\n## References\n\n- `references/actions.md` — All Assets.json action types with examples\n</content>\n</invoke>",".agents/skills/orchardcore-module-creator/SKILL.md":"---\nname: orchardcore-module-creator\ndescription: Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality.\n---\n\n# OrchardCore Module Creator\n\nThis skill guides you through creating new OrchardCore modules following project conventions.\n\n## Prerequisites\n\n- OrchardCore repository at `D:\\orchardcore`\n- .NET SDK 10.0+ installed\n\n## Module Creation Workflow\n\n### Step 1: Determine Module Type\n\n**What kind of module are you creating?**\n\n| Type | Description | Key Components |\n|------|-------------|----------------|\n| **Content Part** | Adds data/behavior to content items | Part, Driver, Views |\n| **Content Field** | Custom field type | Field, Driver, Views |\n| **Settings** | Site-wide configuration | SiteSettings, Driver |\n| **Admin Feature** | Admin pages/tools | Controller, Views, Menu |\n| **API** | REST endpoints | ApiController |\n| **Background Task** | Scheduled jobs | IBackgroundTask |\n\n### Step 2: Create Module Directory\n\n```bash\n# Create module folder\nmkdir src/OrchardCore.Modules/OrchardCore.YourModule\ncd src/OrchardCore.Modules/OrchardCore.YourModule\n```\n\n### Step 3: Create Required Files\n\nEvery module needs these three files:\n\n1. **Manifest.cs** - Module metadata\n2. **Startup.cs** - Service registration  \n3. **OrchardCore.YourModule.csproj** - Project file\n\nSee `references/module-structure.md` for templates.\n\n### Step 4: Add Components Based on Type\n\n**For Content Part modules:**\n```\nModels/YourPart.cs\nViewModels/YourPartViewModel.cs\nDrivers/YourPartDisplayDriver.cs\nViews/YourPart.cshtml\nViews/YourPart_Edit.cshtml\n```\n\n**For Admin modules:**\n```\nControllers/AdminController.cs\nViews/Admin/Index.cshtml\nAdminMenu.cs\nPermissionProvider.cs\n```\n\n**For Data-storing modules:**\n```\nMigrations.cs\nIndexes/YourIndex.cs\n```\n\nSee `references/patterns.md` for code templates.\n\n### Step 5: Register in Startup.cs\n\n```csharp\npublic override void ConfigureServices(IServiceCollection services)\n{\n    // Content part\n    services.AddContentPart<YourPart>()\n        .UseDisplayDriver<YourPartDisplayDriver>();\n    \n    // Services\n    services.AddScoped<IYourService, YourService>();\n    \n    // Migrations (if storing data)\n    services.AddDataMigration<Migrations>();\n    \n    // Permissions (if securing features)\n    services.AddPermissionProvider<PermissionProvider>();\n    \n    // Navigation (if adding admin menu)\n    services.AddNavigationProvider<AdminMenu>();\n}\n```\n\n### Step 6: Build and Test\n\n```bash\n# Build the module\ncd D:\\orchardcore\ndotnet build src/OrchardCore.Modules/OrchardCore.YourModule\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n\n# Enable the feature in Admin → Features\n```\n\n## Quick Reference\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Module folder | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Namespace | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Feature ID | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Content Part | `NamePart` | `RatingPart` |\n| Driver | `NamePartDisplayDriver` | `RatingPartDisplayDriver` |\n| View | `PartName.cshtml` | `RatingPart.cshtml` |\n| Edit View | `PartName_Edit.cshtml` | `RatingPart_Edit.cshtml` |\n\n### Common Dependencies\n\nAdd to `.csproj` as needed:\n\n```xml\n<!-- Core module support -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Module.Targets\\OrchardCore.Module.Targets.csproj\" />\n\n<!-- Content management -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.ContentManagement\\OrchardCore.ContentManagement.csproj\" />\n\n<!-- Admin UI -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Admin\\OrchardCore.Admin.csproj\" />\n```\n\n### Feature Categories\n\nUse in `Manifest.cs`:\n- `Content Management`\n- `Content`\n- `Navigation`\n- `Security`\n- `Infrastructure`\n- `Theming`\n- `Developer`\n\n## References\n\n- `references/module-structure.md` - Directory layout and file templates\n- `references/patterns.md` - Code patterns (parts, drivers, handlers, etc.)\n- `references/examples.md` - Complete module examples\n- `AGENTS.md` (repo root) - Coding conventions and build commands\n",".agents/skills/orchardcore-recipe-creator/SKILL.md":"---\nname: orchardcore-recipe-creator\ndescription: Creates OrchardCore setup recipes (.recipe.json) that provision a tenant on setup — enabling features, setting themes, defining content types, importing content, roles, and settings. Use when the user needs to create a setup recipe, a starter recipe, or automate tenant configuration.\n---\n\n# OrchardCore Setup Recipe Creator\n\nThis skill guides you through creating a **setup recipe** for OrchardCore. A setup recipe is a `.recipe.json` file that runs when a tenant is created, provisioning everything the site needs: enabled features, active themes, content definitions, content items, roles, and settings.\n\n## Prerequisites\n\n- OrchardCore repository (working directory)\n- A module or theme to host the recipe (recipes live in a `Recipes/` folder)\n- Basic knowledge of the features your site needs\n\n## What makes a recipe a *setup* recipe\n\nA setup recipe is a normal recipe with `\"issetuprecipe\": true` in its header. That flag makes it appear in the setup screen's recipe dropdown so a user can pick it when creating a tenant. Without it, the recipe is still executable from the admin (Configuration → Recipes) but won't show during setup.\n\n## Setup Recipe Workflow\n\n### Step 1: Choose the host and create the Recipes folder\n\nRecipes are discovered from a `Recipes/` folder inside any enabled module or theme:\n\n```bash\nmkdir src/OrchardCore.Themes/YourTheme/Recipes\n```\n\nThe file name must end with `.recipe.json`:\n\n```\nsrc/OrchardCore.Themes/YourTheme/Recipes/yoursite.recipe.json\n```\n\n### Step 2: Write the recipe header\n\n```json\n{\n  \"name\": \"YourSite\",\n  \"displayName\": \"Your Site\",\n  \"description\": \"Provisions a Your Site tenant with content and theme.\",\n  \"author\": \"The Orchard Core Team\",\n  \"website\": \"https://orchardcore.net\",\n  \"version\": \"1.0.0\",\n  \"issetuprecipe\": true,\n  \"categories\": [ \"default\" ],\n  \"tags\": [ \"yoursite\" ],\n  \"variables\": {\n    \"homeContentItemId\": \"[js:uuid()]\"\n  },\n  \"steps\": []\n}\n```\n\n`variables` are evaluated on first access and reused across steps — use them for content item ids so steps can reference the same item. Recipe JSON may contain `// comments`.\n\n### Step 3: Add the core steps in order\n\nSteps run **top to bottom**. A typical setup recipe order:\n\n1. `feature` — enable the modules and theme features the site needs.\n2. `themes` — set the active site and admin themes.\n3. `settings` — site-wide settings (home route, culture, time zone).\n4. `Roles` — roles and their permissions.\n5. `ContentDefinition` — content types and parts.\n6. `content` — the actual content items (pages, menus, widgets).\n7. `media`, `queries`, `layers`, `Templates`, `AdminMenu` — as needed.\n\nMinimum viable setup recipe:\n\n```json\n\"steps\": [\n  {\n    \"name\": \"feature\",\n    \"enable\": [\n      \"OrchardCore.Admin\",\n      \"OrchardCore.Contents\",\n      \"OrchardCore.ContentTypes\",\n      \"OrchardCore.Title\",\n      \"OrchardCore.Autoroute\",\n      \"OrchardCore.Alias\",\n      \"OrchardCore.Themes\",\n      \"OrchardCore.HomeRoute\",\n      \"TheTheme\",\n      \"YourTheme\"\n    ]\n  },\n  {\n    \"name\": \"themes\",\n    \"admin\": \"TheAdmin\",\n    \"site\": \"YourTheme\"\n  }\n]\n```\n\n!!! warning\n    Always enable your own theme's feature in the `feature` step. If you set it as the `site` theme without enabling it, the layout won't render after setup.\n\n### Step 4: Reference the full step catalog\n\nFor every built-in step (`feature`, `themes`, `settings`, `ContentDefinition`, `content`, `media`, `Roles`, `layers`, `queries`, `Templates`, `AdminMenu`, `WorkflowType`, `deployment`, `custom-settings`, `recipes`, search indexes), see `references/steps.md`.\n\n### Step 5: Use recipe helpers for dynamic values\n\nValues can embed expressions with `[helper:...]` syntax:\n\n| Helper | Example | Use |\n|--------|---------|-----|\n| `js` | `\"[js:variables('homeContentItemId')]\"` | Reference variables / JS expressions |\n| `uuid` | `\"[js:uuid()]\"` | Generate a new content item id |\n| `file` | `\"[file:text('Snippets/home.liquid')]\"` | Inline an external file's text |\n| `env` | `\"[env:MY_VAR]\"` | Read an environment variable |\n| `appsettings` | `\"[appsettings:OrchardCore:SiteName]\"` | Read appsettings config |\n| `localization` | `\"[localization:WelcomeTitle]\"` | Localized string |\n| `base64` / `gzip` | `\"[js:base64('...')]\"` | Decode embedded binary/text |\n\n### Step 6: Test the recipe\n\n```bash\n# Build the host module/theme\ndotnet build src/OrchardCore.Themes/YourTheme\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n```\n\nThen create a new tenant (or reset the default one) and pick **Your Site** in the setup screen's recipe list. Verify features, theme, and content provisioned correctly.\n\nTo re-run a non-setup recipe without a fresh tenant: Admin → Configuration → Recipes → Run.\n\n## Quick Reference\n\n### Header properties\n\n| Property | Purpose |\n|----------|---------|\n| `name` | Unique internal id (used by the `recipes` step to call it) |\n| `displayName` | Friendly name in setup/admin UI |\n| `description` | Short summary shown to the user |\n| `issetuprecipe` | `true` makes it selectable during tenant setup |\n| `categories` / `tags` | Grouping/filtering in the UI |\n| `variables` | Reusable values, evaluated once on first access |\n| `steps` | Ordered actions to execute |\n\n### Common feature ids\n\n| Area | Features |\n|------|----------|\n| Core | `OrchardCore.Admin`, `OrchardCore.HomeRoute`, `OrchardCore.Themes`, `OrchardCore.Settings`, `OrchardCore.Recipes` |\n| Content | `OrchardCore.Contents`, `OrchardCore.ContentTypes`, `OrchardCore.ContentFields`, `OrchardCore.Title`, `OrchardCore.Autoroute`, `OrchardCore.Alias`, `OrchardCore.Html`, `OrchardCore.Flows`, `OrchardCore.Lists`, `OrchardCore.Widgets` |\n| Media/Menu | `OrchardCore.Media`, `OrchardCore.Menu`, `OrchardCore.Layers` |\n| Security | `OrchardCore.Users`, `OrchardCore.Roles`, `OrchardCore.Security` |\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Recipe file | `name.recipe.json` (lowercase) | `blog.recipe.json` |\n| `name` property | PascalCase | `Blog` |\n| Location | `Recipes/` in a module or theme | `Themes/TheBlogTheme/Recipes/` |\n\n## References\n\n- `references/recipe-structure.md` - File layout, header, variables, helpers\n- `references/steps.md` - Every built-in recipe step with examples\n- `reference/modules/Recipes/README.md` (docs) - Full recipe documentation\n- Example recipes: `src/OrchardCore.Themes/*/Recipes/*.recipe.json`\n",".agents/skills/orchardcore-tenants/SKILL.md":"# orchardcore-tenants\n\nPath: `.agents/skills/orchardcore-tenants/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/SKILL.md)",".agents/skills/orchardcore-tester/SKILL.md":"# orchardcore-tester\n\nPath: `.agents/skills/orchardcore-tester/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/SKILL.md)",".agents/skills/orchardcore-theme-creator/SKILL.md":"# orchardcore-theme-creator\n\nPath: `.agents/skills/orchardcore-theme-creator/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/SKILL.md)",".agents/skills/orchardcore-unit-test/SKILL.md":"# orchardcore-unit-test\n\nPath: `.agents/skills/orchardcore-unit-test/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/SKILL.md)",".agents/skills/orchardcore-video-producer/SKILL.md":"# orchardcore-video-producer\n\nPath: `.agents/skills/orchardcore-video-producer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/SKILL.md)",".agents/skills/orchardcore-workflow-activity/SKILL.md":"# orchardcore-workflow-activity\n\nPath: `.agents/skills/orchardcore-workflow-activity/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/SKILL.md)","AGENTS.md":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/AGENTS.md)",".github/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.github/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.github/mcp.json)",".vscode/mcp.json":"# Model Context Protocol (MCP) Configuration\n\nPath: `.vscode/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.vscode/mcp.json)",".agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md)",".agents/skills/orchardcore-admin-index-views/references/worked-example.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-index-views/references/worked-example.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/references/worked-example.md)",".agents/skills/orchardcore-asset-manager/references/actions.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-asset-manager/references/actions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/references/actions.md)",".agents/skills/orchardcore-breadcrumbs/references/theming.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-breadcrumbs/references/theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/references/theming.md)",".agents/skills/orchardcore-data-migration/references/patterns.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/patterns.md)",".agents/skills/orchardcore-data-migration/references/schema-builder.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/schema-builder.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/schema-builder.md)",".agents/skills/orchardcore-display-management/references/drivers.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/drivers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/drivers.md)",".agents/skills/orchardcore-display-management/references/placement.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/placement.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/placement.md)",".agents/skills/orchardcore-docs-writer/references/docs-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-docs-writer/references/docs-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-docs-writer/references/docs-structure.md)",".agents/skills/orchardcore-localization/references/localization.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-localization/references/localization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/references/localization.md)",".agents/skills/orchardcore-module-creator/references/examples.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/examples.md)",".agents/skills/orchardcore-module-creator/references/module-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/module-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/module-structure.md)",".agents/skills/orchardcore-module-creator/references/patterns.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/patterns.md)",".agents/skills/orchardcore-query-indexing/references/indexing.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-query-indexing/references/indexing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/references/indexing.md)",".agents/skills/orchardcore-recipe-creator/references/recipe-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md)",".agents/skills/orchardcore-recipe-creator/references/steps.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/steps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/steps.md)",".agents/skills/orchardcore-tenants/references/shells.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tenants/references/shells.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/references/shells.md)",".agents/skills/orchardcore-tester/references/admin-navigation.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/admin-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/admin-navigation.md)",".agents/skills/orchardcore-tester/references/autosetup.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/autosetup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/autosetup.md)",".agents/skills/orchardcore-tester/references/common-features.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/common-features.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/common-features.md)",".agents/skills/orchardcore-tester/references/debugging.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/debugging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/debugging.md)",".agents/skills/orchardcore-tester/references/playwright-cli.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/playwright-cli.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/playwright-cli.md)",".agents/skills/orchardcore-tester/references/setup-wizard.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/setup-wizard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/setup-wizard.md)",".agents/skills/orchardcore-theme-creator/references/assets.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/assets.md)",".agents/skills/orchardcore-theme-creator/references/theme-structure.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/theme-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/theme-structure.md)",".agents/skills/orchardcore-unit-test/references/testing.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-unit-test/references/testing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/references/testing.md)",".agents/skills/orchardcore-video-producer/references/recording.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/recording.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/recording.md)",".agents/skills/orchardcore-video-producer/references/specs.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/specs.md)",".agents/skills/orchardcore-video-producer/references/storyboard.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/storyboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/storyboard.md)",".agents/skills/orchardcore-workflow-activity/references/activities.md":"# References Skill\n\nPath: `.agents/skills/orchardcore-workflow-activity/references/activities.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/references/activities.md)","src/docs/guides/integrate-facebook-plugins/README.md":"# integrate-facebook-plugins Documentation\n\nPath: `src/docs/guides/integrate-facebook-plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/guides/integrate-facebook-plugins/README.md)","src/docs/topics/local-nuget-packages/README.md":"# local-nuget-packages Documentation\n\nPath: `src/docs/topics/local-nuget-packages/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/topics/local-nuget-packages/README.md)",".agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json":"# Secrets-module Skill\n\nPath: `.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json)","src/docs/reference/modules/Rules/README.md":"# Rules Skill\n\nPath: `src/docs/reference/modules/Rules/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/reference/modules/Rules/README.md)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/AGENTS.md)","tokens":300,"sizeBytes":156},{"name":"ocat-classes.md","path":".agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md)","tokens":300,"sizeBytes":262},{"name":"SKILL.md","path":".agents/skills/orchardcore-admin-edit-views/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/SKILL.md","title":"orchardcore-admin-edit-views","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-admin-edit-views\ndescription: Creates and updates OrchardCore admin edit views using the ocat-* CSS class conventions. Use when creating or modifying Edit view files (*.Edit.cshtml, *.Fields.Edit.cshtml) to ensure correct layout and styling.\nstatus: completed\n---\n\n# OrchardCore Admin Edit Views\n\nThis skill guides you through creating and updating admin edit view files using the `ocat-*` CSS class conventions required by the OrchardCore admin theme.\n\n## The `ocat-*` CSS Class System\n\nThe `ocat-*` CSS classes provide a standardized two-column layout for admin form fields. Every form field uses a consistent structure:\n\n- **Left column** – The field label (`ocat-label`)\n- **Right column** – The input, validation message, and hint (`ocat-end`)\n\n### Core Classes\n\n| Class | Element | Purpose |\n|-------|---------|---------|\n| `ocat-wrapper` | `<div>` | Outer container for a form field row (replaces `mb-3`/`form-group`) |\n| `ocat-label` | `<label>` | Styles the field label in the left column (replaces `form-label`) |\n| `ocat-label-required` | `<label>` | Add alongside `ocat-label` to mark a field as required |\n| `ocat-end` | `<div>` | Wraps the input, validation, and hint in the right column |\n| `ocat-end-offset` | `<div>` | Right-column content with no left label (e.g., checkboxes) |\n| `ocat-limited-wrapper` | `<div>` | Outer row for limited-width controls such as short text, numbers, selects, or small settings inputs |\n| `ocat-limited` | `<div>` | Constrains the control width inside `ocat-limited-wrapper` |\n\n## Choosing the Right Wrapper\n\nUse this decision guide when creating or updating admin edit views:\n\n| Scenario | Preferred Pattern |\n|----------|-------------------|\n| Standard full-width field row | `ocat-wrapper` + `ocat-label` + `ocat-end` |\n| Standalone checkbox/toggle row with no left label | `ocat-wrapper` + `ocat-end-offset` |\n| Standalone alert, notice, legend, or section headline in the form flow | `ocat-wrapper` + `ocat-end-offset` |\n| Top-level limited-width field row (small text, number, select, token, path, port, etc.) | `ocat-limited-wrapper` + `ocat-label` + `ocat-limited` |\n| Content part / content field editor that already needs wrapper classes like `field-wrapper-*` or `content-part-wrapper-*` | Keep the outer `ocat-wrapper`, then place `ocat-limited-wrapper` inside `ocat-end` |\n\n### Wrapper Selection Rules\n\n1. Use `ocat-wrapper` for the default admin edit-view row structure.\n2. Use `ocat-limited-wrapper` when the field should stay visually narrow instead of stretching the full editor width.\n3. If the outer row also needs Orchard-specific wrapper classes such as `field-wrapper-*` or `content-part-wrapper-*`, do **not** replace that outer row with `ocat-limited-wrapper`; keep the outer `ocat-wrapper` and nest `ocat-limited-wrapper` inside `ocat-end`.\n4. Do **not** use legacy helpers or Bootstrap-only layout classes like `mb-3`, `form-group`, `form-label`, `@Orchard.GetWrapperClasses()`, or `@Orchard.GetLimitedWidthWrapperClasses()`.\n5. Alerts, notices, `<legend>`, and section headings that are part of the edit form but have no left-column label should use their own `ocat-wrapper` + `ocat-end-offset` row.\n6. A single row should use **either** `ocat-end` **or** `ocat-end-offset`, never both.\n\n### Standard Field Structure\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label Text\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Descriptive hint text.\"]</span>\n    </div>\n</div>\n```\n\n## Field Type Templates\n\n### Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Required Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label ocat-label-required\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Code/Script Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n```\n\n### Textarea\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Content\">\n    <label asp-for=\"Content\" class=\"ocat-label\">@T[\"Content\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Content\" class=\"form-control\" rows=\"5\"></textarea>\n        <span asp-validation-for=\"Content\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Select / Dropdown\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"FieldName\" class=\"form-select\">\n            <option value=\"Option1\">@T[\"Option 1\"]</option>\n            <option value=\"Option2\">@T[\"Option 2\"]</option>\n        </select>\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Checkbox (No Left Label)\n\nUse `ocat-end-offset` when there is no label in the left column (e.g., for checkboxes):\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsEnabled\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsEnabled\" />\n            <label asp-for=\"IsEnabled\" class=\"form-check-label\">@T[\"Enable feature\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to enable this feature.\"]</span>\n    </div>\n</div>\n```\n\n### Headline / Alert Row (No Left Label)\n\nUse this for standalone form guidance, warnings, legends, or section headers:\n\n```html\n<div class=\"ocat-wrapper\">\n    <div class=\"ocat-end-offset\">\n        <h5>@T[\"Section heading\"]</h5>\n        <div class=\"alert alert-warning\" role=\"alert\">\n            @T[\"Important information for editors.\"]\n        </div>\n    </div>\n</div>\n```\n\n### Input Group (with prefix/suffix)\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Url\">\n    <label asp-for=\"Url\" class=\"ocat-label\">@T[\"URL\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"input-group\">\n            <span class=\"input-group-text\">https://</span>\n            <input asp-for=\"Url\" type=\"text\" class=\"form-control\" />\n        </div>\n        <span asp-validation-for=\"Url\"></span>\n        <span class=\"hint\">@T[\"The full URL.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Field Row\n\nUse this when the whole row is a compact admin field, such as a page size, port, callback path, or select list:\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"PageSize\">\n    <label asp-for=\"PageSize\" class=\"ocat-label\">@T[\"Page size\"]</label>\n    <div class=\"ocat-limited\">\n        <input asp-for=\"PageSize\" type=\"number\" class=\"form-control\" />\n        <span asp-validation-for=\"PageSize\"></span>\n        <span class=\"hint\">@T[\"The default page size.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Select Row\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"TimeZone\">\n    <label asp-for=\"TimeZone\" class=\"ocat-label\">@T[\"Default Time Zone\"]</label>\n    <div class=\"ocat-limited\">\n        <select asp-for=\"TimeZone\" class=\"form-select\">\n            <option value=\"\">@T[\"Local to server\"]</option>\n        </select>\n        <span asp-validation-for=\"TimeZone\"></span>\n        <span class=\"hint\">@T[\"Determines the default time zone.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Control Inside a Field Wrapper\n\nUse this when the editor already needs a content field or content part wrapper on the outer row:\n\n```html\n<div class=\"@($\"ocat-wrapper field-wrapper field-wrapper-{Model.PartFieldDefinition.PartDefinition.Name.HtmlClassify()}-{Model.PartFieldDefinition.Name.HtmlClassify()}\")\">\n    <label asp-for=\"Value\" class=\"ocat-label\">@T[\"Value\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"ocat-limited-wrapper\">\n            <div class=\"ocat-limited\">\n                <input asp-for=\"Value\" class=\"form-control\" />\n                <span asp-validation-for=\"Value\"></span>\n            </div>\n        </div>\n        <span class=\"hint\">@T[\"Use a compact editor width while preserving the field wrapper row.\"]</span>\n    </div>\n</div>\n```\n\n### Conditional Fields with JavaScript/Liquid Syntax Selector\n\nFor fields that support both JavaScript and Liquid syntax, use the syntax selector pattern with toggling `d-none`:\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Syntax\">\n    <label asp-for=\"Syntax\" class=\"ocat-label\">@T[\"Syntax\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Syntax\" class=\"form-select\">\n            <option value=\"@nameof(WorkflowScriptSyntax.JavaScript)\">@T[\"JavaScript\"]</option>\n            <option value=\"@nameof(WorkflowScriptSyntax.Liquid)\">@T[\"Liquid\"]</option>\n        </select>\n        <span asp-validation-for=\"Syntax\"></span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.Expression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.JavaScript ? \"\" : \"d-none\")\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.LiquidExpression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.Liquid ? \"\" : \"d-none\")\" asp-validation-class-for=\"LiquidExpression\">\n    <label asp-for=\"LiquidExpression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"LiquidExpression\" class=\"form-control code\" />\n        <span asp-validation-for=\"LiquidExpression\"></span>\n        <span class=\"hint\">@T[\"A Liquid expression. Example: {0}\", \"{{ Workflow.Properties.Value }}\"]</span>\n    </div>\n</div>\n\n<script at=\"Foot\">\n    document.addEventListener('DOMContentLoaded', () => {\n        const syntaxElement = document.getElementById('@Html.IdFor(m => m.Syntax)');\n        const javaScriptGroup = document.getElementById('@($\"{Html.IdFor(m => m.Expression)}_group\")');\n        const liquidGroup = document.getElementById('@($\"{Html.IdFor(m => m.LiquidExpression)}_group\")');\n\n        const toggleEditors = () => {\n            const useLiquid = syntaxElement.value === '@nameof(WorkflowScriptSyntax.Liquid)';\n            javaScriptGroup.classList.toggle('d-none', useLiquid);\n            liquidGroup.classList.toggle('d-none', !useLiquid);\n        };\n\n        syntaxElement.addEventListener('change', toggleEditors);\n        toggleEditors();\n    });\n</script>\n```\n\n## Display-Only Fields (Read-Only)\n\nFor displaying information without a form input:\n\n```html\n<div class=\"ocat-wrapper\">\n    <label class=\"ocat-label\">@T[\"Field Label\"]</label>\n    <div class=\"ocat-end\">\n        <span>@Model.FieldValue</span>\n    </div>\n</div>\n```\n\n## Migration: Old to New Classes\n\nWhen updating existing views, apply these replacements:\n\n| Old Pattern | New Pattern |\n|-------------|-------------|\n| `<div class=\"mb-3\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<div class=\"form-group\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<label ... class=\"form-label\">` | `<label ... class=\"ocat-label\">` |\n| Input/select/textarea directly inside wrapper | Wrap in `<div class=\"ocat-end\">` |\n| Checkbox with label inside wrapper | Wrap in `<div class=\"ocat-end-offset\"><div class=\"form-check\">` |\n| Standalone headline or alert directly in the form flow | Wrap in `<div class=\"ocat-wrapper\"><div class=\"ocat-end-offset\">...</div></div>` |\n| Mixing `ocat-end` and `ocat-end-offset` in one row | Choose one wrapper based on whether there is a left-column label |\n| `@Orchard.GetLimitedWidthWrapperClasses()` | `ocat-limited-wrapper` |\n| `@Orchard.GetLimitedWidthClasses()` | `ocat-limited` |\n| Limited-width content field row using `field-wrapper-*` | Keep outer `ocat-wrapper field-wrapper-*`, nest `ocat-limited-wrapper` inside `ocat-end` |\n\n## Complete Edit View Example\n\n```html\n@model MyModuleViewModel\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Title\">\n    <label asp-for=\"Title\" class=\"ocat-label ocat-label-required\">@T[\"Title\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"Title\" class=\"form-control\" autofocus=\"autofocus\" />\n        <span asp-validation-for=\"Title\"></span>\n        <span class=\"hint\">@T[\"The title of the item.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Description\">\n    <label asp-for=\"Description\" class=\"ocat-label\">@T[\"Description\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Description\" class=\"form-control\" rows=\"4\"></textarea>\n        <span asp-validation-for=\"Description\"></span>\n        <span class=\"hint\">@T[\"A brief description.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Category\">\n    <label asp-for=\"Category\" class=\"ocat-label\">@T[\"Category\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Category\" class=\"form-select\">\n            <option value=\"\">@T[\"-- Select --\"]</option>\n            <option value=\"A\">@T[\"Category A\"]</option>\n            <option value=\"B\">@T[\"Category B\"]</option>\n        </select>\n        <span asp-validation-for=\"Category\"></span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsPublished\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsPublished\" />\n            <label asp-for=\"IsPublished\" class=\"form-check-label\">@T[\"Published\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to publish this item.\"]</span>\n    </div>\n</div>\n```\n\n## References\n\n- `references/ocat-classes.md` - Detailed class reference with CSS context\n- Admin theme source: `src/OrchardCore.Themes/TheAdmin/`\n- Example usage: `src/OrchardCore.Modules/OrchardCore.Workflows/Views/Items/`\n- AGENTS.md (repo root) - Coding conventions and build commands\n","frontmatter":{"name":"orchardcore-admin-edit-views","description":"Creates and updates OrchardCore admin edit views using the ocat-* CSS class conventions. Use when creating or modifying Edit view files (*.Edit.cshtml, *.Fields.Edit.cshtml) to ensure correct layout and styling.","status":"completed"},"isInternal":false,"tokens":3875,"sizeBytes":14985},{"name":"worked-example.md","path":".agents/skills/orchardcore-admin-index-views/references/worked-example.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/references/worked-example.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-admin-index-views/references/worked-example.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/references/worked-example.md)","tokens":300,"sizeBytes":268},{"name":"SKILL.md","path":".agents/skills/orchardcore-admin-index-views/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/SKILL.md","title":"orchardcore-admin-index-views","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-admin-index-views\ndescription: Builds OrchardCore admin index (list) pages with the AdminList shape and the admin-list tag helpers — view-declared columns, row display drivers placing shapes for both the SummaryAdmin (List) and SummaryAdminGrid (Grid) display types, column providers, row actions, toolbar, search, pager and per-list alternates. Use for every new admin page that lists items, when converting a hand-written admin listing, adding or reordering columns of an admin list, extending another module's list, or changing how its rows and actions render.\n---\n\n# OrchardCore Admin Index Views\n\nEvery admin page that lists items (an \"index\" page: content items, users, queries, workflows, ...) renders its\nitems through the shared `AdminList` shape. Never hand-write a `<ul class=\"list-group\">`, a `<table>`, a toolbar,\na select-all checkbox, an item count, an empty state or a pager for an admin listing: the list renders all of\nthem, in the `List` or `Grid` layout the site and the user choose.\n\n## Ownership\n\n| Layer | Responsibility |\n| --- | --- |\n| Controller | Query the raw items and supply an `AdminListContext<TModel>` with the typed `Source`, pager, search, toolbar and other region shapes. Never build the list shape or the row shapes. |\n| View | Declare the list name, the grid columns, the CSS classes, the empty message and other presentation properties with `<admin-list>`, inside the page's form. |\n| Row display driver | Return ordinary `IDisplayResult` shapes, each with a `SummaryAdmin` location (List) and a `SummaryAdminGrid` location (Grid). Set optional row classes/attributes on `context.Shape`. |\n| Column provider | Optional. Extend the columns a view declares, e.g. from another module. |\n| `AdminList` | Resolve the layout at display time, build each row once with the display type of that layout, and render the whole listing. |\n\n`AdminListLayout` has exactly two values: `List` renders each `SummaryAdmin` row shape whole (its row template\ndecides the look); `Grid` renders the `SummaryAdminGrid` row zones directly, one cell per column, each cell\nrendering the zone named after its column. Layouts are not discovered or registered through templates.\nDo not add column or cell drivers, column metadata classes, or build both representations of a row.\n\n## Decide\n\n| Situation | Do this |\n| --- | --- |\n| New admin page listing items | Follow the workflow below. |\n| Rows already built with `BuildDisplayAsync(..., \"SummaryAdmin\")` and a hand-written list | Convert: move the row building out of the controller into `AdminListContext<TModel>.Source`, add the `SummaryAdminGrid` locations, then follow steps 3 to 6. |\n| Rows are hard-coded `<li>` markup in Razor | Write a display driver for the row model first, so a row is a zone-holding shape, then follow the workflow. |\n| Adding a column to another module's list | Step 5, plus a `SummaryAdminGrid` location in your driver. |\n| The page edits a single entity | Not this skill: use `orchardcore-admin-edit-views`. |\n| The page arranges items rather than listing them (a tree, a canvas, a media library, widgets by zone) | Not this skill: such pages keep their own presentation. |\n\n## Workflow\n\n### 1. Write the row display driver\n\nA row is the shape `IDisplayManager<TModel>` builds for the item. Its type is `typeof(TModel).Name` plus the\ndisplay type, e.g. `IndexProfile_SummaryAdmin`, so register the driver against a domain model named exactly what\nthe shape should be called, never a `*ViewModel`. Register it in the `Startup` of the feature that owns the list:\na driver registered by a neighbouring feature leaves every row empty on a site where that feature is off.\n\nGive every shape a location for each display type. The shape types and templates are the same; only the zone\ndiffers, a row zone for `List` and the zone of a column for `Grid`:\n\n```csharp\npublic override Task<IDisplayResult> DisplayAsync(IndexProfile profile, BuildDisplayContext context)\n{\n    // The row's classes and attributes are rendered on its row element, in both layouts.\n    if (context.DisplayType is OrchardCoreConstants.DisplayType.SummaryAdmin or OrchardCoreConstants.DisplayType.SummaryAdminGrid\n        && context.Shape is Shape rowShape)\n    {\n        rowShape.Classes.Add(\"item\");\n        rowShape.Attributes[\"data-filter-value\"] = profile.Name?.ToLowerInvariant() ?? string.Empty;\n    }\n\n    return CombineAsync(\n        View(\"IndexProfile_Checkbox_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Checkbox:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Select:1\"),\n        View(\"IndexProfile_Fields_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Content:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Name:1\"),\n        View(\"IndexProfile_DefaultTags_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Tags:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Source:5\"),\n        View(\"IndexProfile_Buttons_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Actions:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Actions:5\"),\n        View(\"IndexProfile_ActionsMenuItems_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"ActionsMenu:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"ActionsMenu:5\"));\n}\n```\n\n- Every visible piece of a row (checkbox, title, badges, dates, buttons, menu items) is its own shape in a zone.\n  Anything hard-coded in the row template is missing from the grid.\n- A location without a display type, `.Location(\"Tags:5\")`, applies to both display types, so in the grid the\n  shape lands in a `Tags` zone no column renders. Always pair a `SummaryAdmin` location with a `SummaryAdminGrid`\n  one, unless the zone name is also a column name (e.g. `Actions`).\n- Display types do not inherit placement: a shape placed only for `SummaryAdmin`, by a driver or by\n  `placement.json`, does not appear in the grid. A grid-only shape declares only a `SummaryAdminGrid` location.\n- `placement.json` rules filtered by `\"displayType\": \"SummaryAdminGrid\"` move a shape to another column zone\n  (`Source:10`), hide it (`-`), change its template or add wrappers and alternates.\n- Do not use editor `|Column` groupings for cells.\n\n### 2. Write the row template for the List layout\n\n`{Model}.SummaryAdmin.cshtml` renders the zones of the row; the grid does not use it. Every shipped row has the\nsame structure, so the lists look alike:\n\n```cshtml\n<div class=\"row g-0 align-items-center\">\n    <div class=\"col\">\n        <div class=\"title d-flex align-items-center\">\n            @* Checkbox, then <div class=\"summary d-flex flex-column flex-md-row list-item-search-text\"> with Content, Tags, Meta *@\n        </div>\n        @if (Model.Description != null)\n        {\n            @await DisplayAsync(Model.Description)\n        }\n    </div>\n    <div class=\"col-auto d-flex justify-content-end ps-2\">\n        @await DisplayAsync(await Factory.CreateAdminListActionsAsync((IShape)Model))\n    </div>\n</div>\n```\n\n| Zone | Renders as | Never |\n| --- | --- | --- |\n| `Content` | The title, a plain link to the edit page | A heading (`<h5>`) |\n| `Tags`, `Meta` | `<span class=\"badge ta-badge fw-normal\">` per fact, leading icon, `title` tooltip | Plain text or a `<span class=\"hint\">` |\n| `Description` | `<div class=\"admin-list-secondary\">`, its own line under the title | A second full-width row running under the actions |\n| `Actions`, `ActionsMenu` | Through `AdminListActions` (`CreateAdminListActionsAsync`), so the Buttons/Menu setting applies | A hand-written button group, or `ActionsMenu` items wrapped in `<li>` |\n\n### 3. Supply the items from the controller\n\n```csharp\nmodel.List = new AdminListContext<IndexProfile>\n{\n    Source = items,\n    Pager = pagerShape,\n};\n```\n\nThe view model exposes `public AdminListContext List { get; set; }`. `AdminListContext<TModel>` sets `ItemType`\nand exposes a typed `IEnumerable<TModel> Source`; its generic argument can select a registered base/interface\ndisplay manager, even for an empty collection of derived items. Items must be non-null instances of that type.\nThe list invokes the display manager after resolving its layout, so the controller has no row-rendering\ndependencies and never builds rows, row callbacks or prebuilt shapes.\n\nThe default `IAdminListItemDisplayManager<TModel>` adapter calls `IDisplayManager<TModel>`. Register a closed\nadapter for a specialized pipeline, e.g. content items use `IContentItemDisplayManager`.\n\nOther context properties, all optional:\n\n- `Header`: an options editor shape (`Summary` and `Actions` zones), as the content items and users pass.\n- `Toolbar`: a toolbar shape of the page's own. Otherwise the list builds `AdminListToolbar` from the rows, the\n  pager, `BulkActions`, `ShowSelectAll` and `ToolbarActions` (a shape at its end, e.g. filters).\n  `ShowToolbar = false` omits it.\n- `Search`, `Actions`: the search bar (e.g. the `AdminListSearch` shape) and the page buttons, in the action bar.\n- `PageSize`: a separate page size selector; set `ShowPageSizeSelector = false` on the pager when passing it.\n- `Data`: what a column provider may need, e.g. the content types the listing is filtered by.\n- `Layout`: forces a layout, only when the page can be rendered one way, e.g. while its rows are dragged.\n- `ShowLayoutSelector = false`: for a page that renders several lists or places one selector itself, by creating\n  an `AdminListLayoutSelector` shape with the shared `ListName`.\n\n### 4. Render the list in the view\n\nImport the tag helpers once per module, in `Views/_ViewImports.cshtml`:\n\n```cshtml\n@using OrchardCore.Admin\n@addTagHelper *, OrchardCore.Admin.Abstractions\n```\n\nThe view keeps the form, its hidden inputs and the action bar, and renders the list with `<admin-list>`:\n\n```cshtml\n<form asp-action=\"Index\" method=\"post\" class=\"no-multisubmit bulk-select-list\"\n      data-checkbox-name=\"itemIds\" data-bulk-action-name=\"Options.BulkAction\" data-selected-text=\"@T[\"selected\"]\">\n    <input type=\"submit\" name=\"submit.Filter\" id=\"submitFilter\" class=\"visually-hidden\" />\n    <input asp-for=\"Options.BulkAction\" type=\"hidden\" />\n    <input type=\"submit\" name=\"submit.BulkAction\" class=\"visually-hidden\" />\n\n    <admin-list name=\"Indexes\" context=\"Model.List\"\n                bulk-actions=\"Model.Options.BulkActions\"\n                empty-message=\"@T[\"<strong>Nothing here!</strong> There are no indexes at the moment.\"]\">\n        <admin-list-list item-css-class=\"list-group-item\" />\n        <admin-list-grid>\n            <admin-list-column type=\"Select\" />\n            <admin-list-column name=\"Name\" title=\"@T[\"Name\"]\" position=\"20\" />\n            <admin-list-column name=\"Source\" title=\"@T[\"Source\"]\" position=\"30\" width=\"auto\" />\n            <admin-list-column type=\"Actions\" title=\"@T[\"Actions\"]\" />\n        </admin-list-grid>\n    </admin-list>\n</form>\n```\n\n- `name` and `context` are both required. The name identifies the list for column providers, the user's layout\n  choice and the alternates. Use a string literal when only the view uses it; keep a module constant when it is\n  reused, e.g. `ContentsAdminList.Name`, and do not create a holder class for a single use.\n- The tag also takes `show-toolbar`, `show-select-all`, `show-layout-selector`, `layout`, `item-css-class`,\n  `empty-message`, `bulk-actions`, and `rows-*` attributes (or `rows-attributes`) for the rows container, e.g.\n  `rows-id` for a sortable list.\n- `admin-list-list` and `admin-list-grid` configure their layout's `rows-class` and `item-css-class` without\n  forcing it. Each can be declared once. List item classes replace the default; grid item classes are added to\n  the required grid row class. Children emit no HTML.\n- The hidden `submit.Filter` button stays first in the form: Enter in the search box triggers it. The search is an\n  input group ending with a magnifying-glass button, never an icon inside the input.\n- Use the shared `bulk-select-list` and `list-search-filter` components, wired by one small module per page\n  (`Assets/ts/*.ts`), and keep the `select-all`, `items`, `selected-items` and `actions` ids the toolbar renders.\n\n### 5. Declare the grid columns\n\nEach `<admin-list-column>` renders the row zone named after it; `zones=\"A B\"` renders several named zones instead.\nA zone no column renders is omitted from the grid.\n\n| Attribute | Meaning |\n| --- | --- |\n| `type` | `Select` (the `Select` zone, at position `10`) or `Actions` (the `Actions` and `ActionsMenu` zones, at position `end`); omit for a regular column. A preset keeps its defaults unless overridden. |\n| `name` | Required for a regular column, unique ignoring case; also the default zone and the `admin-list-column-{name}` CSS class. |\n| `title` | Localized header text, e.g. `@T[\"Name\"]`; it also labels the cells when a narrow list stacks them. |\n| `position` | Placement syntax (`10`, `25`, `end`); columns are sorted by it. |\n| `width` | Any CSS width, or `auto` to hug the content. No width shares the remaining space. |\n| `alignment`, `no-wrap`, `css-class` | Cell presentation. |\n\nTo change the columns of another module's list, implement `IAdminListColumnsProvider`:\n\n```csharp\npublic sealed class CultureColumnsProvider : IAdminListColumnsProvider\n{\n    private readonly IStringLocalizer S;\n\n    public CultureColumnsProvider(IStringLocalizer<CultureColumnsProvider> stringLocalizer)\n    {\n        S = stringLocalizer;\n    }\n\n    public Task PopulateAsync(AdminListColumnsContext context)\n    {\n        // Between \"Title\" (20) and \"Type\" (30), whatever order the providers run in.\n        context.Columns.Add(new AdminListColumn { Name = \"Culture\", Position = \"25\", Title = S[\"Culture\"], Width = AdminListColumn.AutoWidth });\n\n        return Task.CompletedTask;\n    }\n}\n```\n\n```csharp\nservices.AddAdminListColumnsProvider<CultureColumnsProvider>(ContentsAdminList.Name);\n```\n\n- Registered with a list name, a provider is only created and run for that list (keyed DI with an\n  `AdminListColumnsProviderKey`; names are case-sensitive). Several providers can target a list, and a provider\n  can target several lists. Duplicate registrations are ignored.\n- Without a name, `AddAdminListColumnsProvider<TProvider>()` registers a global provider for every list. Global\n  providers run first, then the providers of the list, each group in feature dependency order. A global provider\n  must tolerate different column sets: check `context.Find(name)` before changing an optional column.\n- `context.Columns.Add(...)`, `context.Find(name)` and `context.Remove(name)` change the columns.\n  `context.Data`, `TryGetData<T>()` and `GetData<T>()` read what the page passed, e.g.\n  `ContentsAdminList.ContentTypesKey` and `ContentsAdminList.StereotypesKey`.\n- A provider never builds cells: the content of its column comes from a display driver placing a shape in the\n  zone of that column for `SummaryAdminGrid`.\n\n### 6. Test and document it\n\n- Add the page's URL to `_lists` in `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs` when the\n  Blog recipe enables its feature (add an optional-list test otherwise). The test renders every list in the grid\n  and fails when a column renders nothing on any row, which catches a shape missing its `SummaryAdminGrid` location.\n- Check the page in both layouts and both actions layouts (Buttons and Menu), with rows and without, and in a\n  narrow window, where the grid stacks its cells.\n- Add the list to the table of lists in `src/docs/reference/modules/Admin/README.md`.\n\n## Regions and properties of the shape\n\nRows and regions inherit the list name and layout automatically, so their alternates work without a page passing\nthem. `Rows` is output built during display, never an input; `DisplayType` is the resolved row display type.\nCustom `AdminListCell` templates receive `Row` and `Column`. The Actions cell renders the row's `Actions` and\n`ActionsMenu` zones through `AdminListActions`; moving an action to another zone removes it from that cell.\n\n## Alternates\n\n| Shape | Alternates, least to most specific |\n| --- | --- |\n| `AdminList` | `AdminList__{Layout}`, `AdminList__{Name}`, `AdminList__{Name}__{Layout}` |\n| `AdminListCell` | `AdminListCell__{Column}`, `AdminListCell__{Name}__{Column}` |\n| `AdminListActions` | `AdminListActions__{Layout}`, `AdminListActions__{Name}`, `AdminListActions__{Name}__{Layout}`, where `{Layout}` is `Buttons` or `Menu` |\n| `AdminListToolbar`, `AdminListSearch` | `__{Layout}`, `__{Name}`, `__{Name}__{Layout}` |\n\n`AdminListActionsLayout` has exactly two values, `Buttons` and `Menu`; configuration and the nullable site\nsettings use the enum. Keep templates and alternates overridable, but do not add option shapes or custom modes.\n\n## Gotchas\n\n1. **Never name the rows `Items`**: `Shape` already exposes `Items`, its child shapes.\n2. **A location without a display type does not reach the grid column** (step 1). An empty grid column is the symptom.\n3. **Per-type row templates only apply to the List layout**: `Content-BlogPost.SummaryAdmin.cshtml` does not\n   affect the grid; customise it with `SummaryAdminGrid` placement or `AdminListCell-{List}-{Column}.cshtml`.\n4. **Never wrap `ActionsMenu` items in `<li>`**: the dropdown sits inside the `<li>` of a List row, and the parser\n   hoists a nested `<li>` out of it, so the menu renders empty and stray links appear under the list.\n5. **No viewport-based classes such as `d-md-none` in list Razor**: the grid stacks with a container query on the\n   list itself (`_admin-list.scss`), because the sidebar makes the viewport a poor proxy for the list width.\n6. **A sortable list drags the rows, never their container**: in the grid the rows container is\n   `display: contents`. Mark it with `rows-*` attributes, and send SortableJS `oldDraggableIndex`/`newDraggableIndex`,\n   which count only the rows.\n7. **Rows are built while the view renders**, from the `AdminList` displaying event: a driver's queries and\n   errors happen during rendering, not in the controller.\n\n## References\n\n- `references/worked-example.md`: the Indexes page, driver, controller and view.\n- `src/docs/reference/modules/Admin/README.md`: the user and developer documentation of admin lists.\n- `src/OrchardCore.Modules/OrchardCore.Admin/Views/AdminList*.cshtml`: the layout, cell, actions and toolbar templates.\n- `src/OrchardCore.Themes/TheAdmin/Assets/scss/components/_admin-list.scss`: the layout CSS and its container queries.\n- `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs`: renders every list in the grid.\n","frontmatter":{"name":"orchardcore-admin-index-views","description":"Builds OrchardCore admin index (list) pages with the AdminList shape and the admin-list tag helpers — view-declared columns, row display drivers placing shapes for both the SummaryAdmin (List) and SummaryAdminGrid (Grid) display types, column providers, row actions, toolbar, search, pager and per-list alternates. Use for every new admin page that lists items, when converting a hand-written admin listing, adding or reordering columns of an admin list, extending another module's list, or changing how its rows and actions render."},"isInternal":false,"tokens":4510,"sizeBytes":18806},{"name":"actions.md","path":".agents/skills/orchardcore-asset-manager/references/actions.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/references/actions.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-asset-manager/references/actions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/references/actions.md)","tokens":300,"sizeBytes":246},{"name":"SKILL.md","path":".agents/skills/orchardcore-asset-manager/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/SKILL.md","title":"orchardcore-asset-manager","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-asset-manager\ndescription: Builds, watches, and manages frontend assets in OrchardCore. Use when modifying SCSS, JS, TS, or Vue files, adding new assets to a module/theme, or troubleshooting build failures. Covers all asset actions (vite, sass, min, copy, parcel, webpack, concat) and the three-tier package structure.\n---\n\n# OrchardCore Asset Manager\n\nHandles all frontend asset compilation for OrchardCore modules and themes. Assets are discovered via `Assets.json` files under `src/{OrchardCore.Modules,OrchardCore.Themes}/*/`.\n\n## Node.js Version\n\nThe repo requires **Node.js 24.x** (pinned in `.node-version`). The build script auto-detects a mismatch and prompts:\n\n```\n1) Continue anyway\n2) Abort\n3) Install via fnm, Node.js 24.x and build\n4) Install via Volta, Node.js 24.x and build\n```\n\nAlways select **option 1** (continue) or **option 3** (install via fnm). The allowed Bash commands for this project pre-approve both:\n\n```bash\necho \"1\" | yarn build          # continue with current Node.js\necho \"3\" | yarn build          # install Node 24 via fnm and build\n```\n\n## Build Commands\n\nAll commands run from the **repository root** (`/home/skrypt/repo/orchardcore`).\n\n```bash\n# Build all assets\necho \"1\" | yarn build\n\n# Build a specific asset by name\necho \"1\" | yarn build -n media-app\n\n# Build multiple assets\necho \"1\" | yarn build -n media-app,media-field\n\n# Build by tag\necho \"1\" | yarn build -t admin\n\n# Watch a specific asset (dev mode, auto-rebuilds on save)\necho \"1\" | yarn watch -n media-app\n\n# Host with bundler dev server (HMR)\necho \"1\" | yarn host -n media-app\n\n# Clean all build output\necho \"1\" | yarn clean\n```\n\nThe `-n` name maps to the `\"name\"` field in `Assets.json`.\n\n## Local Claude Code Setup (One-time, per developer)\n\n`.claude/` is gitignored. To avoid being prompted on every build command, create `.claude/settings.json` locally with these pre-approved commands:\n\n```json\n{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(echo \\\"1\\\" | yarn build*)\",\n      \"Bash(echo \\\"3\\\" | yarn build*)\",\n      \"Bash(fnm exec --using 24.14.1 -- corepack yarn install*)\",\n      \"Bash(yarn check*)\",\n      \"Bash(yarn lint*)\",\n      \"Bash(echo \\\"1\\\" | yarn dry-run*)\"\n    ]\n  }\n}\n```\n\nDo **not** commit this file or add a `.gitignore` exception for it — it is intentionally kept local to avoid silently pre-approving commands on other developers' machines.\n\n## Prerequisites / Installing Dependencies\n\nBefore building, if packages are missing:\n\n```bash\nfnm exec --using 24.14.1 -- corepack yarn install\n```\n\nAfter modifying any `package.json` (root, `.scripts/assets-manager/`, or a module's `Assets/`), re-run install.\n\n## Built Output\n\nBuilt files go to the module/theme's `wwwroot/` folder and **must be committed** to the repo. After any asset change, commit both the source file and the generated `wwwroot/` files.\n\n## Package Structure (Three-Tier)\n\n| Location | Purpose |\n|---|---|\n| Root `package.json` | Workspace orchestration, version resolutions |\n| `.scripts/assets-manager/package.json` | Build toolchain (Vite, Parcel, Sass, etc.) |\n| `src/.../Assets/package.json` | Runtime deps bundled into that module/theme |\n\nTo add a **runtime dependency** to a module:\n```bash\n# From the module's Assets/ folder\nyarn add some-library@1.2.3\n# Then rebuild\necho \"1\" | yarn build -n asset-name\n```\n\nTo add a **build tool** (affects all assets):\n- Edit `.scripts/assets-manager/package.json`\n- Run `fnm exec --using 24.14.1 -- corepack yarn install`\n\n## Assets.json\n\nEach module/theme with assets has an `Assets.json` at its root (e.g. `src/OrchardCore.Modules/OrchardCore.Media/Assets.json`). See `references/actions.md` for all supported actions.\n\nQuick example:\n```json\n[\n  {\n    \"action\": \"vite\",\n    \"name\": \"media-app\",\n    \"source\": \"Assets/media-app/\",\n    \"tags\": [\"admin\", \"js\"]\n  },\n  {\n    \"action\": \"sass\",\n    \"name\": \"media-styles\",\n    \"source\": \"Assets/scss/media.scss\",\n    \"tags\": [\"admin\", \"css\"]\n  }\n]\n```\n\n## Code Quality Commands\n\nThese do **not** go through the Node.js version prompt — run them directly.\n\n```bash\n# TypeScript type-check all Vue/TS files (vue-tsc --noEmit)\nyarn check\n\n# Lint all JS/TS/Vue files (ESLint)\nyarn lint\n\n# Preview which files would be built/copied without writing anything\necho \"1\" | yarn dry-run\necho \"1\" | yarn dry-run -n media-app   # scoped to one asset\n```\n\n- **`yarn check`** — runs `vue-tsc --noEmit`; catches type errors across all Vue/TS source files. Run before committing TS/Vue changes. Does **not** support `-n`; to check a specific module, point it at that module's tsconfig directly: `yarn vue-tsc --noEmit -p src/OrchardCore.Modules/OrchardCore.Media/Assets/media-app/tsconfig.json`\n- **`yarn lint`** — runs ESLint across the repo. Accepts a file or directory to scope it: `yarn lint src/OrchardCore.Modules/OrchardCore.Media/Assets/`. Fix any errors before committing.\n- **`yarn dry-run`** — logs what the build would do (copy targets, output paths) without touching any files. Useful when adding a new `Assets.json` entry to verify paths before the first real build.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `Cannot find package '@tailwindcss/vite'` | Run `fnm exec --using 24.14.1 -- corepack yarn install` |\n| Parcel cache stale after deleting output | Run `echo \"1\" \\| yarn clean` then rebuild |\n| Changes not reflected | Confirm the built `wwwroot/` files changed; rebuild if not |\n| Ran a full-repo build by accident (wrong `-n` value, e.g. package.json name instead of `Assets.json` \"name\") | `git status --short` immediately; `git checkout --` every changed path outside your target module/theme before committing |\n| Edited source but bug still repros in the running app | You forgot to rebuild. `stat -c '%y' <source>.ts` vs `stat -c '%y' wwwroot/Scripts/<bundle>.js` — if the bundle is older, run `yarn build -n <asset-name>` |\n\n## References\n\n- `references/actions.md` — All Assets.json action types with examples\n</content>\n</invoke>","frontmatter":{"name":"orchardcore-asset-manager","description":"Builds, watches, and manages frontend assets in OrchardCore. Use when modifying SCSS, JS, TS, or Vue files, adding new assets to a module/theme, or troubleshooting build failures. Covers all asset actions (vite, sass, min, copy, parcel, webpack, concat) and the three-tier package structure."},"isInternal":false,"tokens":1607,"sizeBytes":5974},{"name":"theming.md","path":".agents/skills/orchardcore-breadcrumbs/references/theming.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/references/theming.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-breadcrumbs/references/theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/references/theming.md)","tokens":300,"sizeBytes":242},{"name":"SKILL.md","path":".agents/skills/orchardcore-breadcrumbs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/SKILL.md","title":"orchardcore-breadcrumbs","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-breadcrumbs\ndescription: Authors explicit breadcrumb titles, inline ancestors, and lightweight IBreadcrumbProvider postprocessors on OrchardCore admin screens and the front end. Use when declaring current-page titles, extending ancestors, resolving a parent entity, documenting hidden-trail processing, or theming breadcrumbs through name-based shape alternates.\n---\n\n# OrchardCore Breadcrumbs\n\nA breadcrumb is a **named trail with an explicit current-page title**. The parent `<breadcrumb>` requires an `IHtmlContent` title and normalizes it to text; every `<breadcrumb-item>` child is an ancestor. For visible trails, the view supplies dynamic ancestor links and localized text, and registered `IBreadcrumbProvider` implementations can postprocess only those ancestors. The helper preserves their final list order and appends the normalized title as the final, unlinked current node. Hidden admin trails use only the explicit title and skip children and providers entirely. On the admin, the trail appears above a heading containing that text; on the front end, no heading is rendered by default.\n\nKeep built-in views inline, with the current page in `title` rather than a child. Use lightweight providers for shared or cross-module ancestor changes, not a separate manager, builder, named-provider base class, or contextual-data contract. Customize the owning view or its partials for the title and screen-specific ancestors, and use shape alternates for presentation. `INavigationProvider` remains the separate extension point for ordinary navigation menus.\n\n**Keep all Show breadcrumb logic in the tag helper.** Views and partials must not read `AdminSettings.ShowBreadcrumb` or gate lookups, child declarations, or breadcrumb markup on it. Declare nodes and resolve needed data normally; the helper alone chooses visibility and the provider/title pipeline.\n\n## Mental model\n\n1. Require a stable `name` and a non-null `Microsoft.AspNetCore.Html.IHtmlContent` title, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text but still leaves a current node in a visible trail.\n2. Check visibility before any child or provider work. A hidden admin trail never calls `GetChildContentAsync`, resolves or enumerates providers, constructs or invokes them, or creates a `BreadcrumbContext` or any breadcrumb items, including the current node.\n3. For a hidden trail, only the explicit title matters. When heading or browser-title output is needed, call `WriteTo(writer, HtmlEncoder.Default)` once and HTML-decode once, then safely encode the text for output. With `heading=\"\"` and `page-title=\"false\"`, validate the non-null title but also skip `WriteTo`.\n4. For a visible trail, normalize the title, collect ancestor children, and await registered providers sequentially in DI registration order on the shared mutable ancestor list. Helper-created provider contexts always have `ShowTrail == true`.\n5. Preserve the providers' final ancestor list order without sorting, then unconditionally append the explicit title node with fixed `Id = \"Title\"`, last/current and never linked. Clearing or reordering ancestors cannot remove or displace it.\n6. Visible trails finalize ancestor links and render shapes. Hidden trails render only the requested explicit heading/browser title, with no URL generation, permission lookup, link authorization, or shapes. The admin setting does not affect front-end trails.\n\n## Naming\n\n- Use stable literal strings directly in `.cshtml`: `name=\"ContentsEdit\"` and ancestor `id=\"Contents\"`.\n- Use `name` for provider targeting, the trail's CSS class, shape alternates, and name-based deduplication, including Dashboard. The current node automatically has `Id = \"Title\"`; its name-specific alternate needs no separate attribute.\n- Reuse a screen's admin list name when it has one. The content items list uses `Contents`.\n- Prefer PascalCase names without separators so shape alternate file names remain readable.\n- Do not introduce breadcrumb constants classes, contextual-data keys, or a `data` attribute.\n- Ancestor IDs and the automatic current-node ID `Title` identify node shape alternates; they are not rendered as HTML `id` attributes.\n\n## Workflow A: add a breadcrumb to a view\n\n### Step 1: Import the tag helpers\n\nReference `OrchardCore.Navigation.Core` and add this to the module's `Views/_ViewImports.cshtml` if it is not already imported:\n\n```razor\n@addTagHelper *, OrchardCore.Navigation.Core\n```\n\nThe always-enabled `OrchardCore.Navigation` module supplies the shapes. Inline authoring needs no custom provider registration.\n\n### Step 2: Declare the explicit title and ancestor children\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nPrefer paired parent tags with inline ancestors. The parent supplies the current page through `title`; the helper appends it with ID `Title`. Do not add a duplicate current-page child.\n\nPass the normal Razor localizer result directly, as with `RenderTitleSegments`: `title=\"@T[\"Edit Record\"]\"` or `title=\"@T[\"Edit {0}\", record.Name]\"`. The helper renders the HTML-aware value when needed, including its formatting arguments. Do not extract `.Value` from `T[...]` or add caller conversions. Keep `@T[\"...\"]` for ancestor child text too.\n\nPlain strings require the existing safe wrapper `new OrchardCore.DisplayManagement.Html.HtmlContentString(value ?? string.Empty)`. For `Microsoft.Extensions.Localization.LocalizedString` and `OrchardCore.Localization.Data.DataLocalizedString` from `IDataLocalizer`, wrap `.Value ?? string.Empty`. Do not pass these non-HTML values directly, extract `T[...].Value`, or use `Html.Raw`: `HtmlContentString` encodes untrusted strings when written.\n\nA self-closing helper still requires both attributes, for example `<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />`. Providers may supply ancestors, but not the current title. With no ancestors, a visible trail still contains the appended current node.\n\n| Parent attribute | Behavior |\n|------------------|----------|\n| `name` | Required stable trail name used for provider targeting, CSS, shape alternates, and name-based ancestor deduplication. |\n| `title` | Required non-null `Microsoft.AspNetCore.Html.IHtmlContent`, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text, not the current node in a visible trail. |\n| `heading` | Defaults to `h1` on admin requests and no heading on the front end. `heading=\"\"` suppresses the heading. |\n| `page-title` | Defaults to `true`; adds the nonempty explicit title as a browser-title segment. |\n| `display-type` | Defaults to `DetailAdmin` on admin requests and `Detail` elsewhere. Supplies shape alternates. |\n\n### Title normalization\n\nWith `@using OrchardCore.DisplayManagement.Html`, prepare the view's values as follows:\n\n| Value in the view | Title expression |\n|-------------------|------------------|\n| HTML-localized label | `title=\"@T[\"Edit {0}\", record.Name]\"` |\n| Plain model string | `title=\"@(new HtmlContentString(Model.DisplayText ?? string.Empty))\"` |\n| `LocalizedString` | `title=\"@(new HtmlContentString(localizedTitle.Value ?? string.Empty))\"` |\n| `DataLocalizedString` | `title=\"@(new HtmlContentString(dataTitle.Value ?? string.Empty))\"` |\n\nUse `title=\"@Microsoft.AspNetCore.Html.HtmlString.Empty\"` for intentionally empty content. A null title is invalid, including on the no-output path.\n\nWhen needed, every title follows the same normalization: render its `IHtmlContent` once with `WriteTo(writer, HtmlEncoder.Default)`, then HTML-decode once into plain text. The helper safely encodes that string for the heading, current node, and browser title. Literal `&amp;` in a string wrapped with `HtmlContentString` stays literal `&amp;`, serialized as `&amp;amp;` in HTML. Do not pre-encode the wrapper's input. `IHtmlContent` does not enable arbitrary title markup: final title output remains text-only.\n\n### Step 3: Remove duplicate title output\n\nRemove the view's old page heading and `RenderTitleSegments` call. The helper supplies both by default on the admin. Keep the helper in the `Title` zone so it works with the admin theme's top-bar title setting.\n\nFor a trail that does not own the heading, use the `Breadcrumbs` zone and `heading=\"\"`. Also set `page-title=\"false\"` if the page registers its browser title separately.\n\n## Child attributes and link behavior\n\n| Attribute | Type | Behavior |\n|-----------|------|----------|\n| `id` | `string` | Stable node identifier for shape alternates. |\n| `action`, `controller`, `area` | `string` | MVC route target. Prefer these over hand-built action URLs. |\n| `route-*` | `string` | Additional action route values, such as `route-id=\"@record.Id\"`. |\n| `route-values` | `IDictionary<string, string>` | Additional action route values supplied as a dictionary. |\n| `url` | `string` | Direct link target; ignored when `action` is set. |\n| `permission` | `string` | Stored as `BreadcrumbItem.PermissionName`, resolved through `IPermissionService` during visible ancestor link processing after providers; unknown names are ignored. |\n| `resource` | `object` | Resource passed to authorization for the permission. |\n| `link-enabled` | `bool` | Defaults to `true`. Use `false` when the view has already determined that the link should be unavailable. |\n\nThe inner content is localized ancestor text, for example `@T[\"Records\"]`. Child content is HTML-decoded after Razor evaluates it, then safely encoded by the shape template. The parent's `IHtmlContent` title is rendered and decoded once as described above. Templates safely encode normalized title and provider-supplied text; do not emit them with `Html.Raw`.\n\nEvery child and provider item remains an ancestor, including the last item in the list. The helper appends a separate title node after all ancestors; only that node is current and **never linked**. Ancestors with denied permissions, `link-enabled=\"false\"`, or no link target remain visible as text. Do not omit ancestors solely because the user cannot follow their links.\n\nChildren and providers only store named permissions. The helper resolves `PermissionName` after providers and link-eligibility checks, so removed or unlinkable ancestors do not cause a name lookup. The explicit title node never needs link processing. Providers can also add `Permission` objects to `BreadcrumbItem.Permissions`; authorization evaluates applicable permissions against `Resource`. Hidden trails perform neither permission lookup nor authorization.\n\n## Workflow B: resolve a nested editor's parent\n\nUse the real parent's display name and route ID, obtained from the view model when available. For example, a model exposing `Parent` and `Record` can render:\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", Model.Record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n        <breadcrumb-item id=\"Parent\" action=\"Edit\" controller=\"Record\" area=\"My.Module\"\n                         route-id=\"@Model.Parent.Id\"\n                         permission=\"@MyPermissions.ManageRecords.Name\"\n                         resource=\"@Model.Parent\">@T[\"Edit {0}\", Model.Parent.Name]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nIf only the parent ID is available, inject an existing domain service into Razor, not a breadcrumb-specific service. A model with `ParentContentItemId`, for example, can use:\n\n```razor\n@inject OrchardCore.ContentManagement.IContentManager ContentManager\n@{\n    var parent = await ContentManager.GetAsync(Model.ParentContentItemId);\n}\n```\n\nUse the loaded parent's ID and display text in ancestor child items. Follow the screen's existing missing-parent handling and error reporting. Do not guard parent lookups on the breadcrumb visibility setting. Hidden trails always skip child content, but parent attribute expressions and Razor code or model lookups outside that child content still execute.\n\n## Workflow C: postprocess a trail with a provider\n\nImplement `OrchardCore.Navigation.IBreadcrumbProvider`:\n\n```csharp\npublic interface IBreadcrumbProvider\n{\n    ValueTask BuildBreadcrumbAsync(BreadcrumbContext context);\n}\n```\n\n`BreadcrumbContext` is also in `OrchardCore.Navigation`. Its public constructor is `BreadcrumbContext(string name, string title, List<BreadcrumbItem> items, ViewContext viewContext, bool showTrail)`, with `ViewContext` from `Microsoft.AspNetCore.Mvc.Rendering`. The helper passes the normalized plain-string title, not the original `IHtmlContent`. It exposes read-only properties:\n\n| Property | Type | Use |\n|----------|------|-----|\n| `Name` | `string` | Filter the trail's literal name explicitly. |\n| `Title` | `string` | Normalized plain-text explicit title, which providers cannot replace. |\n| `Items` | `List<BreadcrumbItem>` | Shared mutable ancestor list. Add, insert, remove, replace, reorder, or edit ancestors; do not replace the read-only list reference. The final list order is preserved. The explicit current node is not in the list. |\n| `ViewContext` | `Microsoft.AspNetCore.Mvc.Rendering.ViewContext` | Read request context, such as `HttpContext`, when filtering by admin/front-end request. |\n| `ShowTrail` | `bool` | Whether the trail is visible. Helper-created provider contexts always have `ShowTrail == true`; hidden trails create no context and invoke no providers. |\n\nProviders see all visible trails unless they explicitly filter the context. This example targets names starting with `Example`, excluding the `Examples` list itself by name rather than localized title text:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Localization;\nusing OrchardCore.Navigation;\n\npublic sealed class ExampleBreadcrumbProvider : IBreadcrumbProvider\n{\n    private readonly IStringLocalizer S;\n\n    public ExampleBreadcrumbProvider(IStringLocalizer<ExampleBreadcrumbProvider> localizer)\n    {\n        S = localizer;\n    }\n\n    public ValueTask BuildBreadcrumbAsync(BreadcrumbContext context)\n    {\n        if (!context.Name.StartsWith(\"Example\", StringComparison.Ordinal) || context.Name == \"Examples\" ||\n            context.Items.Any(item => item.Id == \"Examples\"))\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        context.Items.Insert(0, new BreadcrumbItem\n        {\n            Id = \"Examples\",\n            Text = S[\"Examples\"].Value,\n            Url = \"~/examples\",\n        });\n\n        return ValueTask.CompletedTask;\n    }\n}\n```\n\nRegister the provider in `Startup.ConfigureServices`:\n\n```csharp\nservices.AddBreadcrumbProvider<ExampleBreadcrumbProvider>();\n```\n\nRegistration is scoped and uses `TryAddEnumerable`. For visible trails, providers run in DI registration order after collecting ancestor children, before appending the explicit current node and finalizing `IsCurrent` and `Href`. Later providers observe earlier ancestor mutations, and the helper preserves the final list order.\n\nUse `context.Name` to target a trail, or `context.ViewContext` to target a request. An admin-only provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)`. Set `Text` to a plain string, not encoded HTML; use `Url` or `RouteValues`, `PermissionName` or the `Permissions` list, `Resource`, and `LinkEnabled` to describe links. Do not depend on incoming `IsCurrent` or `Href`, which the helper computes afterward.\n\nThe example also supplies an ancestor to a trail with no inline children. The view must still provide its title:\n\n```razor\n<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />\n```\n\nProviders cannot remove or replace the explicit title: `Title` is read-only, and `Items` contains only ancestors. The helper appends the title node with `Id = \"Title\"` after all providers have run, even if a provider cleared or reordered every ancestor.\n\nHidden admin trails bypass providers completely: the helper does not resolve, enumerate, construct, or invoke `IBreadcrumbProvider` implementations. It creates no `BreadcrumbContext`; the retained `ShowTrail` property and constructor parameter are always true in helper-created provider contexts. Only the explicit title supplies hidden heading/browser-title output.\n\n## Dashboard ancestor\n\nThe Admin Dashboard feature registers `OrchardCore.AdminDashboard.Services.DashboardBreadcrumbProvider` with `AddBreadcrumbProvider<DashboardBreadcrumbProvider>()`.\n\nThe provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)` and leaves front-end trails unchanged. It skips insertion if `context.Name == \"Dashboard\"` or an ancestor already has ID `Dashboard`; it does not compare localized title text. Otherwise it inserts a localized Dashboard ancestor at index `0`, with ID `Dashboard` and `Url = \"~/\" + adminOptions.AdminUrlPrefix`. Later providers can move it just like any other ancestor.\n\nIts `Permissions` list contains the static `OrchardCore.AdminDashboard.Permissions.AccessAdminDashboard` permission object, so no named-permission lookup is required. Denied access makes a visible Dashboard ancestor plain text. The Dashboard page uses `name=\"Dashboard\"` and `title=\"@T[\"Dashboard\"]\"` so only the explicit current node, with ID `Title`, represents Dashboard.\n\nFor visible admin trails, Dashboard can be added when there are no other ancestors, but the explicit current title is still appended afterward. Hidden trails skip this provider entirely; it never affects title text. Disabling the feature removes this provider contribution; there is no Dashboard option on the tag helper.\n\n## List order\n\nAncestor order comes only from the list. Inline children are collected in declaration order; providers can use `Add`, `Insert`, `RemoveAt`, or other list operations to change it. The helper preserves the final list order without sorting.\n\n`BreadcrumbItem` has no `Position` property and does not implement `IPositioned`. The child helper has no `position` attribute. List changes cannot displace the current page: the helper appends the explicit title after every ancestor.\n\n## Disabled admin trails\n\n**Show breadcrumb** under **Configuration > Settings > Admin** (`AdminSettings.ShowBreadcrumb`, default on) controls admin trail visibility, not front-end trails.\n\nWhen disabled, the helper **skips all providers and breadcrumb items**, regardless of provider registration. It never calls `GetChildContentAsync`, never resolves, enumerates, constructs, or invokes providers (including Dashboard), and creates no `BreadcrumbContext` or items, including the title node. Child text and links are not evaluated.\n\nOnly the explicit title supplies heading/browser-title text. When needed, it is formatted once through `WriteTo(writer, HtmlEncoder.Default)` and HTML-decoded once. The helper safely encodes the normalized string, renders the heading directly with `oc-breadcrumb-title`, and registers the browser title when `page-title` is true. It does not generate URLs, resolve permission names, authorize links, or create shapes. Breadcrumb templates and alternates do not run.\n\nWhen the trail is hidden and both `heading=\"\"` and `page-title=\"false\"` are set, the helper validates the non-null title but also skips `WriteTo`. Parent attribute expressions and Razor code or model lookups outside child content still run: `T[...]` results and `HtmlContentString` wrappers are created before the helper, even when it skips `WriteTo`. All child-only work is skipped for every hidden trail; do not add visibility guards to views. For a visible trail, these attributes suppress only heading/browser-title output, not ancestors, providers, or the explicit current node.\n\n## Theming and low-level shapes\n\nVisible trails use the existing `Breadcrumb` and `BreadcrumbItem` shapes and their name, ID, and display-type alternates:\n\n- Whole trail: `Breadcrumb.cshtml`, `Breadcrumb-ContentsEdit.cshtml`, `Breadcrumb.DetailAdmin.cshtml`, `Breadcrumb-ContentsEdit.DetailAdmin.cshtml`.\n- One node: `BreadcrumbItem.cshtml`, `BreadcrumbItem-Dashboard.cshtml`, `BreadcrumbItem-ContentsEdit-Title.DetailAdmin.cshtml`.\n\n`Breadcrumb.cshtml` owns both the visible trail and heading. Override it to rearrange them. Keep text safely encoded. See `references/theming.md` for alternate precedence, encoding, and the distinction between hidden admin trails and shape-level `ShowTrail`.\n\nThe helper assigns the current node `Id = \"Title\"`. The existing alternate factory provides `BreadcrumbItem-Title.cshtml`, `BreadcrumbItem-[Name]-Title.cshtml`, `BreadcrumbItem-Title.[DisplayType].cshtml`, and `BreadcrumbItem-[Name]-Title.[DisplayType].cshtml`; no extra attribute or shape type is needed. Ancestor IDs still come from child `id` or provider-supplied `Id`.\n\n`IShapeFactory.BreadcrumbAsync` is a low-level presentation API for prebuilt `BreadcrumbItem` objects. Its caller supplies the complete ordered sequence, including the terminal current node, with authorized `Href` values and `IsCurrent` already resolved. It does not append a title node, invoke providers, build trails from names, load parents, add Dashboard, or register the browser title. It is not the recommended authoring path for a view.\n\n## Gotchas\n\n- **Missing title is invalid.** Every helper, including a self-closing one, needs a non-null `IHtmlContent` title, even on the no-output path. `HtmlString.Empty` is valid and still leaves a current node in a visible trail.\n- **Wrap non-HTML title values safely.** Pass `@T[...]` directly, never its `.Value`. Use `new HtmlContentString(value ?? string.Empty)` for strings and `new HtmlContentString(localizedTitle.Value ?? string.Empty)` for non-HTML localizer results; never `Html.Raw`.\n- **HTML-aware input is still text-only output.** Do not render normalized title text as raw HTML or decode it again.\n- **Children are ancestors only.** Declare the current page in parent `title`; the helper appends it with ID `Title`, so do not duplicate it with a child.\n- **Providers cannot change the current title.** Clearing or reordering ancestors never removes or replaces the appended current node.\n- **Hidden means title-only.** The helper skips all provider resolution and execution, child evaluation, contexts, and items. Provider contexts created for visible trails always have `ShowTrail == true`.\n- **No visibility guards in views.** Do not read `AdminSettings.ShowBreadcrumb` or wrap lookups or markup in setting-dependent branches; visibility belongs to the helper.\n- **Named permissions are deferred.** Do not look them up while collecting or postprocessing ancestors; removed or unlinkable ancestors do not need that work.\n- **Second heading.** Remove the old title output when the helper owns the title.\n- **Wrong dictionary type.** `route-values` accepts `IDictionary<string, string>`, not an anonymous object or `RouteValueDictionary`.\n- **Link checks are not endpoint security.** Keep authorization on the target action; `permission` and `link-enabled` control only the rendered link.\n- **Renamed identifiers affect themes and providers.** Keep trail names and ancestor IDs stable. The automatic current-node ID is always `Title`; screen-specific title alternates use the trail name.\n- **Hidden trails bypass templates.** A breadcrumb shape override cannot change the helper's directly rendered disabled-state heading.\n\n## References\n\n- `references/theming.md` - alternate tables, display types, front-end usage, and heading behavior.\n- `src/OrchardCore/OrchardCore.Navigation.Core/` - `BreadcrumbTagHelper`, `BreadcrumbItemTagHelper`, `IBreadcrumbProvider`, `BreadcrumbContext`, `BreadcrumbItem`, and `ShapeFactoryExtensions`.\n- `src/OrchardCore.Modules/OrchardCore.AdminDashboard/Services/DashboardBreadcrumbProvider.cs` - feature-registered admin ancestor provider.\n- `src/OrchardCore.Modules/OrchardCore.Navigation/` - `BreadcrumbShapes`, `BreadcrumbAlternatesFactory`, and the breadcrumb views.\n- `src/docs/reference/modules/Navigation/README.md` - canonical breadcrumb reference.\n- `AGENTS.md` - project conventions.\n","frontmatter":{"name":"orchardcore-breadcrumbs","description":"Authors explicit breadcrumb titles, inline ancestors, and lightweight IBreadcrumbProvider postprocessors on OrchardCore admin screens and the front end. Use when declaring current-page titles, extending ancestors, resolving a parent entity, documenting hidden-trail processing, or theming breadcrumbs through name-based shape alternates."},"isInternal":false,"tokens":5101,"sizeBytes":24355},{"name":"patterns.md","path":".agents/skills/orchardcore-data-migration/references/patterns.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/patterns.md)","tokens":300,"sizeBytes":250},{"name":"schema-builder.md","path":".agents/skills/orchardcore-data-migration/references/schema-builder.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/schema-builder.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/schema-builder.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/schema-builder.md)","tokens":300,"sizeBytes":262},{"name":"SKILL.md","path":".agents/skills/orchardcore-data-migration/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/SKILL.md","title":"orchardcore-data-migration","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-data-migration\ndescription: Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema.\n---\n\n# OrchardCore Data Migration\n\nThis skill guides you through writing OrchardCore data migrations following project conventions.\n\nA data migration is a class that inherits `DataMigration`. OrchardCore discovers its methods by reflection and runs them **sequentially**, per tenant, on application start. The chain starts at `CreateAsync()` and continues through `UpdateFrom1Async()`, `UpdateFrom2Async()`, … Each method returns the schema version number the **next** method must match.\n\n## When to use a migration\n\n- Define or alter content types / parts / fields (`IContentDefinitionManager`).\n- Create or alter SQL index tables (`SchemaBuilder`).\n- Patch existing content item data (query + `ISession.SaveAsync`).\n- Run a setup recipe at install/upgrade (`IRecipeMigrator`).\n- Seed roles, settings, or other documents.\n\n## The version-chain rule (read first)\n\nThe migration runner stores the **last returned number** per migration class per tenant. It then calls the method whose name matches: returned `4` → runs `UpdateFrom4Async()`. The method's return value is the new stored version.\n\n| Rule | Why |\n|------|-----|\n| `CreateAsync` runs only on a **fresh** install. | Existing tenants already passed it. |\n| `CreateAsync` returns the **current latest** version, skipping all `UpdateFromX`. | New sites get the final schema directly. |\n| `UpdateFromXAsync` runs only when stored version == `X`. | Sequential upgrade path. |\n| **Never renumber** an already-shipped method. | Other tenants store the old number; renumbering breaks their upgrade. |\n| Each `UpdateFromX` returns `X+1` (usually). | Skipping is allowed when a later step subsumes an earlier one (return a higher number to jump). |\n\n## Migration Workflow\n\n### Step 1: Create the Migrations class\n\nPlace `Migrations.cs` in the module root (or a `Migrations/` subfolder for multiple classes, e.g. `Migrations/PermissionMigrations.cs`).\n\n```csharp\nusing OrchardCore.ContentManagement.Metadata;\nusing OrchardCore.Data.Migration;\n\nnamespace YourModule;\n\npublic sealed class Migrations : DataMigration\n{\n    private readonly IContentDefinitionManager _contentDefinitionManager;\n\n    public Migrations(IContentDefinitionManager contentDefinitionManager)\n    {\n        _contentDefinitionManager = contentDefinitionManager;\n    }\n\n    public async Task<int> CreateAsync()\n    {\n        // ... define schema ...\n\n        // Return the latest version so new installs skip every UpdateFromX.\n        return 1;\n    }\n}\n```\n\n`SchemaBuilder` is injected by the base class — do **not** add it as a constructor parameter.\n\n### Step 2: Register the migration\n\nIn the module `Startup.cs`:\n\n```csharp\nservices.AddDataMigration<Migrations>();\n```\n\nRegister each migration class separately if the module has more than one.\n\n### Step 3: Write the initial `CreateAsync`\n\nReturn the highest version your module currently ships. Example (content part):\n\n```csharp\npublic async Task<int> CreateAsync()\n{\n    await _contentDefinitionManager.AlterPartDefinitionAsync(\"TitlePart\", builder => builder\n        .Attachable()\n        .WithDescription(\"Provides a Title for your content item.\"));\n\n    // Shortcut other migration steps on new content definition schemas.\n    return 2;\n}\n```\n\n### Step 4: Add `UpdateFromX` for each shipped change\n\nWhen you change the schema in a release, **append** a new `UpdateFromXAsync` — never edit a prior one. Bump the number `CreateAsync` returns to match.\n\n```csharp\n// This code can be removed in a later version.\npublic async Task<int> UpdateFrom1Async()\n{\n    await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table\n        .AddColumn<string>(\"BigUrl\", column => column.Nullable().Unlimited()));\n\n    return 2;\n}\n```\n\n### Step 5: Test the migration\n\n1. **Fresh install** — run on a clean tenant; only `CreateAsync` executes, schema lands at the latest version.\n2. **Upgrade** — start from a DB at the previous version; confirm each `UpdateFromX` runs in order.\n3. Test on every targeted provider (SQLite, SQL Server, MySQL, PostgreSQL). SQLite cannot drop columns — guard with `try/catch` (see `references/schema-builder.md`).\n\n## Method signatures\n\n| Method | Forms | Runs when |\n|--------|-------|-----------|\n| Create | `int Create()` · `Task<int> CreateAsync()` | fresh install |\n| Update | `int UpdateFromX()` · `Task<int> UpdateFromXAsync()` | stored version == X |\n| Uninstall | `void Uninstall()` · `Task UninstallAsync()` | feature uninstalled |\n\nStatic or instance, sync or async — all supported. Async (`...Async`) is the convention for new code.\n\n## Quick Reference\n\n### Common operations\n\n| Goal | API |\n|------|-----|\n| Define/alter content type | `_contentDefinitionManager.AlterTypeDefinitionAsync(...)` |\n| Define/alter part | `_contentDefinitionManager.AlterPartDefinitionAsync(...)` |\n| Remove part / type | `RemovePart(...)`, `DeletePartDefinitionAsync(...)` |\n| Create SQL index table | `SchemaBuilder.CreateMapIndexTableAsync<TIndex>(...)` |\n| Add/drop column or index | `SchemaBuilder.AlterIndexTableAsync<TIndex>(...)` |\n| Run a recipe | `_recipeMigrator.ExecuteAsync(\"init.recipe.json\", this)` |\n| Patch content items | `ISession.Query<ContentItem, ContentItemIndex>(...)` + `SaveAsync` |\n\n### Type definition builder methods\n\n| Method | Effect |\n|--------|--------|\n| `.Creatable()` | Appears in the New menu |\n| `.Listable()` | Shows in content list |\n| `.Draftable()` | Supports drafts |\n| `.Versionable()` | Keeps version history |\n| `.Securable()` | Per-type permissions |\n| `.WithPart(\"PartName\")` | Attaches a part |\n\n## Gotchas\n\n- Always set column **length** on string index columns (`.WithLength(n)`) or `.Unlimited()` — unlengthed columns fail on some providers.\n- `CreateAsync` should return the latest version, not `1`, once `UpdateFromX` methods exist.\n- Patch loops must page by `DocumentId` and call `FlushAsync()` periodically (see `references/patterns.md`).\n- Mark obsolete `UpdateFromX` methods with `// This code can be removed in a later version.` but keep them until you drop support for that upgrade path.\n\n## References\n\n- `references/patterns.md` — content definition changes, content-item data patching, recipe migrations\n- `references/schema-builder.md` — index tables, columns, indexes, provider quirks\n- `src/docs/reference/modules/Migrations/README.md` (repo) — official reference\n- `AGENTS.md` (repo root) — build commands\n","frontmatter":{"name":"orchardcore-data-migration","description":"Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema."},"isInternal":false,"tokens":1543,"sizeBytes":6731},{"name":"drivers.md","path":".agents/skills/orchardcore-display-management/references/drivers.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/drivers.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/drivers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/drivers.md)","tokens":300,"sizeBytes":256},{"name":"placement.md","path":".agents/skills/orchardcore-display-management/references/placement.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/placement.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/placement.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/placement.md)","tokens":300,"sizeBytes":260},{"name":"SKILL.md","path":".agents/skills/orchardcore-display-management/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/SKILL.md","title":"orchardcore-display-management","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-display-management\ndescription: Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or write a display driver.\n---\n\n# OrchardCore Display Management\n\nThis skill guides you through OrchardCore's display system following project conventions.\n\nOrchardCore renders content by building **shapes** (dynamic view models) from drivers, then placing each shape into a **zone** of the layout. **Drivers** decide *what* shapes exist and their default location; **`placement.json`** overrides *where* they go, *whether* they show, and *which* template renders them.\n\n## Mental model\n\n```\nContentItem ──drivers──▶ shapes ──placement.json──▶ zones ──templates──▶ HTML\n```\n\n- **Shape** — a dynamic object with a `ShapeType` and `Metadata`. The shape type (e.g. `TextField`, `Parts_Contents_Publish`, `ContentPart`) selects the template and the placement rule.\n- **Driver** — `DisplayDriver` / `ContentPartDisplayDriver` / `ContentFieldDisplayDriver`. Returns `IDisplayResult`s from `Display`, `Edit`, `UpdateAsync`, each with a default `.Location(...)`.\n- **Placement** — `placement.json` at the **root** of a module or theme. Filters shapes and rewrites their location/visibility/template.\n- **Zone** — named region of the layout (`Content`, `Header`, `Footer`, `Actions`, …) or an editor group (tab/card/column).\n\n## Decide: driver vs placement\n\n| Want to… | Use |\n|----------|-----|\n| Reposition / hide a shape someone else built | `placement.json` |\n| Move a shape to a layout zone | `placement.json` (`place` starts with `/`) | \n| Swap the template or add alternates | `placement.json` (`shape` / `alternates`) |\n| Lay out the admin editor (tabs/cards/columns) | `placement.json` groupings |\n| Produce a *new* shape from your part/field | display driver |\n| Read/write the editor model | display driver (`Edit` / `UpdateAsync`) |\n\nPrefer `placement.json` for layout — no rebuild needed, theme can override.\n\n## Workflow A: place a shape\n\n### Step 1: Find the shape type and differentiator\n\nThe placement property name is the **shape type**, not the part name. A part **without** its own driver renders as `ContentPart` (differentiator = part name). A field renders as its field type (`TextField`) with differentiator `{PartName}-{FieldName}`.\n\nSee `references/placement.md` for the full differentiator table.\n\n### Step 2: Add a rule to `placement.json`\n\n```json\n{\n  \"TextField\": [\n    {\n      \"differentiator\": \"Article-Subtitle\",\n      \"displayType\": \"Detail\",\n      \"place\": \"Content:2\"\n    }\n  ]\n}\n```\n\n### Step 3: Verify\n\nRun the site, view the content item. Use `?display` debug shapes or the template's shape tracing to confirm the shape type. Wrong type → no rule matches.\n\n## Workflow B: write a display driver\n\n### Step 1: Inherit the right base\n\n| Base class | For |\n|------------|-----|\n| `ContentPartDisplayDriver<TPart>` | a content part |\n| `ContentFieldDisplayDriver<TField>` | a content field |\n| `SectionDisplayDriver<TModel, TSection>` | settings sections |\n| `DisplayDriver<TModel>` | anything else |\n\n### Step 2: Implement Display / Edit / Update\n\n```csharp\npublic sealed class TextFieldDisplayDriver : ContentFieldDisplayDriver<TextField>\n{\n    public override IDisplayResult Display(TextField field, BuildFieldDisplayContext context)\n    {\n        return Initialize<DisplayTextFieldViewModel>(GetDisplayShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        })\n        .Location(OrchardCoreConstants.DisplayType.Detail, \"Content\")\n        .Location(OrchardCoreConstants.DisplayType.Summary, \"Content\");\n    }\n\n    public override IDisplayResult Edit(TextField field, BuildFieldEditorContext context)\n    {\n        return Initialize<EditTextFieldViewModel>(GetEditorShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        });\n    }\n\n    public override async Task<IDisplayResult> UpdateAsync(TextField field, UpdateFieldEditorContext context)\n    {\n        await context.Updater.TryUpdateModelAsync(field, Prefix, f => f.Text);\n        return Edit(field, context);\n    }\n}\n```\n\n### Step 3: Register the driver\n\nIn `Startup.cs`:\n\n```csharp\nservices.AddContentPartDisplayDriver<MyPart, MyPartDisplayDriver>();\n// or for a field:\nservices.AddContentFieldDisplayDriver<TextField, TextFieldDisplayDriver>();\n```\n\nSee `references/drivers.md` for shape-type helpers, the fluent `.Location` API, and registration variants.\n\n## Quick Reference\n\n### Placement rule keys\n\n| Key | Purpose |\n|-----|---------|\n| `place` | `Zone:Position`. `-` hides, `/Zone` targets a layout zone |\n| `displayType` | Match `Detail`, `Summary`, `SummaryAdmin`, `SummaryAdminGrid`, `DetailAdmin`, `Edit` |\n| `differentiator` | Distinguish reused shapes (field/part name) |\n| `contentType` | Match content type / stereotype (`Art*` wildcard, or array) |\n| `contentPart` | Match content items containing a part |\n| `path` | Match request path |\n| `alternates` | Add alternate shape types |\n| `wrappers` | Wrap the shape in other shapes |\n| `shape` | Substitute a different shape type |\n\n### Position values\n\n`before` < `0`/empty < `1` < `1.1` < `2` < `10` < `after`. Numeric, not alphabetic. Dot notation inserts between (`5.1` is between 5 and 6).\n\n### Editor grouping modifiers\n\n| Modifier | Group | Example |\n|----------|-------|---------|\n| `#` | Tab | `Parts:0#Media;1` |\n| `%` | Card | `Parts:0%Details;2` |\n| `\\|` | Column | `Parts:0\\|Left_9;1` (`_9` = width, `;1` = order) |\n\n`;N` after a group name = group order. `_N` after a column name = Bootstrap grid width (12-col).\n\n### Common zones\n\n| Zone | Purpose |\n|------|---------|\n| `Content` | Main content shapes |\n| `Header` / `Footer` | Layout regions |\n| `Actions` | Admin list row buttons (`SummaryAdmin` and `SummaryAdminGrid`) |\n| `Parts` | Editor parts container |\n| `Meta` | Metadata block |\n\n## Gotchas\n\n- Hiding a whole editor row → target `ContentPart_Edit` with differentiator `{ContentType}-{PartName}`, not the inner `XxxPart_Edit`.\n- Shape type ≠ part type. No driver → shape is `ContentPart`. Summary display of a dynamic part → `ContentPart_Summary`.\n- Field display modes are stricter: shape type needs `_Display`, differentiator is `{Part}-{Field}-{FieldType}_Display__{Mode}`.\n- Placement precedence: startup project → active theme → modules (by dependency). A theme can override any module's placement.\n- Admin list rows use `SummaryAdmin` in the List layout and `SummaryAdminGrid` in the Grid layout, where each zone is a column. Display types do not inherit placement, so a rule or a driver location for `SummaryAdmin` does not reach the grid; see `orchardcore-admin-index-views`.\n- Same position value → registration order decides; use distinct positions when order matters.\n\n## References\n\n- `references/placement.md` — placement.json filters, differentiators, layout zones, editor groupings\n- `references/drivers.md` — driver base classes, shape helpers, fluent Location API, registration\n- `src/docs/reference/modules/Placement/README.md` (repo) — official placement reference\n- `src/docs/reference/modules/DisplayManagement/README.md` (repo) — display management reference\n- `src/docs/reference/modules/Templates/README.md` (repo) — shape differentiators and alternates\n- `AGENTS.md` (repo root) — build commands\n","frontmatter":{"name":"orchardcore-display-management","description":"Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or write a display driver."},"isInternal":false,"tokens":1849,"sizeBytes":7796},{"name":"docs-structure.md","path":".agents/skills/orchardcore-docs-writer/references/docs-structure.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-docs-writer/references/docs-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-docs-writer/references/docs-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-docs-writer/references/docs-structure.md)","tokens":300,"sizeBytes":256},{"name":"localization.md","path":".agents/skills/orchardcore-localization/references/localization.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/references/localization.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-localization/references/localization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/references/localization.md)","tokens":300,"sizeBytes":254},{"name":"SKILL.md","path":".agents/skills/orchardcore-localization/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/SKILL.md","title":"orchardcore-localization","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-localization\ndescription: Localizes OrchardCore apps — UI strings (IStringLocalizer/IViewLocalizer S, T, H), LocalizationSource, updating and exporting checked-in POT templates, localization analyzers, PO files and pluralization, content translation (LocalizationPart, culture picker), and supported-culture configuration. Use when adding or changing localizable strings, updating POT files, fixing OCLOC diagnostics, translating strings, adding a culture, or making content multilingual.\n---\n\n# OrchardCore Localization\n\nThis skill guides you through OrchardCore localization following project conventions.\n\nThree layers, often confused:\n\n- **UI/code localization** — translate hard-coded strings in C#/Razor/Liquid via `IStringLocalizer` (`S`), `IViewLocalizer` (`T`), `IHtmlLocalizer` (`H`). Checked-in **POT templates** describe the source strings and are embedded in assemblies; translated **PO files** supply runtime translations.\n- **Content localization** — translate *content items* across cultures via `LocalizationPart` and the `OrchardCore.ContentLocalization` module.\n- **Culture configuration** — which cultures the site supports and how the request culture is chosen.\n\n## Decide which layer\n\n| Goal | Layer |\n|------|-------|\n| Translate a label/message in code or a view | UI localization (`S`/`T`/`H`) + PO file |\n| Pluralize (\"1 item\" / \"{0} items\") | `S.Plural(...)` + PO `msgid_plural` |\n| Add, change, remove, or move a source string | Update the owning project's checked-in POT template (Workflow D) |\n| Export source strings for Crowdin or another translation platform | Export POT templates (Workflow D), then deploy translated PO files |\n| Translate a blog post into French | Content localization (`LocalizationPart`) |\n| Add a supported language / set default | Localization settings (`LocalizationSettings`) |\n| Translate data-driven values (e.g. taxonomy terms) | Data Localization (`DataLocalization` module) |\n\n## Workflow A: localize UI strings\n\n### Step 1: Inject the right localizer\n\n| Localizer | Convention | Where |\n|-----------|-----------|-------|\n| `IStringLocalizer<T>` | field `S` | drivers, services, controllers |\n| `IViewLocalizer` | property `T` | Razor views (already on `RazorPage`) |\n| `IHtmlLocalizer<T>` | field `H` | when the translation contains HTML |\n\n```csharp\npublic sealed class AliasPartDisplayDriver : ContentPartDisplayDriver<AliasPart>\n{\n    internal readonly IStringLocalizer S;\n\n    public AliasPartDisplayDriver(IStringLocalizer<AliasPartDisplayDriver> localizer)\n        => S = localizer;\n}\n```\n\nIn Razor: `@T[\"Welcome to Orchard Core\"]`. In a class: `S[\"A value is required for {0}.\", name]`. Keep keys as compile-time constants and pass values as format arguments, not interpolated keys. In Liquid: `{{ \"Welcome to Orchard Core\" | t }}`.\n\n### Step 2: Pluralize when counting\n\n```csharp\nS.Plural(count, \"{0} book\", \"{0} books\");\n// IHtmlLocalizer: H.Plural(count, \"{0} book\", \"{0} books\")\n```\n\n### Step 3: Reuse shared sources for strings declared without a localizer\n\nStrings declared before a localizer is available, such as deployment step categories and titles, are `LocalizationSource` instances translated at display time with `IStringLocalizerFactory.Localize()`. Don't declare them in the class that uses them:\n\n- Use `CommonLocalizationSources` (`OrchardCore.Abstractions`, namespace `OrchardCore.Localization`) for text that several projects use with the same meaning, such as `CommonLocalizationSources.ContentManagement`, or that is general to every module, such as `CommonLocalizationSources.Settings`. Look for an existing member before adding one. Add a member only when another project uses the text too; a module name or a category used by one project goes in that project's class.\n- Declare the other strings in an `internal static class {Area}LocalizationSources` at the root of the module, for example `LuceneLocalizationSources`, with `LocalizationSource.Create(\"...\", typeof({Area}LocalizationSources))`, which extraction tools can find.\n- Keep permission descriptions in their `*Permissions` class, and strings specific to a view in the view, with `T`.\n\n```csharp\n[JsonIgnore] // Not inherited from the base property; LocalizationSource can't be serialized.\npublic override LocalizationSource Category => CommonLocalizationSources.Search;\n\n[JsonIgnore]\npublic override LocalizationSource Title => LuceneLocalizationSources.LuceneSearchIndexes;\n```\n\nThe PO context is the class that declares the source, for example `msgctxt \"OrchardCore.Localization.CommonLocalizationSources\"`.\n\nResolve a source at display time with `stringLocalizerFactory.Localize(source, args)` or `htmlLocalizerFactory.Localize(source, args)`, using the current tenant's factory. Pass arguments to the factory extension, not to `LocalizationSource.Create`; never cache a translated result in a static field. For plurals, use `stringLocalizerFactory.Plural(count, source, \"{0} items\")` (or the HTML factory equivalent), with a constant plural form. The singular source must have a context; `count` supplies `{0}` automatically.\n\n### Step 4: Provide PO translations\n\nPO files are discovered (in order) from each module's `Localization/`, a global `/Localization`, the tenant's `App_Data/Sites/{tenant}/Localization`, and culture folders. A PO entry:\n\n```po\nmsgctxt \"OrchardCore.Admin.Views.AdminDashboard\"\nmsgid \"Welcome to Orchard Core\"\nmsgstr \"Bienvenue sur Orchard Core\"\n```\n\n`msgctxt` = `{Namespace}.{Class}` (services) or `{Namespace}.{ViewPath}` (views). Plurals:\n\n```po\nmsgctxt \"OrchardCore.AdminMenu.Views.Menu.List\"\nmsgid \"1 item\"\nmsgid_plural \"{0} items\"\nmsgstr[0] \"{0} élément\"\nmsgstr[1] \"{0} éléments\"\n```\n\nSee `references/localization.md` for the full PO discovery order and headers.\n\n### Step 5: Update and commit the source templates\n\nAfter adding, changing, removing, or moving a localized string, update the affected project's POT file using Workflow D below. Context changes and moving shared sources can change translation identities even when the displayed text stays the same.\n\n## Workflow B: localize content\n\n### Step 1: Enable the feature, attach the part\n\nEnable `OrchardCore.ContentLocalization` (Content Localization). Attach `LocalizationPart` to the content type (admin or recipe). It carries:\n\n```csharp\npublic class LocalizationPart : ContentPart, ILocalizable\n{\n    public string LocalizationSet { get; set; } // links all translations\n    public string Culture { get; set; }         // this version's culture\n}\n```\n\nOn create, the part handler generates a unique `LocalizationSet` and sets the default culture automatically.\n\n### Step 2: Translate\n\nFrom the editor's Localization, \"Translate\" creates a sibling content item sharing the same `LocalizationSet` with a different `Culture`. Programmatically:\n\n```csharp\nvar translated = await _contentLocalizationManager.LocalizeAsync(contentItem, \"fr-FR\");\n```\n\n### Step 3: Surface translations / culture picker\n\n`IContentLocalizationManager` fetches across a set:\n\n```csharp\nawait _contentLocalizationManager.GetContentItemAsync(localizationSet, \"fr\");\nawait _contentLocalizationManager.GetItemsForSetAsync(localizationSet);\n```\n\nLiquid: `{{ Model.ContentItem.Content.LocalizationPart.LocalizationSet | localization_set: \"en\" }}`. The Content Culture Picker feature adds a front-end language switcher and a `ContentRequestCultureProvider` that derives the culture from the localized route.\n\n## Workflow C: configure cultures\n\nRecipe `settings` step:\n\n```json\n{\n  \"name\": \"settings\",\n  \"LocalizationSettings\": {\n    \"DefaultCulture\": \"en-US\",\n    \"SupportedCultures\": [ \"en-US\", \"fr-FR\", \"es-ES\" ],\n    \"FallBackToParentCulture\": true\n  }\n}\n```\n\nThe Localization module reads these at startup and calls `app.UseRequestLocalization(...)` with the configured default + supported cultures.\n\n## Workflow D: update and export POT templates\n\n### Step 1: Use the existing build integration\n\nIn this repository, `OrchardCore.Commons.props` already imports the localization targets for framework projects, modules, and themes. Do not add redundant imports or package references. Outside the repository, each project whose strings need extraction must reference `OrchardCore.Localization.Build` with `PrivateAssets=\"all\"` and a version matching Orchard Core.\n\nEach project's template is `<AssemblyName>.pot`, next to its project file. A **normal build only embeds an existing template** as `<AssemblyName>.Localization.pot`; it neither extracts strings nor refreshes a stale template. A project without a template embeds nothing. `GenerateLocalizationCatalog=true` enables this integration, not automatic extraction.\n\n### Step 2: Regenerate from source\n\nRun commands from the repository root. Prefer updating just the affected project:\n\n```powershell\ndotnet msbuild src\\OrchardCore.Modules\\OrchardCore.Admin\\OrchardCore.Admin.csproj -t:UpdateLocalizationCatalog\n```\n\nUse `-restore` with `dotnet msbuild` when dependencies need restoring. The target updates only that project's template, not those of its references. For an application and its project references, or the entire solution:\n\n```powershell\ndotnet build src\\OrchardCore.Cms.Web\\OrchardCore.Cms.Web.csproj -p:UpdateLocalizationCatalogs=true\ndotnet build OrchardCore.slnx -p:UpdateLocalizationCatalogs=true\n```\n\nChoose the appropriate scope, rather than running every command. Use the SDK selected by `global.json`: extraction loads that SDK's Roslyn and Razor compilers. The source-tree worker is built on demand; no separate PoExtractor installation is needed.\n\nUpdates create or rewrite the template, or remove it if no messages remain. Unchanged inputs reuse the cache under `obj\\`; unchanged output is not rewritten. Do not hand-edit generated entries, headers, or references. Templates use deterministic ordering, UTF-8 and LF, no timestamps, and file references without line numbers by default. Multi-targeted projects extract using their first target framework; `DEBUG` and `TRACE` are excluded during extraction.\n\n### Step 3: Review the result and diagnostics\n\n```powershell\ngit status --short -- '*.pot'\ngit --no-pager diff -- '*.pot'\n```\n\nReview new files too (a normal `git diff` does not show untracked templates). Check the expected `msgctxt`, `msgid`, `msgid_plural`, source references, format flags, and format-argument comments. Keep `msgstr` values empty in POT files; translations belong in culture-specific PO files. Commit added, modified, and deleted POT files with their source changes. CI updates the solution's templates and fails if any are uncommitted or out of date.\n\nExtraction covers C#, Razor, literal Liquid `t` inputs, context-bearing `LocalizationSource` declarations, and explicit constant DataAnnotations validation messages. It does not extract JavaScript/TypeScript directly: expose their strings through a C# `IJSLocalizer` implementation. Fix dynamic keys or unresolved contexts at the source instead of inserting guessed POT entries.\n\nIDE/build analyzers report `OCLOC101` (constant source missing a context), `OCLOC102` (different declaring context, informational), `OCLOC103` (nonconstant source value), and `OCLOC104` (nonconstant localizer key or plural forms). The update worker reports `OCLOC001`-`OCLOC009`; see the reference for their meanings. Extraction errors always fail; `-p:LocalizationStrict=true` also fails on extraction warnings.\n\nUse `[SkipLocalizationExtraction]` only on forwarding infrastructure whose keys are declared elsewhere; it excludes marked classes/methods and nested declarations from extraction and analyzer diagnostics, not application calls to those helpers. For an intentionally dynamic application call, locally suppress `OCLOC104` with an explanation of where its keys originate. Do not hide missing application strings with broad suppressions.\n\n### Step 4: Export templates when requested\n\nBuild with an **absolute**, dedicated output directory to collect existing templates without regenerating them:\n\n```powershell\n$output = Join-Path $PWD 'artifacts\\localization'\ndotnet build OrchardCore.slnx \"-p:LocalizationCatalogOutputPath=$output\"\n```\n\nFiles are copied to `<output>\\<TargetFramework>\\<AssemblyName>.pot`. Add `-p:UpdateLocalizationCatalogs=true` only when an update is also intended. Use an empty export directory for an exact current snapshot; export does not remove stale files. Only built projects contribute templates, not prebuilt NuGet dependencies. Upload POT files to the translation platform, preserve contexts/plurals/placeholders, and download translated PO files to a supported runtime localization location. Embedded POT files alone do not change runtime translations.\n\nFor configuration properties, custom Liquid inputs, extraction diagnostics, and library entry points, see `references/localization.md`.\n\n## Quick Reference\n\n### Localizers\n\n| Symbol | Type | Output |\n|--------|------|--------|\n| `S` | `IStringLocalizer<T>` | plain string |\n| `T` | `IViewLocalizer` | encoded view string |\n| `H` | `IHtmlLocalizer<T>` | trusted translation markup; format arguments are HTML-encoded |\n\n### Content localization APIs\n\n| Member | Purpose |\n|--------|---------|\n| `IContentLocalizationManager.LocalizeAsync(item, culture)` | create a translation |\n| `.GetContentItemAsync(set, culture)` | one translation |\n| `.GetItemsForSetAsync(set)` | all translations |\n| `.GetItemsForSetsAsync(sets, culture)` | batch |\n| `ILocalizationService.GetDefaultCultureAsync()` / `GetSupportedCulturesAsync()` | culture config |\n\n### Request culture providers\n\n| Provider | Picks culture from |\n|----------|--------------------|\n| `ContentRequestCultureProvider` | the localized content route (added first) |\n| `AdminCookieCultureProvider` | `admin_culture_*` cookie, admin paths only |\n| cookie / querystring / Accept-Language | ASP.NET Core defaults |\n\n### Liquid filters\n\n`localization_set: \"fr\"` — translated item; `switch_culture_url` — culture-switch URL.\n\n## Gotchas\n\n- `msgctxt` must exactly match the namespace+class (or namespace+view path). Wrong context → string stays untranslated.\n- `IStringLocalizer<T>` context comes from `T`'s type — inject the localizer typed to the class that owns the strings.\n- `LocalizationSet` links translations; never share one set across unrelated items, and don't overwrite it.\n- Add a culture to `SupportedCultures` before content can be translated into it.\n- DataAnnotations need `AddDataAnnotationsPortableObjectLocalization()` (the Localization module registers it).\n- Ordinary builds do not refresh POT templates; always explicitly update and commit them with string/context changes.\n- A gettext source plural has exactly two constant forms; translated PO files can have more forms, selected by the culture's plural rule.\n- Set `GenerateLocalizationCatalog=false` only when intentionally disabling both embedding and updating, not as a fix for extraction errors.\n\n## References\n\n- `references/localization.md` — POT configuration and diagnostics, extraction APIs, PO discovery order, contexts, plural forms, content localization internals, startup wiring\n- `src/docs/reference/modules/Localize/README.md` (repo)\n- `src/docs/reference/modules/ContentLocalization/README.md` (repo)\n- `src/docs/reference/modules/DataLocalization/README.md` (repo)\n- `src/docs/reference/modules/Localize/javascript-localization.md` (repo) — `IJSLocalizer` bridge; no direct JavaScript extraction\n- `AGENTS.md` (repo root) — build commands\n","frontmatter":{"name":"orchardcore-localization","description":"Localizes OrchardCore apps — UI strings (IStringLocalizer/IViewLocalizer S, T, H), LocalizationSource, updating and exporting checked-in POT templates, localization analyzers, PO files and pluralization, content translation (LocalizationPart, culture picker), and supported-culture configuration. Use when adding or changing localizable strings, updating POT files, fixing OCLOC diagnostics, translating strings, adding a culture, or making content multilingual."},"isInternal":false,"tokens":3376,"sizeBytes":15486},{"name":"examples.md","path":".agents/skills/orchardcore-module-creator/references/examples.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/examples.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/examples.md)","tokens":300,"sizeBytes":250},{"name":"module-structure.md","path":".agents/skills/orchardcore-module-creator/references/module-structure.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/module-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/module-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/module-structure.md)","tokens":300,"sizeBytes":266},{"name":"patterns.md","path":".agents/skills/orchardcore-module-creator/references/patterns.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/patterns.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/patterns.md)","tokens":300,"sizeBytes":250},{"name":"SKILL.md","path":".agents/skills/orchardcore-module-creator/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/SKILL.md","title":"orchardcore-module-creator","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-module-creator\ndescription: Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality.\n---\n\n# OrchardCore Module Creator\n\nThis skill guides you through creating new OrchardCore modules following project conventions.\n\n## Prerequisites\n\n- OrchardCore repository at `D:\\orchardcore`\n- .NET SDK 10.0+ installed\n\n## Module Creation Workflow\n\n### Step 1: Determine Module Type\n\n**What kind of module are you creating?**\n\n| Type | Description | Key Components |\n|------|-------------|----------------|\n| **Content Part** | Adds data/behavior to content items | Part, Driver, Views |\n| **Content Field** | Custom field type | Field, Driver, Views |\n| **Settings** | Site-wide configuration | SiteSettings, Driver |\n| **Admin Feature** | Admin pages/tools | Controller, Views, Menu |\n| **API** | REST endpoints | ApiController |\n| **Background Task** | Scheduled jobs | IBackgroundTask |\n\n### Step 2: Create Module Directory\n\n```bash\n# Create module folder\nmkdir src/OrchardCore.Modules/OrchardCore.YourModule\ncd src/OrchardCore.Modules/OrchardCore.YourModule\n```\n\n### Step 3: Create Required Files\n\nEvery module needs these three files:\n\n1. **Manifest.cs** - Module metadata\n2. **Startup.cs** - Service registration  \n3. **OrchardCore.YourModule.csproj** - Project file\n\nSee `references/module-structure.md` for templates.\n\n### Step 4: Add Components Based on Type\n\n**For Content Part modules:**\n```\nModels/YourPart.cs\nViewModels/YourPartViewModel.cs\nDrivers/YourPartDisplayDriver.cs\nViews/YourPart.cshtml\nViews/YourPart_Edit.cshtml\n```\n\n**For Admin modules:**\n```\nControllers/AdminController.cs\nViews/Admin/Index.cshtml\nAdminMenu.cs\nPermissionProvider.cs\n```\n\n**For Data-storing modules:**\n```\nMigrations.cs\nIndexes/YourIndex.cs\n```\n\nSee `references/patterns.md` for code templates.\n\n### Step 5: Register in Startup.cs\n\n```csharp\npublic override void ConfigureServices(IServiceCollection services)\n{\n    // Content part\n    services.AddContentPart<YourPart>()\n        .UseDisplayDriver<YourPartDisplayDriver>();\n    \n    // Services\n    services.AddScoped<IYourService, YourService>();\n    \n    // Migrations (if storing data)\n    services.AddDataMigration<Migrations>();\n    \n    // Permissions (if securing features)\n    services.AddPermissionProvider<PermissionProvider>();\n    \n    // Navigation (if adding admin menu)\n    services.AddNavigationProvider<AdminMenu>();\n}\n```\n\n### Step 6: Build and Test\n\n```bash\n# Build the module\ncd D:\\orchardcore\ndotnet build src/OrchardCore.Modules/OrchardCore.YourModule\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n\n# Enable the feature in Admin → Features\n```\n\n## Quick Reference\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Module folder | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Namespace | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Feature ID | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Content Part | `NamePart` | `RatingPart` |\n| Driver | `NamePartDisplayDriver` | `RatingPartDisplayDriver` |\n| View | `PartName.cshtml` | `RatingPart.cshtml` |\n| Edit View | `PartName_Edit.cshtml` | `RatingPart_Edit.cshtml` |\n\n### Common Dependencies\n\nAdd to `.csproj` as needed:\n\n```xml\n<!-- Core module support -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Module.Targets\\OrchardCore.Module.Targets.csproj\" />\n\n<!-- Content management -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.ContentManagement\\OrchardCore.ContentManagement.csproj\" />\n\n<!-- Admin UI -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Admin\\OrchardCore.Admin.csproj\" />\n```\n\n### Feature Categories\n\nUse in `Manifest.cs`:\n- `Content Management`\n- `Content`\n- `Navigation`\n- `Security`\n- `Infrastructure`\n- `Theming`\n- `Developer`\n\n## References\n\n- `references/module-structure.md` - Directory layout and file templates\n- `references/patterns.md` - Code patterns (parts, drivers, handlers, etc.)\n- `references/examples.md` - Complete module examples\n- `AGENTS.md` (repo root) - Coding conventions and build commands\n","frontmatter":{"name":"orchardcore-module-creator","description":"Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality."},"isInternal":false,"tokens":1046,"sizeBytes":4228},{"name":"SKILL.md","path":".agents/skills/orchardcore-nswag-regenerate/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-nswag-regenerate/SKILL.md","title":"orchardcore-nswag-regenerate","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-nswag-regenerate\ndescription: Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to \"regenerate the NSwag client(s)\", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration.\n---\n\n# OrchardCore NSwag Regenerate\n\nRegenerates the OpenApi module's NSwag-generated C#/TypeScript API clients and verifies the regeneration produces a stable, non-reshuffled diff.\n\n## Preferred: `tools/OpenApiClientGenerator` (automated, no browser/dev-server needed)\n\nA console project that does the whole pipeline headlessly:\n\n```bash\ndotnet run --project tools/OpenApiClientGenerator -c Release\nyarn build   # refreshes the Vue app's bundled JS against the new TS client\n```\n\nIt boots the CMS in-process on an ephemeral port, uses `OrchardCore.AutoSetup` with\n`src/OrchardCore.Cms.Web/Recipes/openapi-generation-setup.recipe.json` (`issetuprecipe: true`)\nto provision the **Default** tenant with the same feature set as `openapi-generation.recipe.json`\nbelow, fetches `swagger.json`, writes a scratch `.nswag` config pointing at that capture, and\nshells out to the `nswag` CLI (same prerequisite as the manual path: `dotnet tool install -g\nNSwag.ConsoleCore`). Source: `tools/OpenApiClientGenerator/Program.cs`.\n\n**Why Default tenant specifically**: `OrchardCore.Tenants` (and `.Distributed`,\n`.FeatureProfiles`) have `DefaultTenantOnly = true` in their `[Feature]` manifest attribute —\nthey can only be enabled on the Default/Host shell, never a secondary tenant. A Blog-recipe\nfunctional-test tenant cannot enable `OrchardCore.Tenants`, which is why this tool targets the\nDefault shell via `AutoSetup` rather than reusing the functional-test harness's tenant pattern.\n\nThis tool only regenerates the clients against whatever API surface the current source produces\n— it does not add a CI drift-check gate (that was scoped out; see `nswag-automation-plan.md` in\ngit stash history for the fuller original proposal, \"Part 1\" of which this tool implements).\n\nThe manual procedure below still applies if you need to regenerate against a real running dev\nserver (e.g. to inspect `swagger.json` interactively) or investigate the tool's own behavior.\n\n## Where things live\n\n- Config: `src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag` — **this file already exists**, do not assume it's missing. It defines both generators:\n  - `openApiToCSharpClient` → outputs `Services/OpenApiClient.cs` (relative to the module folder)\n  - `openApiToTypeScriptClient` → outputs `../../../.scripts/bloom/services/OpenApiClient.ts`\n- Source document: `documentGenerator.fromDocument.url` points at a running instance's Swashbuckle endpoint, `https://localhost:5001/swagger/v1/swagger.json`. NSwag also accepts a **local file path** in this same field instead of a URL — useful for reproducible/offline generation (see below).\n- Recipe: `src/OrchardCore.Modules/OrchardCore.OpenApi/Recipes/openapi-generation.recipe.json` (name `OpenApiGeneration`) enables every feature that exposes an API endpoint: `OrchardCore.Contents`, `OrchardCore.Queries`, `OrchardCore.Tenants`, `OrchardCore.Search.Lucene`, `OrchardCore.Search.Elasticsearch`, `OrchardCore.OpenApi`. It also contains a `Settings` step setting `OpenApiSettings.AllowAnonymousSchemaAccess: true` — anonymous schema access is **disabled by default** (the middleware returns 401 for unauthenticated `swagger.json` fetches), and both NSwag paths below fetch the schema anonymously. The setup recipe used by the generator tool (`openapi-generation-setup.recipe.json`) has the same step. Note the `Settings` step replaces the whole stored `OpenApiSettings` object, wiping any OAuth configuration on the tenant it runs on — fine for throwaway generation tenants, worth knowing on a configured one.\n  - It has `\"issetuprecipe\": false`, so it does **not** appear in the new-tenant/site-setup recipe dropdown. Run it against an already-set-up tenant instead, via **Admin ▸ Configuration ▸ Recipes**, click \"Run\" next to \"OpenApi Generation\", confirm the modal. (`/Admin/Recipes`, `AdminController.Execute` in `OrchardCore.Recipes`.)\n\n## Manual procedure (fallback)\n\n### NSwag CLI\n\nInstalled as a dotnet tool: `~/.dotnet/tools/nswag` (or `dotnet tool install -g NSwag.ConsoleCore` if missing). Run with:\n\n```bash\nnswag run src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag\n```\n\nThis requires a live server at the configured `url`. For a real regeneration that updates the committed files, run the actual local dev server, set it up with the `OpenApiGeneration` recipe, and run the command above unmodified.\n\n## Reproducible / offline generation (for testing determinism, without touching committed files)\n\n1. Fetch `swagger.json` from a running instance into a scratch file.\n2. Copy the `.nswag` config, and in the copy only change `documentGenerator.fromDocument.url` to the scratch file's path, and both generators' `output` to scratch paths. Do this with a small Python/jq one-liner rather than hand-editing — the config is plain JSON.\n3. `nswag run <scratch-config>`.\n\nThis lets you diff two independent generations without ever touching the real generated files or needing HTTPS/dev-cert setup.\n\n## Determinism: why regeneration used to reshuffle unrelated methods\n\nTwo independent root causes were found and fixed (skrypt/openapi branch):\n\n1. **Swashbuckle's operation order wasn't pinned.** Without an explicit sort, `swagger.json` operations come out in whatever order ASP.NET Core's action discovery enumerates them — which depends on assembly/feature load order in OrchardCore's modular architecture, not source order. Fixed in `OrchardCore.OpenApi/Startup.cs`'s `AddSwaggerGen` call:\n   ```csharp\n   c.OrderActionsBy(apiDesc => $\"{apiDesc.RelativePath}_{apiDesc.HttpMethod}\");\n   ```\n   Route path + verb is fixed at compile time and unique per operation, so this is stable regardless of load order.\n\n2. **A duplicate `operationId` across two operations.** `QueryApiController` used to have one action handling both `GET` and `POST` on `api/queries/{name}` under a single `[EndpointName(\"ApiExecuteQuery\")]`. OpenAPI requires `operationId` to be unique per operation — reusing one across two operations forced NSwag to invent a disambiguating suffix internally, and that suffix logic wasn't deterministic across runs (e.g. `ApiExecuteQueryPOSTAsync` vs `ApiExecuteQueryPOSTPOSTAsync`). Fixed by splitting into two actions with distinct names (`ApiExecuteQueryGet` / `ApiExecuteQueryPost`), delegating to a shared private method — the pattern already used by e.g. `ElasticsearchApiController` (`ApiGetElasticsearchContent`/`ApiPostElasticsearchContent`).\n\n**Lesson for any future controller/endpoint added to the OpenApi-exposed surface**: never reuse the same `[EndpointName]`/`operationId` across two different HTTP verbs on the same route. Give each verb its own action and its own name, even if they share implementation via a private helper.\n\n## Verifying stability empirically\n\nBoot two independently-provisioned tenants (each gets its own `ShellContext` and fresh feature/extension discovery — this is what actually varies, not wall-clock time), run the `OpenApiGeneration` recipe on each, fetch `swagger.json` from each, generate against both, and diff. A stable setup produces byte-identical output except for the tenant's own base-URL prefix embedded in the client's default `baseUrl` (an expected, real difference between distinct tenants — not a bug).\n\nThe existing functional test suite (`test/OrchardCore.Tests.Functional/Tests/Cms/OpenApiTests.cs`) already exercises the relevant plumbing (feature enablement, swagger.json access) if you need a template for scripting this via Playwright/`CmsTestBase`. Any throwaway verification test written for this should be deleted afterward — it's not meant to be a permanent part of the suite.\n","frontmatter":{"name":"orchardcore-nswag-regenerate","description":"Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to \"regenerate the NSwag client(s)\", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration."},"isInternal":false,"tokens":1864,"sizeBytes":8052},{"name":"indexing.md","path":".agents/skills/orchardcore-query-indexing/references/indexing.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/references/indexing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-query-indexing/references/indexing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/references/indexing.md)","tokens":300,"sizeBytes":250},{"name":"SKILL.md","path":".agents/skills/orchardcore-query-indexing/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/SKILL.md","title":"orchardcore-query-indexing","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-query-indexing\ndescription: Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps.\n---\n\n# OrchardCore Query & Indexing\n\nThis skill guides you through OrchardCore's query and indexing systems following project conventions.\n\nTwo distinct systems, often confused:\n\n- **Queries** (`OrchardCore.Queries`) — named, stored queries executed on demand (`SQL`, `Lucene`, `Elasticsearch` sources). Return raw rows or content items.\n- **Indexing** (`OrchardCore.Indexing` + providers) — build full-text **index profiles** that mirror content into Lucene/Elasticsearch/Azure AI Search for searching. Driven by **index handlers**.\n\nSeparate again from **YesSql SQL index tables** (`MapIndex` + `SchemaBuilder`) — those are relational indexes for fast DB queries, covered by the `orchardcore-data-migration` skill, not here.\n\n## Decide what you need\n\n| Goal | System |\n|------|--------|\n| Run a stored SQL/Lucene query, expose via GraphQL/API | Queries |\n| Make content searchable (full-text) | Indexing (index profile + provider) |\n| Index a custom part/field's data into search | `IContentPartIndexHandler` / `IContentFieldIndexHandler` |\n| Fast relational lookup by a column | YesSql `MapIndex` → see `orchardcore-data-migration` |\n| Run a search box over an index | Search (`ISearchService`) |\n\n## Workflow A: define and run a query\n\n### Step 1: Pick a source\n\n`Query` has `Name`, `Source` (`Sql`/`Lucene`/`Elasticsearch`), `ReturnContentItems`, and source-specific metadata (e.g. a SQL `Template`).\n\n### Step 2: Create via recipe or manager\n\nRecipe step:\n\n```json\n{\n  \"name\": \"queries\",\n  \"Queries\": [\n    {\n      \"Source\": \"Sql\",\n      \"Name\": \"RecentPosts\",\n      \"Template\": \"select DocumentId from ContentItemIndex where ContentType = 'BlogPost'\",\n      \"ReturnContentItems\": true\n    }\n  ]\n}\n```\n\nProgrammatically:\n\n```csharp\nvar query = await _queryManager.NewAsync(\"Sql\", data);\nawait _queryManager.SaveAsync(query);\n```\n\n### Step 3: Execute\n\n```csharp\nvar query = await _queryManager.GetQueryAsync(\"RecentPosts\");\nvar results = await _queryManager.ExecuteQueryAsync(query, new Dictionary<string, object>\n{\n    [\"maxResults\"] = 10,\n});\nforeach (var item in results.Items) { /* JsonObject or ContentItem */ }\n```\n\nSQL templates are Liquid-rendered (parameters become Liquid vars), parsed by `SqlParser`, then run via Dapper. `ReturnContentItems = true` loads `ContentItem`s by `DocumentId`; otherwise rows are returned as `JsonObject`.\n\n## Workflow B: index content for search\n\n### Step 1: Create an index profile\n\nA profile binds a provider (`Lucene`/`Elasticsearch`/`AzureAISearch`) + a type (`Content`) + which content types to index.\n\n```json\n{\n  \"name\": \"CreateOrUpdateIndexProfile\",\n  \"indexes\": [\n    {\n      \"Name\": \"BlogPostsLucene\",\n      \"IndexName\": \"blogposts\",\n      \"ProviderName\": \"Lucene\",\n      \"Type\": \"Content\",\n      \"Properties\": {\n        \"ContentIndexMetadata\": {\n          \"IndexLatest\": false,\n          \"IndexedContentTypes\": [ \"BlogPost\" ],\n          \"Culture\": \"any\"\n        },\n        \"LuceneIndexMetadata\": { \"AnalyzerName\": \"standard\", \"StoreSourceData\": true }\n      }\n    }\n  ]\n}\n```\n\nContent changes raise indexing tasks; `ContentIndexingService` resolves the content item and calls every part/field index handler to build a `DocumentIndex`, which the provider stores.\n\n### Step 2: Index custom part/field data\n\nFor a custom part, implement `ContentPartIndexHandler<TPart>`:\n\n```csharp\npublic class AliasPartIndexHandler : ContentPartIndexHandler<AliasPart>\n{\n    public override Task BuildIndexAsync(AliasPart part, BuildPartIndexContext context)\n    {\n        var options = DocumentIndexOptions.Keyword | DocumentIndexOptions.Store;\n        foreach (var key in context.Keys)\n        {\n            context.DocumentIndex.Set(key, part.Alias, options);\n        }\n        return Task.CompletedTask;\n    }\n}\n```\n\nFields use `ContentFieldIndexHandler<TField>` with `BuildFieldIndexContext`. `context.Keys` are the index field names to populate; `DocumentIndexOptions` flags control storage:\n\n| Flag | Effect |\n|------|--------|\n| `Store` | keep the value retrievable from the index |\n| `Keyword` | index as a non-analyzed keyword (exact match) |\n| `Sanitize` | strip HTML before indexing |\n| `None` | analyzed full-text (default text) |\n\n### Step 3: Register the handler\n\n```csharp\nservices.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();\n// field:\nservices.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();\n```\n\n### Step 4: Reset / rebuild\n\nRecipe steps `ResetIndex` (re-sync from last position) and `RebuildIndex` (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.\n\n## Quick Reference\n\n### Query APIs\n\n| Type | Member |\n|------|--------|\n| `IQueryManager` | `NewAsync`, `SaveAsync`, `GetQueryAsync`, `ExecuteQueryAsync`, `ListQueriesAsync` |\n| `IQuerySource` | `Name`, `ExecuteQueryAsync(query, parameters)` — implement to add a source |\n| `IQueryResults` | `IEnumerable<object> Items` |\n\n### Indexing APIs\n\n| Type | Member |\n|------|--------|\n| `IIndexProfileManager` | `NewAsync`, `CreateAsync`, `UpdateAsync`, `FindByNameAsync`, `SynchronizeAsync`, `ResetAsync` |\n| `IDocumentIndexHandler` | `BuildIndexAsync`, `DocumentsAddedOrUpdatedAsync`, `DocumentsDeletedAsync` |\n| `IContentPartIndexHandler` / `IContentFieldIndexHandler` | `BuildIndexAsync(...)` to populate `DocumentIndex` |\n| `IIndexManager` (per provider) | `CreateAsync`, `RebuildAsync`, `DeleteAsync`, `ExistsAsync` |\n| `IDocumentIndexManager` | `AddOrUpdateDocumentsAsync`, `DeleteDocumentsAsync` |\n| `DocumentIndex` | `Set(name, value, options)` overloads; `Types` enum |\n\n### Register an indexing source\n\n```csharp\nservices.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(\n    providerName, implementationType);\n// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);\n```\n\n### Search\n\n`ISearchService.SearchAsync(IndexProfile index, string term, int start, int size)` → `SearchResult`. Providers: `OrchardCore.Search.Lucene`, `.Elasticsearch`, `.AzureAI`.\n\n## Gotchas\n\n- \"Index\" is overloaded: full-text **index profiles** (this skill) vs YesSql **MapIndex tables** (`orchardcore-data-migration`). Don't mix them.\n- Query recipe step is `queries`; index profile step is `CreateOrUpdateIndexProfile`. Older `lucene-index` / `ElasticIndexSettings` steps are deprecated — prefer `CreateOrUpdateIndexProfile`.\n- `IndexLatest: true` indexes draft (latest) versions; `false` indexes published. Match to your search UI.\n- SQL query `Template` is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.\n\n## References\n\n- `references/indexing.md` — index profiles, handlers, DocumentIndex, providers, recipe steps\n- `src/docs/reference/modules/Queries/README.md` (repo)\n- `src/docs/reference/modules/Indexing/README.md` (repo)\n- `src/docs/reference/modules/{Lucene,Elasticsearch,SQLIndexing}/README.md` (repo)\n- `AGENTS.md` (repo root) — build commands\n","frontmatter":{"name":"orchardcore-query-indexing","description":"Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps."},"isInternal":false,"tokens":1781,"sizeBytes":7278},{"name":"recipe-structure.md","path":".agents/skills/orchardcore-recipe-creator/references/recipe-structure.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md)","tokens":300,"sizeBytes":266},{"name":"steps.md","path":".agents/skills/orchardcore-recipe-creator/references/steps.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/steps.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/steps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/steps.md)","tokens":300,"sizeBytes":244},{"name":"SKILL.md","path":".agents/skills/orchardcore-recipe-creator/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/SKILL.md","title":"orchardcore-recipe-creator","category":"anthropic-skill","format":"markdown","content":"---\nname: orchardcore-recipe-creator\ndescription: Creates OrchardCore setup recipes (.recipe.json) that provision a tenant on setup — enabling features, setting themes, defining content types, importing content, roles, and settings. Use when the user needs to create a setup recipe, a starter recipe, or automate tenant configuration.\n---\n\n# OrchardCore Setup Recipe Creator\n\nThis skill guides you through creating a **setup recipe** for OrchardCore. A setup recipe is a `.recipe.json` file that runs when a tenant is created, provisioning everything the site needs: enabled features, active themes, content definitions, content items, roles, and settings.\n\n## Prerequisites\n\n- OrchardCore repository (working directory)\n- A module or theme to host the recipe (recipes live in a `Recipes/` folder)\n- Basic knowledge of the features your site needs\n\n## What makes a recipe a *setup* recipe\n\nA setup recipe is a normal recipe with `\"issetuprecipe\": true` in its header. That flag makes it appear in the setup screen's recipe dropdown so a user can pick it when creating a tenant. Without it, the recipe is still executable from the admin (Configuration → Recipes) but won't show during setup.\n\n## Setup Recipe Workflow\n\n### Step 1: Choose the host and create the Recipes folder\n\nRecipes are discovered from a `Recipes/` folder inside any enabled module or theme:\n\n```bash\nmkdir src/OrchardCore.Themes/YourTheme/Recipes\n```\n\nThe file name must end with `.recipe.json`:\n\n```\nsrc/OrchardCore.Themes/YourTheme/Recipes/yoursite.recipe.json\n```\n\n### Step 2: Write the recipe header\n\n```json\n{\n  \"name\": \"YourSite\",\n  \"displayName\": \"Your Site\",\n  \"description\": \"Provisions a Your Site tenant with content and theme.\",\n  \"author\": \"The Orchard Core Team\",\n  \"website\": \"https://orchardcore.net\",\n  \"version\": \"1.0.0\",\n  \"issetuprecipe\": true,\n  \"categories\": [ \"default\" ],\n  \"tags\": [ \"yoursite\" ],\n  \"variables\": {\n    \"homeContentItemId\": \"[js:uuid()]\"\n  },\n  \"steps\": []\n}\n```\n\n`variables` are evaluated on first access and reused across steps — use them for content item ids so steps can reference the same item. Recipe JSON may contain `// comments`.\n\n### Step 3: Add the core steps in order\n\nSteps run **top to bottom**. A typical setup recipe order:\n\n1. `feature` — enable the modules and theme features the site needs.\n2. `themes` — set the active site and admin themes.\n3. `settings` — site-wide settings (home route, culture, time zone).\n4. `Roles` — roles and their permissions.\n5. `ContentDefinition` — content types and parts.\n6. `content` — the actual content items (pages, menus, widgets).\n7. `media`, `queries`, `layers`, `Templates`, `AdminMenu` — as needed.\n\nMinimum viable setup recipe:\n\n```json\n\"steps\": [\n  {\n    \"name\": \"feature\",\n    \"enable\": [\n      \"OrchardCore.Admin\",\n      \"OrchardCore.Contents\",\n      \"OrchardCore.ContentTypes\",\n      \"OrchardCore.Title\",\n      \"OrchardCore.Autoroute\",\n      \"OrchardCore.Alias\",\n      \"OrchardCore.Themes\",\n      \"OrchardCore.HomeRoute\",\n      \"TheTheme\",\n      \"YourTheme\"\n    ]\n  },\n  {\n    \"name\": \"themes\",\n    \"admin\": \"TheAdmin\",\n    \"site\": \"YourTheme\"\n  }\n]\n```\n\n!!! warning\n    Always enable your own theme's feature in the `feature` step. If you set it as the `site` theme without enabling it, the layout won't render after setup.\n\n### Step 4: Reference the full step catalog\n\nFor every built-in step (`feature`, `themes`, `settings`, `ContentDefinition`, `content`, `media`, `Roles`, `layers`, `queries`, `Templates`, `AdminMenu`, `WorkflowType`, `deployment`, `custom-settings`, `recipes`, search indexes), see `references/steps.md`.\n\n### Step 5: Use recipe helpers for dynamic values\n\nValues can embed expressions with `[helper:...]` syntax:\n\n| Helper | Example | Use |\n|--------|---------|-----|\n| `js` | `\"[js:variables('homeContentItemId')]\"` | Reference variables / JS expressions |\n| `uuid` | `\"[js:uuid()]\"` | Generate a new content item id |\n| `file` | `\"[file:text('Snippets/home.liquid')]\"` | Inline an external file's text |\n| `env` | `\"[env:MY_VAR]\"` | Read an environment variable |\n| `appsettings` | `\"[appsettings:OrchardCore:SiteName]\"` | Read appsettings config |\n| `localization` | `\"[localization:WelcomeTitle]\"` | Localized string |\n| `base64` / `gzip` | `\"[js:base64('...')]\"` | Decode embedded binary/text |\n\n### Step 6: Test the recipe\n\n```bash\n# Build the host module/theme\ndotnet build src/OrchardCore.Themes/YourTheme\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n```\n\nThen create a new tenant (or reset the default one) and pick **Your Site** in the setup screen's recipe list. Verify features, theme, and content provisioned correctly.\n\nTo re-run a non-setup recipe without a fresh tenant: Admin → Configuration → Recipes → Run.\n\n## Quick Reference\n\n### Header properties\n\n| Property | Purpose |\n|----------|---------|\n| `name` | Unique internal id (used by the `recipes` step to call it) |\n| `displayName` | Friendly name in setup/admin UI |\n| `description` | Short summary shown to the user |\n| `issetuprecipe` | `true` makes it selectable during tenant setup |\n| `categories` / `tags` | Grouping/filtering in the UI |\n| `variables` | Reusable values, evaluated once on first access |\n| `steps` | Ordered actions to execute |\n\n### Common feature ids\n\n| Area | Features |\n|------|----------|\n| Core | `OrchardCore.Admin`, `OrchardCore.HomeRoute`, `OrchardCore.Themes`, `OrchardCore.Settings`, `OrchardCore.Recipes` |\n| Content | `OrchardCore.Contents`, `OrchardCore.ContentTypes`, `OrchardCore.ContentFields`, `OrchardCore.Title`, `OrchardCore.Autoroute`, `OrchardCore.Alias`, `OrchardCore.Html`, `OrchardCore.Flows`, `OrchardCore.Lists`, `OrchardCore.Widgets` |\n| Media/Menu | `OrchardCore.Media`, `OrchardCore.Menu`, `OrchardCore.Layers` |\n| Security | `OrchardCore.Users`, `OrchardCore.Roles`, `OrchardCore.Security` |\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Recipe file | `name.recipe.json` (lowercase) | `blog.recipe.json` |\n| `name` property | PascalCase | `Blog` |\n| Location | `Recipes/` in a module or theme | `Themes/TheBlogTheme/Recipes/` |\n\n## References\n\n- `references/recipe-structure.md` - File layout, header, variables, helpers\n- `references/steps.md` - Every built-in recipe step with examples\n- `reference/modules/Recipes/README.md` (docs) - Full recipe documentation\n- Example recipes: `src/OrchardCore.Themes/*/Recipes/*.recipe.json`\n","frontmatter":{"name":"orchardcore-recipe-creator","description":"Creates OrchardCore setup recipes (.recipe.json) that provision a tenant on setup — enabling features, setting themes, defining content types, importing content, roles, and settings. Use when the user needs to create a setup recipe, a starter recipe, or automate tenant configuration."},"isInternal":false,"tokens":1684,"sizeBytes":6458},{"name":"shells.md","path":".agents/skills/orchardcore-tenants/references/shells.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/references/shells.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tenants/references/shells.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/references/shells.md)","tokens":300,"sizeBytes":232},{"name":"SKILL.md","path":".agents/skills/orchardcore-tenants/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/SKILL.md","title":"orchardcore-tenants","category":"anthropic-skill","format":"markdown","content":"# orchardcore-tenants\n\nPath: `.agents/skills/orchardcore-tenants/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/SKILL.md)","tokens":300,"sizeBytes":211},{"name":"admin-navigation.md","path":".agents/skills/orchardcore-tester/references/admin-navigation.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/admin-navigation.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/admin-navigation.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/admin-navigation.md)","tokens":300,"sizeBytes":250},{"name":"autosetup.md","path":".agents/skills/orchardcore-tester/references/autosetup.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/autosetup.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/autosetup.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/autosetup.md)","tokens":300,"sizeBytes":236},{"name":"common-features.md","path":".agents/skills/orchardcore-tester/references/common-features.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/common-features.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/common-features.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/common-features.md)","tokens":300,"sizeBytes":248},{"name":"debugging.md","path":".agents/skills/orchardcore-tester/references/debugging.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/debugging.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/debugging.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/debugging.md)","tokens":300,"sizeBytes":236},{"name":"playwright-cli.md","path":".agents/skills/orchardcore-tester/references/playwright-cli.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/playwright-cli.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/playwright-cli.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/playwright-cli.md)","tokens":300,"sizeBytes":246},{"name":"setup-wizard.md","path":".agents/skills/orchardcore-tester/references/setup-wizard.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/setup-wizard.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-tester/references/setup-wizard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/references/setup-wizard.md)","tokens":300,"sizeBytes":242},{"name":"SKILL.md","path":".agents/skills/orchardcore-tester/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/SKILL.md","title":"orchardcore-tester","category":"anthropic-skill","format":"markdown","content":"# orchardcore-tester\n\nPath: `.agents/skills/orchardcore-tester/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tester/SKILL.md)","tokens":300,"sizeBytes":208},{"name":"assets.md","path":".agents/skills/orchardcore-theme-creator/references/assets.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/assets.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/assets.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/assets.md)","tokens":300,"sizeBytes":244},{"name":"theme-structure.md","path":".agents/skills/orchardcore-theme-creator/references/theme-structure.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/theme-structure.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-theme-creator/references/theme-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/references/theme-structure.md)","tokens":300,"sizeBytes":262},{"name":"SKILL.md","path":".agents/skills/orchardcore-theme-creator/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/SKILL.md","title":"orchardcore-theme-creator","category":"anthropic-skill","format":"markdown","content":"# orchardcore-theme-creator\n\nPath: `.agents/skills/orchardcore-theme-creator/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-theme-creator/SKILL.md)","tokens":300,"sizeBytes":229},{"name":"testing.md","path":".agents/skills/orchardcore-unit-test/references/testing.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/references/testing.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-unit-test/references/testing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/references/testing.md)","tokens":300,"sizeBytes":238},{"name":"SKILL.md","path":".agents/skills/orchardcore-unit-test/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/SKILL.md","title":"orchardcore-unit-test","category":"anthropic-skill","format":"markdown","content":"# orchardcore-unit-test\n\nPath: `.agents/skills/orchardcore-unit-test/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-unit-test/SKILL.md)","tokens":300,"sizeBytes":217},{"name":"storyboard.json","path":".agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json","title":"Secrets-module Skill","category":"anthropic-skill","format":"json","content":"# Secrets-module Skill\n\nPath: `.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/examples/secrets-module/storyboard.json)","tokens":300,"sizeBytes":288},{"name":"recording.md","path":".agents/skills/orchardcore-video-producer/references/recording.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/recording.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/recording.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/recording.md)","tokens":300,"sizeBytes":252},{"name":"specs.md","path":".agents/skills/orchardcore-video-producer/references/specs.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/specs.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/specs.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/specs.md)","tokens":300,"sizeBytes":244},{"name":"storyboard.md","path":".agents/skills/orchardcore-video-producer/references/storyboard.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/storyboard.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-video-producer/references/storyboard.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/references/storyboard.md)","tokens":300,"sizeBytes":254},{"name":"SKILL.md","path":".agents/skills/orchardcore-video-producer/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/SKILL.md","title":"orchardcore-video-producer","category":"anthropic-skill","format":"markdown","content":"# orchardcore-video-producer\n\nPath: `.agents/skills/orchardcore-video-producer/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-video-producer/SKILL.md)","tokens":300,"sizeBytes":232},{"name":"activities.md","path":".agents/skills/orchardcore-workflow-activity/references/activities.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/references/activities.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/orchardcore-workflow-activity/references/activities.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/references/activities.md)","tokens":300,"sizeBytes":260},{"name":"SKILL.md","path":".agents/skills/orchardcore-workflow-activity/SKILL.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/SKILL.md","title":"orchardcore-workflow-activity","category":"anthropic-skill","format":"markdown","content":"# orchardcore-workflow-activity\n\nPath: `.agents/skills/orchardcore-workflow-activity/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-workflow-activity/SKILL.md)","tokens":300,"sizeBytes":241},{"name":"README.md","path":"src/docs/reference/modules/Rules/README.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/reference/modules/Rules/README.md","title":"Rules Skill","category":"anthropic-skill","format":"markdown","content":"# Rules Skill\n\nPath: `src/docs/reference/modules/Rules/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/reference/modules/Rules/README.md)","tokens":300,"sizeBytes":201},{"name":"README.md","path":"src/docs/guides/integrate-facebook-plugins/README.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/guides/integrate-facebook-plugins/README.md","title":"integrate-facebook-plugins Documentation","category":"plugin-manifest","format":"markdown","content":"# integrate-facebook-plugins Documentation\n\nPath: `src/docs/guides/integrate-facebook-plugins/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/guides/integrate-facebook-plugins/README.md)","tokens":300,"sizeBytes":250},{"name":"README.md","path":"src/docs/topics/local-nuget-packages/README.md","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/topics/local-nuget-packages/README.md","title":"local-nuget-packages Documentation","category":"plugin-manifest","format":"markdown","content":"# local-nuget-packages Documentation\n\nPath: `src/docs/topics/local-nuget-packages/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/src/docs/topics/local-nuget-packages/README.md)","tokens":300,"sizeBytes":232},{"name":"mcp.json","path":".github/mcp.json","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.github/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.github/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.github/mcp.json)","tokens":300,"sizeBytes":180},{"name":"mcp.json","path":".vscode/mcp.json","rawUrl":"https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.vscode/mcp.json","title":"Model Context Protocol (MCP) Configuration","category":"mcp-config","format":"json","content":"# Model Context Protocol (MCP) Configuration\n\nPath: `.vscode/mcp.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.vscode/mcp.json)","tokens":300,"sizeBytes":180}],"systemPromptSnippet":"<yakaai_skills repo=\"OrchardCMS/OrchardCore\">\n<!-- File: AGENTS.md (Tokens: ~300 | Category: root-instruction) -->\n# AI Agent Protocol & Instructions\n\nPath: `AGENTS.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/AGENTS.md)\n\n<!-- File: .agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-edit-views/references/ocat-classes.md)\n\n<!-- File: .agents/skills/orchardcore-admin-edit-views/SKILL.md (Tokens: ~3875 | Category: anthropic-skill) -->\n---\nname: orchardcore-admin-edit-views\ndescription: Creates and updates OrchardCore admin edit views using the ocat-* CSS class conventions. Use when creating or modifying Edit view files (*.Edit.cshtml, *.Fields.Edit.cshtml) to ensure correct layout and styling.\nstatus: completed\n---\n\n# OrchardCore Admin Edit Views\n\nThis skill guides you through creating and updating admin edit view files using the `ocat-*` CSS class conventions required by the OrchardCore admin theme.\n\n## The `ocat-*` CSS Class System\n\nThe `ocat-*` CSS classes provide a standardized two-column layout for admin form fields. Every form field uses a consistent structure:\n\n- **Left column** – The field label (`ocat-label`)\n- **Right column** – The input, validation message, and hint (`ocat-end`)\n\n### Core Classes\n\n| Class | Element | Purpose |\n|-------|---------|---------|\n| `ocat-wrapper` | `<div>` | Outer container for a form field row (replaces `mb-3`/`form-group`) |\n| `ocat-label` | `<label>` | Styles the field label in the left column (replaces `form-label`) |\n| `ocat-label-required` | `<label>` | Add alongside `ocat-label` to mark a field as required |\n| `ocat-end` | `<div>` | Wraps the input, validation, and hint in the right column |\n| `ocat-end-offset` | `<div>` | Right-column content with no left label (e.g., checkboxes) |\n| `ocat-limited-wrapper` | `<div>` | Outer row for limited-width controls such as short text, numbers, selects, or small settings inputs |\n| `ocat-limited` | `<div>` | Constrains the control width inside `ocat-limited-wrapper` |\n\n## Choosing the Right Wrapper\n\nUse this decision guide when creating or updating admin edit views:\n\n| Scenario | Preferred Pattern |\n|----------|-------------------|\n| Standard full-width field row | `ocat-wrapper` + `ocat-label` + `ocat-end` |\n| Standalone checkbox/toggle row with no left label | `ocat-wrapper` + `ocat-end-offset` |\n| Standalone alert, notice, legend, or section headline in the form flow | `ocat-wrapper` + `ocat-end-offset` |\n| Top-level limited-width field row (small text, number, select, token, path, port, etc.) | `ocat-limited-wrapper` + `ocat-label` + `ocat-limited` |\n| Content part / content field editor that already needs wrapper classes like `field-wrapper-*` or `content-part-wrapper-*` | Keep the outer `ocat-wrapper`, then place `ocat-limited-wrapper` inside `ocat-end` |\n\n### Wrapper Selection Rules\n\n1. Use `ocat-wrapper` for the default admin edit-view row structure.\n2. Use `ocat-limited-wrapper` when the field should stay visually narrow instead of stretching the full editor width.\n3. If the outer row also needs Orchard-specific wrapper classes such as `field-wrapper-*` or `content-part-wrapper-*`, do **not** replace that outer row with `ocat-limited-wrapper`; keep the outer `ocat-wrapper` and nest `ocat-limited-wrapper` inside `ocat-end`.\n4. Do **not** use legacy helpers or Bootstrap-only layout classes like `mb-3`, `form-group`, `form-label`, `@Orchard.GetWrapperClasses()`, or `@Orchard.GetLimitedWidthWrapperClasses()`.\n5. Alerts, notices, `<legend>`, and section headings that are part of the edit form but have no left-column label should use their own `ocat-wrapper` + `ocat-end-offset` row.\n6. A single row should use **either** `ocat-end` **or** `ocat-end-offset`, never both.\n\n### Standard Field Structure\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label Text\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Descriptive hint text.\"]</span>\n    </div>\n</div>\n```\n\n## Field Type Templates\n\n### Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Required Text Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label ocat-label-required\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"FieldName\" class=\"form-control\" />\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Code/Script Input\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n```\n\n### Textarea\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Content\">\n    <label asp-for=\"Content\" class=\"ocat-label\">@T[\"Content\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Content\" class=\"form-control\" rows=\"5\"></textarea>\n        <span asp-validation-for=\"Content\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Select / Dropdown\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"FieldName\">\n    <label asp-for=\"FieldName\" class=\"ocat-label\">@T[\"Label\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"FieldName\" class=\"form-select\">\n            <option value=\"Option1\">@T[\"Option 1\"]</option>\n            <option value=\"Option2\">@T[\"Option 2\"]</option>\n        </select>\n        <span asp-validation-for=\"FieldName\"></span>\n        <span class=\"hint\">@T[\"Hint text.\"]</span>\n    </div>\n</div>\n```\n\n### Checkbox (No Left Label)\n\nUse `ocat-end-offset` when there is no label in the left column (e.g., for checkboxes):\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsEnabled\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsEnabled\" />\n            <label asp-for=\"IsEnabled\" class=\"form-check-label\">@T[\"Enable feature\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to enable this feature.\"]</span>\n    </div>\n</div>\n```\n\n### Headline / Alert Row (No Left Label)\n\nUse this for standalone form guidance, warnings, legends, or section headers:\n\n```html\n<div class=\"ocat-wrapper\">\n    <div class=\"ocat-end-offset\">\n        <h5>@T[\"Section heading\"]</h5>\n        <div class=\"alert alert-warning\" role=\"alert\">\n            @T[\"Important information for editors.\"]\n        </div>\n    </div>\n</div>\n```\n\n### Input Group (with prefix/suffix)\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Url\">\n    <label asp-for=\"Url\" class=\"ocat-label\">@T[\"URL\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"input-group\">\n            <span class=\"input-group-text\">https://</span>\n            <input asp-for=\"Url\" type=\"text\" class=\"form-control\" />\n        </div>\n        <span asp-validation-for=\"Url\"></span>\n        <span class=\"hint\">@T[\"The full URL.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Field Row\n\nUse this when the whole row is a compact admin field, such as a page size, port, callback path, or select list:\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"PageSize\">\n    <label asp-for=\"PageSize\" class=\"ocat-label\">@T[\"Page size\"]</label>\n    <div class=\"ocat-limited\">\n        <input asp-for=\"PageSize\" type=\"number\" class=\"form-control\" />\n        <span asp-validation-for=\"PageSize\"></span>\n        <span class=\"hint\">@T[\"The default page size.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Select Row\n\n```html\n<div class=\"ocat-limited-wrapper\" asp-validation-class-for=\"TimeZone\">\n    <label asp-for=\"TimeZone\" class=\"ocat-label\">@T[\"Default Time Zone\"]</label>\n    <div class=\"ocat-limited\">\n        <select asp-for=\"TimeZone\" class=\"form-select\">\n            <option value=\"\">@T[\"Local to server\"]</option>\n        </select>\n        <span asp-validation-for=\"TimeZone\"></span>\n        <span class=\"hint\">@T[\"Determines the default time zone.\"]</span>\n    </div>\n</div>\n```\n\n### Limited-Width Control Inside a Field Wrapper\n\nUse this when the editor already needs a content field or content part wrapper on the outer row:\n\n```html\n<div class=\"@($\"ocat-wrapper field-wrapper field-wrapper-{Model.PartFieldDefinition.PartDefinition.Name.HtmlClassify()}-{Model.PartFieldDefinition.Name.HtmlClassify()}\")\">\n    <label asp-for=\"Value\" class=\"ocat-label\">@T[\"Value\"]</label>\n    <div class=\"ocat-end\">\n        <div class=\"ocat-limited-wrapper\">\n            <div class=\"ocat-limited\">\n                <input asp-for=\"Value\" class=\"form-control\" />\n                <span asp-validation-for=\"Value\"></span>\n            </div>\n        </div>\n        <span class=\"hint\">@T[\"Use a compact editor width while preserving the field wrapper row.\"]</span>\n    </div>\n</div>\n```\n\n### Conditional Fields with JavaScript/Liquid Syntax Selector\n\nFor fields that support both JavaScript and Liquid syntax, use the syntax selector pattern with toggling `d-none`:\n\n```html\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Syntax\">\n    <label asp-for=\"Syntax\" class=\"ocat-label\">@T[\"Syntax\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Syntax\" class=\"form-select\">\n            <option value=\"@nameof(WorkflowScriptSyntax.JavaScript)\">@T[\"JavaScript\"]</option>\n            <option value=\"@nameof(WorkflowScriptSyntax.Liquid)\">@T[\"Liquid\"]</option>\n        </select>\n        <span asp-validation-for=\"Syntax\"></span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.Expression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.JavaScript ? \"\" : \"d-none\")\" asp-validation-class-for=\"Expression\">\n    <label asp-for=\"Expression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"Expression\" class=\"form-control code\" />\n        <span asp-validation-for=\"Expression\"></span>\n        <span class=\"hint\">@T[\"A JavaScript expression. Example: {0}\", \"input(\\\"Count\\\") > 0\"]</span>\n    </div>\n</div>\n\n<div id=\"@(Html.IdFor(m => m.LiquidExpression))_group\" class=\"ocat-wrapper @(Model.Syntax == WorkflowScriptSyntax.Liquid ? \"\" : \"d-none\")\" asp-validation-class-for=\"LiquidExpression\">\n    <label asp-for=\"LiquidExpression\" class=\"ocat-label\">@T[\"Expression\"]</label>\n    <div class=\"ocat-end\">\n        <input type=\"text\" asp-for=\"LiquidExpression\" class=\"form-control code\" />\n        <span asp-validation-for=\"LiquidExpression\"></span>\n        <span class=\"hint\">@T[\"A Liquid expression. Example: {0}\", \"{{ Workflow.Properties.Value }}\"]</span>\n    </div>\n</div>\n\n<script at=\"Foot\">\n    document.addEventListener('DOMContentLoaded', () => {\n        const syntaxElement = document.getElementById('@Html.IdFor(m => m.Syntax)');\n        const javaScriptGroup = document.getElementById('@($\"{Html.IdFor(m => m.Expression)}_group\")');\n        const liquidGroup = document.getElementById('@($\"{Html.IdFor(m => m.LiquidExpression)}_group\")');\n\n        const toggleEditors = () => {\n            const useLiquid = syntaxElement.value === '@nameof(WorkflowScriptSyntax.Liquid)';\n            javaScriptGroup.classList.toggle('d-none', useLiquid);\n            liquidGroup.classList.toggle('d-none', !useLiquid);\n        };\n\n        syntaxElement.addEventListener('change', toggleEditors);\n        toggleEditors();\n    });\n</script>\n```\n\n## Display-Only Fields (Read-Only)\n\nFor displaying information without a form input:\n\n```html\n<div class=\"ocat-wrapper\">\n    <label class=\"ocat-label\">@T[\"Field Label\"]</label>\n    <div class=\"ocat-end\">\n        <span>@Model.FieldValue</span>\n    </div>\n</div>\n```\n\n## Migration: Old to New Classes\n\nWhen updating existing views, apply these replacements:\n\n| Old Pattern | New Pattern |\n|-------------|-------------|\n| `<div class=\"mb-3\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<div class=\"form-group\" ...>` | `<div class=\"ocat-wrapper\" ...>` |\n| `<label ... class=\"form-label\">` | `<label ... class=\"ocat-label\">` |\n| Input/select/textarea directly inside wrapper | Wrap in `<div class=\"ocat-end\">` |\n| Checkbox with label inside wrapper | Wrap in `<div class=\"ocat-end-offset\"><div class=\"form-check\">` |\n| Standalone headline or alert directly in the form flow | Wrap in `<div class=\"ocat-wrapper\"><div class=\"ocat-end-offset\">...</div></div>` |\n| Mixing `ocat-end` and `ocat-end-offset` in one row | Choose one wrapper based on whether there is a left-column label |\n| `@Orchard.GetLimitedWidthWrapperClasses()` | `ocat-limited-wrapper` |\n| `@Orchard.GetLimitedWidthClasses()` | `ocat-limited` |\n| Limited-width content field row using `field-wrapper-*` | Keep outer `ocat-wrapper field-wrapper-*`, nest `ocat-limited-wrapper` inside `ocat-end` |\n\n## Complete Edit View Example\n\n```html\n@model MyModuleViewModel\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Title\">\n    <label asp-for=\"Title\" class=\"ocat-label ocat-label-required\">@T[\"Title\"]</label>\n    <div class=\"ocat-end\">\n        <input asp-for=\"Title\" class=\"form-control\" autofocus=\"autofocus\" />\n        <span asp-validation-for=\"Title\"></span>\n        <span class=\"hint\">@T[\"The title of the item.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Description\">\n    <label asp-for=\"Description\" class=\"ocat-label\">@T[\"Description\"]</label>\n    <div class=\"ocat-end\">\n        <textarea asp-for=\"Description\" class=\"form-control\" rows=\"4\"></textarea>\n        <span asp-validation-for=\"Description\"></span>\n        <span class=\"hint\">@T[\"A brief description.\"]</span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"Category\">\n    <label asp-for=\"Category\" class=\"ocat-label\">@T[\"Category\"]</label>\n    <div class=\"ocat-end\">\n        <select asp-for=\"Category\" class=\"form-select\">\n            <option value=\"\">@T[\"-- Select --\"]</option>\n            <option value=\"A\">@T[\"Category A\"]</option>\n            <option value=\"B\">@T[\"Category B\"]</option>\n        </select>\n        <span asp-validation-for=\"Category\"></span>\n    </div>\n</div>\n\n<div class=\"ocat-wrapper\" asp-validation-class-for=\"IsPublished\">\n    <div class=\"ocat-end-offset\">\n        <div class=\"form-check\">\n            <input type=\"checkbox\" class=\"form-check-input\" asp-for=\"IsPublished\" />\n            <label asp-for=\"IsPublished\" class=\"form-check-label\">@T[\"Published\"]</label>\n        </div>\n        <span class=\"hint dashed\">@T[\"Check to publish this item.\"]</span>\n    </div>\n</div>\n```\n\n## References\n\n- `references/ocat-classes.md` - Detailed class reference with CSS context\n- Admin theme source: `src/OrchardCore.Themes/TheAdmin/`\n- Example usage: `src/OrchardCore.Modules/OrchardCore.Workflows/Views/Items/`\n- AGENTS.md (repo root) - Coding conventions and build commands\n\n<!-- File: .agents/skills/orchardcore-admin-index-views/references/worked-example.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-admin-index-views/references/worked-example.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-admin-index-views/references/worked-example.md)\n\n<!-- File: .agents/skills/orchardcore-admin-index-views/SKILL.md (Tokens: ~4510 | Category: anthropic-skill) -->\n---\nname: orchardcore-admin-index-views\ndescription: Builds OrchardCore admin index (list) pages with the AdminList shape and the admin-list tag helpers — view-declared columns, row display drivers placing shapes for both the SummaryAdmin (List) and SummaryAdminGrid (Grid) display types, column providers, row actions, toolbar, search, pager and per-list alternates. Use for every new admin page that lists items, when converting a hand-written admin listing, adding or reordering columns of an admin list, extending another module's list, or changing how its rows and actions render.\n---\n\n# OrchardCore Admin Index Views\n\nEvery admin page that lists items (an \"index\" page: content items, users, queries, workflows, ...) renders its\nitems through the shared `AdminList` shape. Never hand-write a `<ul class=\"list-group\">`, a `<table>`, a toolbar,\na select-all checkbox, an item count, an empty state or a pager for an admin listing: the list renders all of\nthem, in the `List` or `Grid` layout the site and the user choose.\n\n## Ownership\n\n| Layer | Responsibility |\n| --- | --- |\n| Controller | Query the raw items and supply an `AdminListContext<TModel>` with the typed `Source`, pager, search, toolbar and other region shapes. Never build the list shape or the row shapes. |\n| View | Declare the list name, the grid columns, the CSS classes, the empty message and other presentation properties with `<admin-list>`, inside the page's form. |\n| Row display driver | Return ordinary `IDisplayResult` shapes, each with a `SummaryAdmin` location (List) and a `SummaryAdminGrid` location (Grid). Set optional row classes/attributes on `context.Shape`. |\n| Column provider | Optional. Extend the columns a view declares, e.g. from another module. |\n| `AdminList` | Resolve the layout at display time, build each row once with the display type of that layout, and render the whole listing. |\n\n`AdminListLayout` has exactly two values: `List` renders each `SummaryAdmin` row shape whole (its row template\ndecides the look); `Grid` renders the `SummaryAdminGrid` row zones directly, one cell per column, each cell\nrendering the zone named after its column. Layouts are not discovered or registered through templates.\nDo not add column or cell drivers, column metadata classes, or build both representations of a row.\n\n## Decide\n\n| Situation | Do this |\n| --- | --- |\n| New admin page listing items | Follow the workflow below. |\n| Rows already built with `BuildDisplayAsync(..., \"SummaryAdmin\")` and a hand-written list | Convert: move the row building out of the controller into `AdminListContext<TModel>.Source`, add the `SummaryAdminGrid` locations, then follow steps 3 to 6. |\n| Rows are hard-coded `<li>` markup in Razor | Write a display driver for the row model first, so a row is a zone-holding shape, then follow the workflow. |\n| Adding a column to another module's list | Step 5, plus a `SummaryAdminGrid` location in your driver. |\n| The page edits a single entity | Not this skill: use `orchardcore-admin-edit-views`. |\n| The page arranges items rather than listing them (a tree, a canvas, a media library, widgets by zone) | Not this skill: such pages keep their own presentation. |\n\n## Workflow\n\n### 1. Write the row display driver\n\nA row is the shape `IDisplayManager<TModel>` builds for the item. Its type is `typeof(TModel).Name` plus the\ndisplay type, e.g. `IndexProfile_SummaryAdmin`, so register the driver against a domain model named exactly what\nthe shape should be called, never a `*ViewModel`. Register it in the `Startup` of the feature that owns the list:\na driver registered by a neighbouring feature leaves every row empty on a site where that feature is off.\n\nGive every shape a location for each display type. The shape types and templates are the same; only the zone\ndiffers, a row zone for `List` and the zone of a column for `Grid`:\n\n```csharp\npublic override Task<IDisplayResult> DisplayAsync(IndexProfile profile, BuildDisplayContext context)\n{\n    // The row's classes and attributes are rendered on its row element, in both layouts.\n    if (context.DisplayType is OrchardCoreConstants.DisplayType.SummaryAdmin or OrchardCoreConstants.DisplayType.SummaryAdminGrid\n        && context.Shape is Shape rowShape)\n    {\n        rowShape.Classes.Add(\"item\");\n        rowShape.Attributes[\"data-filter-value\"] = profile.Name?.ToLowerInvariant() ?? string.Empty;\n    }\n\n    return CombineAsync(\n        View(\"IndexProfile_Checkbox_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Checkbox:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Select:1\"),\n        View(\"IndexProfile_Fields_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Content:1\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Name:1\"),\n        View(\"IndexProfile_DefaultTags_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Tags:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Source:5\"),\n        View(\"IndexProfile_Buttons_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"Actions:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"Actions:5\"),\n        View(\"IndexProfile_ActionsMenuItems_SummaryAdmin\", profile)\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdmin, \"ActionsMenu:5\")\n            .Location(OrchardCoreConstants.DisplayType.SummaryAdminGrid, \"ActionsMenu:5\"));\n}\n```\n\n- Every visible piece of a row (checkbox, title, badges, dates, buttons, menu items) is its own shape in a zone.\n  Anything hard-coded in the row template is missing from the grid.\n- A location without a display type, `.Location(\"Tags:5\")`, applies to both display types, so in the grid the\n  shape lands in a `Tags` zone no column renders. Always pair a `SummaryAdmin` location with a `SummaryAdminGrid`\n  one, unless the zone name is also a column name (e.g. `Actions`).\n- Display types do not inherit placement: a shape placed only for `SummaryAdmin`, by a driver or by\n  `placement.json`, does not appear in the grid. A grid-only shape declares only a `SummaryAdminGrid` location.\n- `placement.json` rules filtered by `\"displayType\": \"SummaryAdminGrid\"` move a shape to another column zone\n  (`Source:10`), hide it (`-`), change its template or add wrappers and alternates.\n- Do not use editor `|Column` groupings for cells.\n\n### 2. Write the row template for the List layout\n\n`{Model}.SummaryAdmin.cshtml` renders the zones of the row; the grid does not use it. Every shipped row has the\nsame structure, so the lists look alike:\n\n```cshtml\n<div class=\"row g-0 align-items-center\">\n    <div class=\"col\">\n        <div class=\"title d-flex align-items-center\">\n            @* Checkbox, then <div class=\"summary d-flex flex-column flex-md-row list-item-search-text\"> with Content, Tags, Meta *@\n        </div>\n        @if (Model.Description != null)\n        {\n            @await DisplayAsync(Model.Description)\n        }\n    </div>\n    <div class=\"col-auto d-flex justify-content-end ps-2\">\n        @await DisplayAsync(await Factory.CreateAdminListActionsAsync((IShape)Model))\n    </div>\n</div>\n```\n\n| Zone | Renders as | Never |\n| --- | --- | --- |\n| `Content` | The title, a plain link to the edit page | A heading (`<h5>`) |\n| `Tags`, `Meta` | `<span class=\"badge ta-badge fw-normal\">` per fact, leading icon, `title` tooltip | Plain text or a `<span class=\"hint\">` |\n| `Description` | `<div class=\"admin-list-secondary\">`, its own line under the title | A second full-width row running under the actions |\n| `Actions`, `ActionsMenu` | Through `AdminListActions` (`CreateAdminListActionsAsync`), so the Buttons/Menu setting applies | A hand-written button group, or `ActionsMenu` items wrapped in `<li>` |\n\n### 3. Supply the items from the controller\n\n```csharp\nmodel.List = new AdminListContext<IndexProfile>\n{\n    Source = items,\n    Pager = pagerShape,\n};\n```\n\nThe view model exposes `public AdminListContext List { get; set; }`. `AdminListContext<TModel>` sets `ItemType`\nand exposes a typed `IEnumerable<TModel> Source`; its generic argument can select a registered base/interface\ndisplay manager, even for an empty collection of derived items. Items must be non-null instances of that type.\nThe list invokes the display manager after resolving its layout, so the controller has no row-rendering\ndependencies and never builds rows, row callbacks or prebuilt shapes.\n\nThe default `IAdminListItemDisplayManager<TModel>` adapter calls `IDisplayManager<TModel>`. Register a closed\nadapter for a specialized pipeline, e.g. content items use `IContentItemDisplayManager`.\n\nOther context properties, all optional:\n\n- `Header`: an options editor shape (`Summary` and `Actions` zones), as the content items and users pass.\n- `Toolbar`: a toolbar shape of the page's own. Otherwise the list builds `AdminListToolbar` from the rows, the\n  pager, `BulkActions`, `ShowSelectAll` and `ToolbarActions` (a shape at its end, e.g. filters).\n  `ShowToolbar = false` omits it.\n- `Search`, `Actions`: the search bar (e.g. the `AdminListSearch` shape) and the page buttons, in the action bar.\n- `PageSize`: a separate page size selector; set `ShowPageSizeSelector = false` on the pager when passing it.\n- `Data`: what a column provider may need, e.g. the content types the listing is filtered by.\n- `Layout`: forces a layout, only when the page can be rendered one way, e.g. while its rows are dragged.\n- `ShowLayoutSelector = false`: for a page that renders several lists or places one selector itself, by creating\n  an `AdminListLayoutSelector` shape with the shared `ListName`.\n\n### 4. Render the list in the view\n\nImport the tag helpers once per module, in `Views/_ViewImports.cshtml`:\n\n```cshtml\n@using OrchardCore.Admin\n@addTagHelper *, OrchardCore.Admin.Abstractions\n```\n\nThe view keeps the form, its hidden inputs and the action bar, and renders the list with `<admin-list>`:\n\n```cshtml\n<form asp-action=\"Index\" method=\"post\" class=\"no-multisubmit bulk-select-list\"\n      data-checkbox-name=\"itemIds\" data-bulk-action-name=\"Options.BulkAction\" data-selected-text=\"@T[\"selected\"]\">\n    <input type=\"submit\" name=\"submit.Filter\" id=\"submitFilter\" class=\"visually-hidden\" />\n    <input asp-for=\"Options.BulkAction\" type=\"hidden\" />\n    <input type=\"submit\" name=\"submit.BulkAction\" class=\"visually-hidden\" />\n\n    <admin-list name=\"Indexes\" context=\"Model.List\"\n                bulk-actions=\"Model.Options.BulkActions\"\n                empty-message=\"@T[\"<strong>Nothing here!</strong> There are no indexes at the moment.\"]\">\n        <admin-list-list item-css-class=\"list-group-item\" />\n        <admin-list-grid>\n            <admin-list-column type=\"Select\" />\n            <admin-list-column name=\"Name\" title=\"@T[\"Name\"]\" position=\"20\" />\n            <admin-list-column name=\"Source\" title=\"@T[\"Source\"]\" position=\"30\" width=\"auto\" />\n            <admin-list-column type=\"Actions\" title=\"@T[\"Actions\"]\" />\n        </admin-list-grid>\n    </admin-list>\n</form>\n```\n\n- `name` and `context` are both required. The name identifies the list for column providers, the user's layout\n  choice and the alternates. Use a string literal when only the view uses it; keep a module constant when it is\n  reused, e.g. `ContentsAdminList.Name`, and do not create a holder class for a single use.\n- The tag also takes `show-toolbar`, `show-select-all`, `show-layout-selector`, `layout`, `item-css-class`,\n  `empty-message`, `bulk-actions`, and `rows-*` attributes (or `rows-attributes`) for the rows container, e.g.\n  `rows-id` for a sortable list.\n- `admin-list-list` and `admin-list-grid` configure their layout's `rows-class` and `item-css-class` without\n  forcing it. Each can be declared once. List item classes replace the default; grid item classes are added to\n  the required grid row class. Children emit no HTML.\n- The hidden `submit.Filter` button stays first in the form: Enter in the search box triggers it. The search is an\n  input group ending with a magnifying-glass button, never an icon inside the input.\n- Use the shared `bulk-select-list` and `list-search-filter` components, wired by one small module per page\n  (`Assets/ts/*.ts`), and keep the `select-all`, `items`, `selected-items` and `actions` ids the toolbar renders.\n\n### 5. Declare the grid columns\n\nEach `<admin-list-column>` renders the row zone named after it; `zones=\"A B\"` renders several named zones instead.\nA zone no column renders is omitted from the grid.\n\n| Attribute | Meaning |\n| --- | --- |\n| `type` | `Select` (the `Select` zone, at position `10`) or `Actions` (the `Actions` and `ActionsMenu` zones, at position `end`); omit for a regular column. A preset keeps its defaults unless overridden. |\n| `name` | Required for a regular column, unique ignoring case; also the default zone and the `admin-list-column-{name}` CSS class. |\n| `title` | Localized header text, e.g. `@T[\"Name\"]`; it also labels the cells when a narrow list stacks them. |\n| `position` | Placement syntax (`10`, `25`, `end`); columns are sorted by it. |\n| `width` | Any CSS width, or `auto` to hug the content. No width shares the remaining space. |\n| `alignment`, `no-wrap`, `css-class` | Cell presentation. |\n\nTo change the columns of another module's list, implement `IAdminListColumnsProvider`:\n\n```csharp\npublic sealed class CultureColumnsProvider : IAdminListColumnsProvider\n{\n    private readonly IStringLocalizer S;\n\n    public CultureColumnsProvider(IStringLocalizer<CultureColumnsProvider> stringLocalizer)\n    {\n        S = stringLocalizer;\n    }\n\n    public Task PopulateAsync(AdminListColumnsContext context)\n    {\n        // Between \"Title\" (20) and \"Type\" (30), whatever order the providers run in.\n        context.Columns.Add(new AdminListColumn { Name = \"Culture\", Position = \"25\", Title = S[\"Culture\"], Width = AdminListColumn.AutoWidth });\n\n        return Task.CompletedTask;\n    }\n}\n```\n\n```csharp\nservices.AddAdminListColumnsProvider<CultureColumnsProvider>(ContentsAdminList.Name);\n```\n\n- Registered with a list name, a provider is only created and run for that list (keyed DI with an\n  `AdminListColumnsProviderKey`; names are case-sensitive). Several providers can target a list, and a provider\n  can target several lists. Duplicate registrations are ignored.\n- Without a name, `AddAdminListColumnsProvider<TProvider>()` registers a global provider for every list. Global\n  providers run first, then the providers of the list, each group in feature dependency order. A global provider\n  must tolerate different column sets: check `context.Find(name)` before changing an optional column.\n- `context.Columns.Add(...)`, `context.Find(name)` and `context.Remove(name)` change the columns.\n  `context.Data`, `TryGetData<T>()` and `GetData<T>()` read what the page passed, e.g.\n  `ContentsAdminList.ContentTypesKey` and `ContentsAdminList.StereotypesKey`.\n- A provider never builds cells: the content of its column comes from a display driver placing a shape in the\n  zone of that column for `SummaryAdminGrid`.\n\n### 6. Test and document it\n\n- Add the page's URL to `_lists` in `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs` when the\n  Blog recipe enables its feature (add an optional-list test otherwise). The test renders every list in the grid\n  and fails when a column renders nothing on any row, which catches a shape missing its `SummaryAdminGrid` location.\n- Check the page in both layouts and both actions layouts (Buttons and Menu), with rows and without, and in a\n  narrow window, where the grid stacks its cells.\n- Add the list to the table of lists in `src/docs/reference/modules/Admin/README.md`.\n\n## Regions and properties of the shape\n\nRows and regions inherit the list name and layout automatically, so their alternates work without a page passing\nthem. `Rows` is output built during display, never an input; `DisplayType` is the resolved row display type.\nCustom `AdminListCell` templates receive `Row` and `Column`. The Actions cell renders the row's `Actions` and\n`ActionsMenu` zones through `AdminListActions`; moving an action to another zone removes it from that cell.\n\n## Alternates\n\n| Shape | Alternates, least to most specific |\n| --- | --- |\n| `AdminList` | `AdminList__{Layout}`, `AdminList__{Name}`, `AdminList__{Name}__{Layout}` |\n| `AdminListCell` | `AdminListCell__{Column}`, `AdminListCell__{Name}__{Column}` |\n| `AdminListActions` | `AdminListActions__{Layout}`, `AdminListActions__{Name}`, `AdminListActions__{Name}__{Layout}`, where `{Layout}` is `Buttons` or `Menu` |\n| `AdminListToolbar`, `AdminListSearch` | `__{Layout}`, `__{Name}`, `__{Name}__{Layout}` |\n\n`AdminListActionsLayout` has exactly two values, `Buttons` and `Menu`; configuration and the nullable site\nsettings use the enum. Keep templates and alternates overridable, but do not add option shapes or custom modes.\n\n## Gotchas\n\n1. **Never name the rows `Items`**: `Shape` already exposes `Items`, its child shapes.\n2. **A location without a display type does not reach the grid column** (step 1). An empty grid column is the symptom.\n3. **Per-type row templates only apply to the List layout**: `Content-BlogPost.SummaryAdmin.cshtml` does not\n   affect the grid; customise it with `SummaryAdminGrid` placement or `AdminListCell-{List}-{Column}.cshtml`.\n4. **Never wrap `ActionsMenu` items in `<li>`**: the dropdown sits inside the `<li>` of a List row, and the parser\n   hoists a nested `<li>` out of it, so the menu renders empty and stray links appear under the list.\n5. **No viewport-based classes such as `d-md-none` in list Razor**: the grid stacks with a container query on the\n   list itself (`_admin-list.scss`), because the sidebar makes the viewport a poor proxy for the list width.\n6. **A sortable list drags the rows, never their container**: in the grid the rows container is\n   `display: contents`. Mark it with `rows-*` attributes, and send SortableJS `oldDraggableIndex`/`newDraggableIndex`,\n   which count only the rows.\n7. **Rows are built while the view renders**, from the `AdminList` displaying event: a driver's queries and\n   errors happen during rendering, not in the controller.\n\n## References\n\n- `references/worked-example.md`: the Indexes page, driver, controller and view.\n- `src/docs/reference/modules/Admin/README.md`: the user and developer documentation of admin lists.\n- `src/OrchardCore.Modules/OrchardCore.Admin/Views/AdminList*.cshtml`: the layout, cell, actions and toolbar templates.\n- `src/OrchardCore.Themes/TheAdmin/Assets/scss/components/_admin-list.scss`: the layout CSS and its container queries.\n- `test/OrchardCore.Tests.Functional/Tests/Cms/AdminListLayoutTests.cs`: renders every list in the grid.\n\n<!-- File: .agents/skills/orchardcore-asset-manager/references/actions.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-asset-manager/references/actions.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-asset-manager/references/actions.md)\n\n<!-- File: .agents/skills/orchardcore-asset-manager/SKILL.md (Tokens: ~1607 | Category: anthropic-skill) -->\n---\nname: orchardcore-asset-manager\ndescription: Builds, watches, and manages frontend assets in OrchardCore. Use when modifying SCSS, JS, TS, or Vue files, adding new assets to a module/theme, or troubleshooting build failures. Covers all asset actions (vite, sass, min, copy, parcel, webpack, concat) and the three-tier package structure.\n---\n\n# OrchardCore Asset Manager\n\nHandles all frontend asset compilation for OrchardCore modules and themes. Assets are discovered via `Assets.json` files under `src/{OrchardCore.Modules,OrchardCore.Themes}/*/`.\n\n## Node.js Version\n\nThe repo requires **Node.js 24.x** (pinned in `.node-version`). The build script auto-detects a mismatch and prompts:\n\n```\n1) Continue anyway\n2) Abort\n3) Install via fnm, Node.js 24.x and build\n4) Install via Volta, Node.js 24.x and build\n```\n\nAlways select **option 1** (continue) or **option 3** (install via fnm). The allowed Bash commands for this project pre-approve both:\n\n```bash\necho \"1\" | yarn build          # continue with current Node.js\necho \"3\" | yarn build          # install Node 24 via fnm and build\n```\n\n## Build Commands\n\nAll commands run from the **repository root** (`/home/skrypt/repo/orchardcore`).\n\n```bash\n# Build all assets\necho \"1\" | yarn build\n\n# Build a specific asset by name\necho \"1\" | yarn build -n media-app\n\n# Build multiple assets\necho \"1\" | yarn build -n media-app,media-field\n\n# Build by tag\necho \"1\" | yarn build -t admin\n\n# Watch a specific asset (dev mode, auto-rebuilds on save)\necho \"1\" | yarn watch -n media-app\n\n# Host with bundler dev server (HMR)\necho \"1\" | yarn host -n media-app\n\n# Clean all build output\necho \"1\" | yarn clean\n```\n\nThe `-n` name maps to the `\"name\"` field in `Assets.json`.\n\n## Local Claude Code Setup (One-time, per developer)\n\n`.claude/` is gitignored. To avoid being prompted on every build command, create `.claude/settings.json` locally with these pre-approved commands:\n\n```json\n{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(echo \\\"1\\\" | yarn build*)\",\n      \"Bash(echo \\\"3\\\" | yarn build*)\",\n      \"Bash(fnm exec --using 24.14.1 -- corepack yarn install*)\",\n      \"Bash(yarn check*)\",\n      \"Bash(yarn lint*)\",\n      \"Bash(echo \\\"1\\\" | yarn dry-run*)\"\n    ]\n  }\n}\n```\n\nDo **not** commit this file or add a `.gitignore` exception for it — it is intentionally kept local to avoid silently pre-approving commands on other developers' machines.\n\n## Prerequisites / Installing Dependencies\n\nBefore building, if packages are missing:\n\n```bash\nfnm exec --using 24.14.1 -- corepack yarn install\n```\n\nAfter modifying any `package.json` (root, `.scripts/assets-manager/`, or a module's `Assets/`), re-run install.\n\n## Built Output\n\nBuilt files go to the module/theme's `wwwroot/` folder and **must be committed** to the repo. After any asset change, commit both the source file and the generated `wwwroot/` files.\n\n## Package Structure (Three-Tier)\n\n| Location | Purpose |\n|---|---|\n| Root `package.json` | Workspace orchestration, version resolutions |\n| `.scripts/assets-manager/package.json` | Build toolchain (Vite, Parcel, Sass, etc.) |\n| `src/.../Assets/package.json` | Runtime deps bundled into that module/theme |\n\nTo add a **runtime dependency** to a module:\n```bash\n# From the module's Assets/ folder\nyarn add some-library@1.2.3\n# Then rebuild\necho \"1\" | yarn build -n asset-name\n```\n\nTo add a **build tool** (affects all assets):\n- Edit `.scripts/assets-manager/package.json`\n- Run `fnm exec --using 24.14.1 -- corepack yarn install`\n\n## Assets.json\n\nEach module/theme with assets has an `Assets.json` at its root (e.g. `src/OrchardCore.Modules/OrchardCore.Media/Assets.json`). See `references/actions.md` for all supported actions.\n\nQuick example:\n```json\n[\n  {\n    \"action\": \"vite\",\n    \"name\": \"media-app\",\n    \"source\": \"Assets/media-app/\",\n    \"tags\": [\"admin\", \"js\"]\n  },\n  {\n    \"action\": \"sass\",\n    \"name\": \"media-styles\",\n    \"source\": \"Assets/scss/media.scss\",\n    \"tags\": [\"admin\", \"css\"]\n  }\n]\n```\n\n## Code Quality Commands\n\nThese do **not** go through the Node.js version prompt — run them directly.\n\n```bash\n# TypeScript type-check all Vue/TS files (vue-tsc --noEmit)\nyarn check\n\n# Lint all JS/TS/Vue files (ESLint)\nyarn lint\n\n# Preview which files would be built/copied without writing anything\necho \"1\" | yarn dry-run\necho \"1\" | yarn dry-run -n media-app   # scoped to one asset\n```\n\n- **`yarn check`** — runs `vue-tsc --noEmit`; catches type errors across all Vue/TS source files. Run before committing TS/Vue changes. Does **not** support `-n`; to check a specific module, point it at that module's tsconfig directly: `yarn vue-tsc --noEmit -p src/OrchardCore.Modules/OrchardCore.Media/Assets/media-app/tsconfig.json`\n- **`yarn lint`** — runs ESLint across the repo. Accepts a file or directory to scope it: `yarn lint src/OrchardCore.Modules/OrchardCore.Media/Assets/`. Fix any errors before committing.\n- **`yarn dry-run`** — logs what the build would do (copy targets, output paths) without touching any files. Useful when adding a new `Assets.json` entry to verify paths before the first real build.\n\n## Troubleshooting\n\n| Problem | Fix |\n|---|---|\n| `Cannot find package '@tailwindcss/vite'` | Run `fnm exec --using 24.14.1 -- corepack yarn install` |\n| Parcel cache stale after deleting output | Run `echo \"1\" \\| yarn clean` then rebuild |\n| Changes not reflected | Confirm the built `wwwroot/` files changed; rebuild if not |\n| Ran a full-repo build by accident (wrong `-n` value, e.g. package.json name instead of `Assets.json` \"name\") | `git status --short` immediately; `git checkout --` every changed path outside your target module/theme before committing |\n| Edited source but bug still repros in the running app | You forgot to rebuild. `stat -c '%y' <source>.ts` vs `stat -c '%y' wwwroot/Scripts/<bundle>.js` — if the bundle is older, run `yarn build -n <asset-name>` |\n\n## References\n\n- `references/actions.md` — All Assets.json action types with examples\n</content>\n</invoke>\n\n<!-- File: .agents/skills/orchardcore-breadcrumbs/references/theming.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-breadcrumbs/references/theming.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-breadcrumbs/references/theming.md)\n\n<!-- File: .agents/skills/orchardcore-breadcrumbs/SKILL.md (Tokens: ~5101 | Category: anthropic-skill) -->\n---\nname: orchardcore-breadcrumbs\ndescription: Authors explicit breadcrumb titles, inline ancestors, and lightweight IBreadcrumbProvider postprocessors on OrchardCore admin screens and the front end. Use when declaring current-page titles, extending ancestors, resolving a parent entity, documenting hidden-trail processing, or theming breadcrumbs through name-based shape alternates.\n---\n\n# OrchardCore Breadcrumbs\n\nA breadcrumb is a **named trail with an explicit current-page title**. The parent `<breadcrumb>` requires an `IHtmlContent` title and normalizes it to text; every `<breadcrumb-item>` child is an ancestor. For visible trails, the view supplies dynamic ancestor links and localized text, and registered `IBreadcrumbProvider` implementations can postprocess only those ancestors. The helper preserves their final list order and appends the normalized title as the final, unlinked current node. Hidden admin trails use only the explicit title and skip children and providers entirely. On the admin, the trail appears above a heading containing that text; on the front end, no heading is rendered by default.\n\nKeep built-in views inline, with the current page in `title` rather than a child. Use lightweight providers for shared or cross-module ancestor changes, not a separate manager, builder, named-provider base class, or contextual-data contract. Customize the owning view or its partials for the title and screen-specific ancestors, and use shape alternates for presentation. `INavigationProvider` remains the separate extension point for ordinary navigation menus.\n\n**Keep all Show breadcrumb logic in the tag helper.** Views and partials must not read `AdminSettings.ShowBreadcrumb` or gate lookups, child declarations, or breadcrumb markup on it. Declare nodes and resolve needed data normally; the helper alone chooses visibility and the provider/title pipeline.\n\n## Mental model\n\n1. Require a stable `name` and a non-null `Microsoft.AspNetCore.Html.IHtmlContent` title, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text but still leaves a current node in a visible trail.\n2. Check visibility before any child or provider work. A hidden admin trail never calls `GetChildContentAsync`, resolves or enumerates providers, constructs or invokes them, or creates a `BreadcrumbContext` or any breadcrumb items, including the current node.\n3. For a hidden trail, only the explicit title matters. When heading or browser-title output is needed, call `WriteTo(writer, HtmlEncoder.Default)` once and HTML-decode once, then safely encode the text for output. With `heading=\"\"` and `page-title=\"false\"`, validate the non-null title but also skip `WriteTo`.\n4. For a visible trail, normalize the title, collect ancestor children, and await registered providers sequentially in DI registration order on the shared mutable ancestor list. Helper-created provider contexts always have `ShowTrail == true`.\n5. Preserve the providers' final ancestor list order without sorting, then unconditionally append the explicit title node with fixed `Id = \"Title\"`, last/current and never linked. Clearing or reordering ancestors cannot remove or displace it.\n6. Visible trails finalize ancestor links and render shapes. Hidden trails render only the requested explicit heading/browser title, with no URL generation, permission lookup, link authorization, or shapes. The admin setting does not affect front-end trails.\n\n## Naming\n\n- Use stable literal strings directly in `.cshtml`: `name=\"ContentsEdit\"` and ancestor `id=\"Contents\"`.\n- Use `name` for provider targeting, the trail's CSS class, shape alternates, and name-based deduplication, including Dashboard. The current node automatically has `Id = \"Title\"`; its name-specific alternate needs no separate attribute.\n- Reuse a screen's admin list name when it has one. The content items list uses `Contents`.\n- Prefer PascalCase names without separators so shape alternate file names remain readable.\n- Do not introduce breadcrumb constants classes, contextual-data keys, or a `data` attribute.\n- Ancestor IDs and the automatic current-node ID `Title` identify node shape alternates; they are not rendered as HTML `id` attributes.\n\n## Workflow A: add a breadcrumb to a view\n\n### Step 1: Import the tag helpers\n\nReference `OrchardCore.Navigation.Core` and add this to the module's `Views/_ViewImports.cshtml` if it is not already imported:\n\n```razor\n@addTagHelper *, OrchardCore.Navigation.Core\n```\n\nThe always-enabled `OrchardCore.Navigation` module supplies the shapes. Inline authoring needs no custom provider registration.\n\n### Step 2: Declare the explicit title and ancestor children\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nPrefer paired parent tags with inline ancestors. The parent supplies the current page through `title`; the helper appends it with ID `Title`. Do not add a duplicate current-page child.\n\nPass the normal Razor localizer result directly, as with `RenderTitleSegments`: `title=\"@T[\"Edit Record\"]\"` or `title=\"@T[\"Edit {0}\", record.Name]\"`. The helper renders the HTML-aware value when needed, including its formatting arguments. Do not extract `.Value` from `T[...]` or add caller conversions. Keep `@T[\"...\"]` for ancestor child text too.\n\nPlain strings require the existing safe wrapper `new OrchardCore.DisplayManagement.Html.HtmlContentString(value ?? string.Empty)`. For `Microsoft.Extensions.Localization.LocalizedString` and `OrchardCore.Localization.Data.DataLocalizedString` from `IDataLocalizer`, wrap `.Value ?? string.Empty`. Do not pass these non-HTML values directly, extract `T[...].Value`, or use `Html.Raw`: `HtmlContentString` encodes untrusted strings when written.\n\nA self-closing helper still requires both attributes, for example `<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />`. Providers may supply ancestors, but not the current title. With no ancestors, a visible trail still contains the appended current node.\n\n| Parent attribute | Behavior |\n|------------------|----------|\n| `name` | Required stable trail name used for provider targeting, CSS, shape alternates, and name-based ancestor deduplication. |\n| `title` | Required non-null `Microsoft.AspNetCore.Html.IHtmlContent`, including `LocalizedHtmlString`. Empty content such as `HtmlString.Empty` suppresses heading/browser-title text, not the current node in a visible trail. |\n| `heading` | Defaults to `h1` on admin requests and no heading on the front end. `heading=\"\"` suppresses the heading. |\n| `page-title` | Defaults to `true`; adds the nonempty explicit title as a browser-title segment. |\n| `display-type` | Defaults to `DetailAdmin` on admin requests and `Detail` elsewhere. Supplies shape alternates. |\n\n### Title normalization\n\nWith `@using OrchardCore.DisplayManagement.Html`, prepare the view's values as follows:\n\n| Value in the view | Title expression |\n|-------------------|------------------|\n| HTML-localized label | `title=\"@T[\"Edit {0}\", record.Name]\"` |\n| Plain model string | `title=\"@(new HtmlContentString(Model.DisplayText ?? string.Empty))\"` |\n| `LocalizedString` | `title=\"@(new HtmlContentString(localizedTitle.Value ?? string.Empty))\"` |\n| `DataLocalizedString` | `title=\"@(new HtmlContentString(dataTitle.Value ?? string.Empty))\"` |\n\nUse `title=\"@Microsoft.AspNetCore.Html.HtmlString.Empty\"` for intentionally empty content. A null title is invalid, including on the no-output path.\n\nWhen needed, every title follows the same normalization: render its `IHtmlContent` once with `WriteTo(writer, HtmlEncoder.Default)`, then HTML-decode once into plain text. The helper safely encodes that string for the heading, current node, and browser title. Literal `&amp;` in a string wrapped with `HtmlContentString` stays literal `&amp;`, serialized as `&amp;amp;` in HTML. Do not pre-encode the wrapper's input. `IHtmlContent` does not enable arbitrary title markup: final title output remains text-only.\n\n### Step 3: Remove duplicate title output\n\nRemove the view's old page heading and `RenderTitleSegments` call. The helper supplies both by default on the admin. Keep the helper in the `Title` zone so it works with the admin theme's top-bar title setting.\n\nFor a trail that does not own the heading, use the `Breadcrumbs` zone and `heading=\"\"`. Also set `page-title=\"false\"` if the page registers its browser title separately.\n\n## Child attributes and link behavior\n\n| Attribute | Type | Behavior |\n|-----------|------|----------|\n| `id` | `string` | Stable node identifier for shape alternates. |\n| `action`, `controller`, `area` | `string` | MVC route target. Prefer these over hand-built action URLs. |\n| `route-*` | `string` | Additional action route values, such as `route-id=\"@record.Id\"`. |\n| `route-values` | `IDictionary<string, string>` | Additional action route values supplied as a dictionary. |\n| `url` | `string` | Direct link target; ignored when `action` is set. |\n| `permission` | `string` | Stored as `BreadcrumbItem.PermissionName`, resolved through `IPermissionService` during visible ancestor link processing after providers; unknown names are ignored. |\n| `resource` | `object` | Resource passed to authorization for the permission. |\n| `link-enabled` | `bool` | Defaults to `true`. Use `false` when the view has already determined that the link should be unavailable. |\n\nThe inner content is localized ancestor text, for example `@T[\"Records\"]`. Child content is HTML-decoded after Razor evaluates it, then safely encoded by the shape template. The parent's `IHtmlContent` title is rendered and decoded once as described above. Templates safely encode normalized title and provider-supplied text; do not emit them with `Html.Raw`.\n\nEvery child and provider item remains an ancestor, including the last item in the list. The helper appends a separate title node after all ancestors; only that node is current and **never linked**. Ancestors with denied permissions, `link-enabled=\"false\"`, or no link target remain visible as text. Do not omit ancestors solely because the user cannot follow their links.\n\nChildren and providers only store named permissions. The helper resolves `PermissionName` after providers and link-eligibility checks, so removed or unlinkable ancestors do not cause a name lookup. The explicit title node never needs link processing. Providers can also add `Permission` objects to `BreadcrumbItem.Permissions`; authorization evaluates applicable permissions against `Resource`. Hidden trails perform neither permission lookup nor authorization.\n\n## Workflow B: resolve a nested editor's parent\n\nUse the real parent's display name and route ID, obtained from the view model when available. For example, a model exposing `Parent` and `Record` can render:\n\n```razor\n<zone Name=\"Title\">\n    <breadcrumb name=\"RecordsEdit\" title=\"@T[\"Edit {0}\", Model.Record.Name]\">\n        <breadcrumb-item id=\"Records\" action=\"Index\" controller=\"Record\" area=\"My.Module\" permission=\"@MyPermissions.ManageRecords.Name\">@T[\"Records\"]</breadcrumb-item>\n        <breadcrumb-item id=\"Parent\" action=\"Edit\" controller=\"Record\" area=\"My.Module\"\n                         route-id=\"@Model.Parent.Id\"\n                         permission=\"@MyPermissions.ManageRecords.Name\"\n                         resource=\"@Model.Parent\">@T[\"Edit {0}\", Model.Parent.Name]</breadcrumb-item>\n    </breadcrumb>\n</zone>\n```\n\nIf only the parent ID is available, inject an existing domain service into Razor, not a breadcrumb-specific service. A model with `ParentContentItemId`, for example, can use:\n\n```razor\n@inject OrchardCore.ContentManagement.IContentManager ContentManager\n@{\n    var parent = await ContentManager.GetAsync(Model.ParentContentItemId);\n}\n```\n\nUse the loaded parent's ID and display text in ancestor child items. Follow the screen's existing missing-parent handling and error reporting. Do not guard parent lookups on the breadcrumb visibility setting. Hidden trails always skip child content, but parent attribute expressions and Razor code or model lookups outside that child content still execute.\n\n## Workflow C: postprocess a trail with a provider\n\nImplement `OrchardCore.Navigation.IBreadcrumbProvider`:\n\n```csharp\npublic interface IBreadcrumbProvider\n{\n    ValueTask BuildBreadcrumbAsync(BreadcrumbContext context);\n}\n```\n\n`BreadcrumbContext` is also in `OrchardCore.Navigation`. Its public constructor is `BreadcrumbContext(string name, string title, List<BreadcrumbItem> items, ViewContext viewContext, bool showTrail)`, with `ViewContext` from `Microsoft.AspNetCore.Mvc.Rendering`. The helper passes the normalized plain-string title, not the original `IHtmlContent`. It exposes read-only properties:\n\n| Property | Type | Use |\n|----------|------|-----|\n| `Name` | `string` | Filter the trail's literal name explicitly. |\n| `Title` | `string` | Normalized plain-text explicit title, which providers cannot replace. |\n| `Items` | `List<BreadcrumbItem>` | Shared mutable ancestor list. Add, insert, remove, replace, reorder, or edit ancestors; do not replace the read-only list reference. The final list order is preserved. The explicit current node is not in the list. |\n| `ViewContext` | `Microsoft.AspNetCore.Mvc.Rendering.ViewContext` | Read request context, such as `HttpContext`, when filtering by admin/front-end request. |\n| `ShowTrail` | `bool` | Whether the trail is visible. Helper-created provider contexts always have `ShowTrail == true`; hidden trails create no context and invoke no providers. |\n\nProviders see all visible trails unless they explicitly filter the context. This example targets names starting with `Example`, excluding the `Examples` list itself by name rather than localized title text:\n\n```csharp\nusing System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Microsoft.Extensions.Localization;\nusing OrchardCore.Navigation;\n\npublic sealed class ExampleBreadcrumbProvider : IBreadcrumbProvider\n{\n    private readonly IStringLocalizer S;\n\n    public ExampleBreadcrumbProvider(IStringLocalizer<ExampleBreadcrumbProvider> localizer)\n    {\n        S = localizer;\n    }\n\n    public ValueTask BuildBreadcrumbAsync(BreadcrumbContext context)\n    {\n        if (!context.Name.StartsWith(\"Example\", StringComparison.Ordinal) || context.Name == \"Examples\" ||\n            context.Items.Any(item => item.Id == \"Examples\"))\n        {\n            return ValueTask.CompletedTask;\n        }\n\n        context.Items.Insert(0, new BreadcrumbItem\n        {\n            Id = \"Examples\",\n            Text = S[\"Examples\"].Value,\n            Url = \"~/examples\",\n        });\n\n        return ValueTask.CompletedTask;\n    }\n}\n```\n\nRegister the provider in `Startup.ConfigureServices`:\n\n```csharp\nservices.AddBreadcrumbProvider<ExampleBreadcrumbProvider>();\n```\n\nRegistration is scoped and uses `TryAddEnumerable`. For visible trails, providers run in DI registration order after collecting ancestor children, before appending the explicit current node and finalizing `IsCurrent` and `Href`. Later providers observe earlier ancestor mutations, and the helper preserves the final list order.\n\nUse `context.Name` to target a trail, or `context.ViewContext` to target a request. An admin-only provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)`. Set `Text` to a plain string, not encoded HTML; use `Url` or `RouteValues`, `PermissionName` or the `Permissions` list, `Resource`, and `LinkEnabled` to describe links. Do not depend on incoming `IsCurrent` or `Href`, which the helper computes afterward.\n\nThe example also supplies an ancestor to a trail with no inline children. The view must still provide its title:\n\n```razor\n<breadcrumb name=\"Example\" title=\"@T[\"Edit Example\"]\" />\n```\n\nProviders cannot remove or replace the explicit title: `Title` is read-only, and `Items` contains only ancestors. The helper appends the title node with `Id = \"Title\"` after all providers have run, even if a provider cleared or reordered every ancestor.\n\nHidden admin trails bypass providers completely: the helper does not resolve, enumerate, construct, or invoke `IBreadcrumbProvider` implementations. It creates no `BreadcrumbContext`; the retained `ShowTrail` property and constructor parameter are always true in helper-created provider contexts. Only the explicit title supplies hidden heading/browser-title output.\n\n## Dashboard ancestor\n\nThe Admin Dashboard feature registers `OrchardCore.AdminDashboard.Services.DashboardBreadcrumbProvider` with `AddBreadcrumbProvider<DashboardBreadcrumbProvider>()`.\n\nThe provider checks `AdminAttribute.IsApplied(context.ViewContext.HttpContext)` and leaves front-end trails unchanged. It skips insertion if `context.Name == \"Dashboard\"` or an ancestor already has ID `Dashboard`; it does not compare localized title text. Otherwise it inserts a localized Dashboard ancestor at index `0`, with ID `Dashboard` and `Url = \"~/\" + adminOptions.AdminUrlPrefix`. Later providers can move it just like any other ancestor.\n\nIts `Permissions` list contains the static `OrchardCore.AdminDashboard.Permissions.AccessAdminDashboard` permission object, so no named-permission lookup is required. Denied access makes a visible Dashboard ancestor plain text. The Dashboard page uses `name=\"Dashboard\"` and `title=\"@T[\"Dashboard\"]\"` so only the explicit current node, with ID `Title`, represents Dashboard.\n\nFor visible admin trails, Dashboard can be added when there are no other ancestors, but the explicit current title is still appended afterward. Hidden trails skip this provider entirely; it never affects title text. Disabling the feature removes this provider contribution; there is no Dashboard option on the tag helper.\n\n## List order\n\nAncestor order comes only from the list. Inline children are collected in declaration order; providers can use `Add`, `Insert`, `RemoveAt`, or other list operations to change it. The helper preserves the final list order without sorting.\n\n`BreadcrumbItem` has no `Position` property and does not implement `IPositioned`. The child helper has no `position` attribute. List changes cannot displace the current page: the helper appends the explicit title after every ancestor.\n\n## Disabled admin trails\n\n**Show breadcrumb** under **Configuration > Settings > Admin** (`AdminSettings.ShowBreadcrumb`, default on) controls admin trail visibility, not front-end trails.\n\nWhen disabled, the helper **skips all providers and breadcrumb items**, regardless of provider registration. It never calls `GetChildContentAsync`, never resolves, enumerates, constructs, or invokes providers (including Dashboard), and creates no `BreadcrumbContext` or items, including the title node. Child text and links are not evaluated.\n\nOnly the explicit title supplies heading/browser-title text. When needed, it is formatted once through `WriteTo(writer, HtmlEncoder.Default)` and HTML-decoded once. The helper safely encodes the normalized string, renders the heading directly with `oc-breadcrumb-title`, and registers the browser title when `page-title` is true. It does not generate URLs, resolve permission names, authorize links, or create shapes. Breadcrumb templates and alternates do not run.\n\nWhen the trail is hidden and both `heading=\"\"` and `page-title=\"false\"` are set, the helper validates the non-null title but also skips `WriteTo`. Parent attribute expressions and Razor code or model lookups outside child content still run: `T[...]` results and `HtmlContentString` wrappers are created before the helper, even when it skips `WriteTo`. All child-only work is skipped for every hidden trail; do not add visibility guards to views. For a visible trail, these attributes suppress only heading/browser-title output, not ancestors, providers, or the explicit current node.\n\n## Theming and low-level shapes\n\nVisible trails use the existing `Breadcrumb` and `BreadcrumbItem` shapes and their name, ID, and display-type alternates:\n\n- Whole trail: `Breadcrumb.cshtml`, `Breadcrumb-ContentsEdit.cshtml`, `Breadcrumb.DetailAdmin.cshtml`, `Breadcrumb-ContentsEdit.DetailAdmin.cshtml`.\n- One node: `BreadcrumbItem.cshtml`, `BreadcrumbItem-Dashboard.cshtml`, `BreadcrumbItem-ContentsEdit-Title.DetailAdmin.cshtml`.\n\n`Breadcrumb.cshtml` owns both the visible trail and heading. Override it to rearrange them. Keep text safely encoded. See `references/theming.md` for alternate precedence, encoding, and the distinction between hidden admin trails and shape-level `ShowTrail`.\n\nThe helper assigns the current node `Id = \"Title\"`. The existing alternate factory provides `BreadcrumbItem-Title.cshtml`, `BreadcrumbItem-[Name]-Title.cshtml`, `BreadcrumbItem-Title.[DisplayType].cshtml`, and `BreadcrumbItem-[Name]-Title.[DisplayType].cshtml`; no extra attribute or shape type is needed. Ancestor IDs still come from child `id` or provider-supplied `Id`.\n\n`IShapeFactory.BreadcrumbAsync` is a low-level presentation API for prebuilt `BreadcrumbItem` objects. Its caller supplies the complete ordered sequence, including the terminal current node, with authorized `Href` values and `IsCurrent` already resolved. It does not append a title node, invoke providers, build trails from names, load parents, add Dashboard, or register the browser title. It is not the recommended authoring path for a view.\n\n## Gotchas\n\n- **Missing title is invalid.** Every helper, including a self-closing one, needs a non-null `IHtmlContent` title, even on the no-output path. `HtmlString.Empty` is valid and still leaves a current node in a visible trail.\n- **Wrap non-HTML title values safely.** Pass `@T[...]` directly, never its `.Value`. Use `new HtmlContentString(value ?? string.Empty)` for strings and `new HtmlContentString(localizedTitle.Value ?? string.Empty)` for non-HTML localizer results; never `Html.Raw`.\n- **HTML-aware input is still text-only output.** Do not render normalized title text as raw HTML or decode it again.\n- **Children are ancestors only.** Declare the current page in parent `title`; the helper appends it with ID `Title`, so do not duplicate it with a child.\n- **Providers cannot change the current title.** Clearing or reordering ancestors never removes or replaces the appended current node.\n- **Hidden means title-only.** The helper skips all provider resolution and execution, child evaluation, contexts, and items. Provider contexts created for visible trails always have `ShowTrail == true`.\n- **No visibility guards in views.** Do not read `AdminSettings.ShowBreadcrumb` or wrap lookups or markup in setting-dependent branches; visibility belongs to the helper.\n- **Named permissions are deferred.** Do not look them up while collecting or postprocessing ancestors; removed or unlinkable ancestors do not need that work.\n- **Second heading.** Remove the old title output when the helper owns the title.\n- **Wrong dictionary type.** `route-values` accepts `IDictionary<string, string>`, not an anonymous object or `RouteValueDictionary`.\n- **Link checks are not endpoint security.** Keep authorization on the target action; `permission` and `link-enabled` control only the rendered link.\n- **Renamed identifiers affect themes and providers.** Keep trail names and ancestor IDs stable. The automatic current-node ID is always `Title`; screen-specific title alternates use the trail name.\n- **Hidden trails bypass templates.** A breadcrumb shape override cannot change the helper's directly rendered disabled-state heading.\n\n## References\n\n- `references/theming.md` - alternate tables, display types, front-end usage, and heading behavior.\n- `src/OrchardCore/OrchardCore.Navigation.Core/` - `BreadcrumbTagHelper`, `BreadcrumbItemTagHelper`, `IBreadcrumbProvider`, `BreadcrumbContext`, `BreadcrumbItem`, and `ShapeFactoryExtensions`.\n- `src/OrchardCore.Modules/OrchardCore.AdminDashboard/Services/DashboardBreadcrumbProvider.cs` - feature-registered admin ancestor provider.\n- `src/OrchardCore.Modules/OrchardCore.Navigation/` - `BreadcrumbShapes`, `BreadcrumbAlternatesFactory`, and the breadcrumb views.\n- `src/docs/reference/modules/Navigation/README.md` - canonical breadcrumb reference.\n- `AGENTS.md` - project conventions.\n\n<!-- File: .agents/skills/orchardcore-data-migration/references/patterns.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/patterns.md)\n\n<!-- File: .agents/skills/orchardcore-data-migration/references/schema-builder.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-data-migration/references/schema-builder.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-data-migration/references/schema-builder.md)\n\n<!-- File: .agents/skills/orchardcore-data-migration/SKILL.md (Tokens: ~1543 | Category: anthropic-skill) -->\n---\nname: orchardcore-data-migration\ndescription: Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX). Use when the user needs to alter content type definitions, create or change SQL index tables, patch existing content items, run a recipe migration, or version a module's schema.\n---\n\n# OrchardCore Data Migration\n\nThis skill guides you through writing OrchardCore data migrations following project conventions.\n\nA data migration is a class that inherits `DataMigration`. OrchardCore discovers its methods by reflection and runs them **sequentially**, per tenant, on application start. The chain starts at `CreateAsync()` and continues through `UpdateFrom1Async()`, `UpdateFrom2Async()`, … Each method returns the schema version number the **next** method must match.\n\n## When to use a migration\n\n- Define or alter content types / parts / fields (`IContentDefinitionManager`).\n- Create or alter SQL index tables (`SchemaBuilder`).\n- Patch existing content item data (query + `ISession.SaveAsync`).\n- Run a setup recipe at install/upgrade (`IRecipeMigrator`).\n- Seed roles, settings, or other documents.\n\n## The version-chain rule (read first)\n\nThe migration runner stores the **last returned number** per migration class per tenant. It then calls the method whose name matches: returned `4` → runs `UpdateFrom4Async()`. The method's return value is the new stored version.\n\n| Rule | Why |\n|------|-----|\n| `CreateAsync` runs only on a **fresh** install. | Existing tenants already passed it. |\n| `CreateAsync` returns the **current latest** version, skipping all `UpdateFromX`. | New sites get the final schema directly. |\n| `UpdateFromXAsync` runs only when stored version == `X`. | Sequential upgrade path. |\n| **Never renumber** an already-shipped method. | Other tenants store the old number; renumbering breaks their upgrade. |\n| Each `UpdateFromX` returns `X+1` (usually). | Skipping is allowed when a later step subsumes an earlier one (return a higher number to jump). |\n\n## Migration Workflow\n\n### Step 1: Create the Migrations class\n\nPlace `Migrations.cs` in the module root (or a `Migrations/` subfolder for multiple classes, e.g. `Migrations/PermissionMigrations.cs`).\n\n```csharp\nusing OrchardCore.ContentManagement.Metadata;\nusing OrchardCore.Data.Migration;\n\nnamespace YourModule;\n\npublic sealed class Migrations : DataMigration\n{\n    private readonly IContentDefinitionManager _contentDefinitionManager;\n\n    public Migrations(IContentDefinitionManager contentDefinitionManager)\n    {\n        _contentDefinitionManager = contentDefinitionManager;\n    }\n\n    public async Task<int> CreateAsync()\n    {\n        // ... define schema ...\n\n        // Return the latest version so new installs skip every UpdateFromX.\n        return 1;\n    }\n}\n```\n\n`SchemaBuilder` is injected by the base class — do **not** add it as a constructor parameter.\n\n### Step 2: Register the migration\n\nIn the module `Startup.cs`:\n\n```csharp\nservices.AddDataMigration<Migrations>();\n```\n\nRegister each migration class separately if the module has more than one.\n\n### Step 3: Write the initial `CreateAsync`\n\nReturn the highest version your module currently ships. Example (content part):\n\n```csharp\npublic async Task<int> CreateAsync()\n{\n    await _contentDefinitionManager.AlterPartDefinitionAsync(\"TitlePart\", builder => builder\n        .Attachable()\n        .WithDescription(\"Provides a Title for your content item.\"));\n\n    // Shortcut other migration steps on new content definition schemas.\n    return 2;\n}\n```\n\n### Step 4: Add `UpdateFromX` for each shipped change\n\nWhen you change the schema in a release, **append** a new `UpdateFromXAsync` — never edit a prior one. Bump the number `CreateAsync` returns to match.\n\n```csharp\n// This code can be removed in a later version.\npublic async Task<int> UpdateFrom1Async()\n{\n    await SchemaBuilder.AlterIndexTableAsync<LinkFieldIndex>(table => table\n        .AddColumn<string>(\"BigUrl\", column => column.Nullable().Unlimited()));\n\n    return 2;\n}\n```\n\n### Step 5: Test the migration\n\n1. **Fresh install** — run on a clean tenant; only `CreateAsync` executes, schema lands at the latest version.\n2. **Upgrade** — start from a DB at the previous version; confirm each `UpdateFromX` runs in order.\n3. Test on every targeted provider (SQLite, SQL Server, MySQL, PostgreSQL). SQLite cannot drop columns — guard with `try/catch` (see `references/schema-builder.md`).\n\n## Method signatures\n\n| Method | Forms | Runs when |\n|--------|-------|-----------|\n| Create | `int Create()` · `Task<int> CreateAsync()` | fresh install |\n| Update | `int UpdateFromX()` · `Task<int> UpdateFromXAsync()` | stored version == X |\n| Uninstall | `void Uninstall()` · `Task UninstallAsync()` | feature uninstalled |\n\nStatic or instance, sync or async — all supported. Async (`...Async`) is the convention for new code.\n\n## Quick Reference\n\n### Common operations\n\n| Goal | API |\n|------|-----|\n| Define/alter content type | `_contentDefinitionManager.AlterTypeDefinitionAsync(...)` |\n| Define/alter part | `_contentDefinitionManager.AlterPartDefinitionAsync(...)` |\n| Remove part / type | `RemovePart(...)`, `DeletePartDefinitionAsync(...)` |\n| Create SQL index table | `SchemaBuilder.CreateMapIndexTableAsync<TIndex>(...)` |\n| Add/drop column or index | `SchemaBuilder.AlterIndexTableAsync<TIndex>(...)` |\n| Run a recipe | `_recipeMigrator.ExecuteAsync(\"init.recipe.json\", this)` |\n| Patch content items | `ISession.Query<ContentItem, ContentItemIndex>(...)` + `SaveAsync` |\n\n### Type definition builder methods\n\n| Method | Effect |\n|--------|--------|\n| `.Creatable()` | Appears in the New menu |\n| `.Listable()` | Shows in content list |\n| `.Draftable()` | Supports drafts |\n| `.Versionable()` | Keeps version history |\n| `.Securable()` | Per-type permissions |\n| `.WithPart(\"PartName\")` | Attaches a part |\n\n## Gotchas\n\n- Always set column **length** on string index columns (`.WithLength(n)`) or `.Unlimited()` — unlengthed columns fail on some providers.\n- `CreateAsync` should return the latest version, not `1`, once `UpdateFromX` methods exist.\n- Patch loops must page by `DocumentId` and call `FlushAsync()` periodically (see `references/patterns.md`).\n- Mark obsolete `UpdateFromX` methods with `// This code can be removed in a later version.` but keep them until you drop support for that upgrade path.\n\n## References\n\n- `references/patterns.md` — content definition changes, content-item data patching, recipe migrations\n- `references/schema-builder.md` — index tables, columns, indexes, provider quirks\n- `src/docs/reference/modules/Migrations/README.md` (repo) — official reference\n- `AGENTS.md` (repo root) — build commands\n\n<!-- File: .agents/skills/orchardcore-display-management/references/drivers.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/drivers.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/drivers.md)\n\n<!-- File: .agents/skills/orchardcore-display-management/references/placement.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-display-management/references/placement.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-display-management/references/placement.md)\n\n<!-- File: .agents/skills/orchardcore-display-management/SKILL.md (Tokens: ~1849 | Category: anthropic-skill) -->\n---\nname: orchardcore-display-management\ndescription: Controls how OrchardCore renders content — placement.json rules, display drivers, shapes, zones, alternates, and editor groupings (tabs/cards/columns). Use when the user needs to position or hide a part/field, move a shape to another zone, add alternates or wrappers, build admin editor layouts, or write a display driver.\n---\n\n# OrchardCore Display Management\n\nThis skill guides you through OrchardCore's display system following project conventions.\n\nOrchardCore renders content by building **shapes** (dynamic view models) from drivers, then placing each shape into a **zone** of the layout. **Drivers** decide *what* shapes exist and their default location; **`placement.json`** overrides *where* they go, *whether* they show, and *which* template renders them.\n\n## Mental model\n\n```\nContentItem ──drivers──▶ shapes ──placement.json──▶ zones ──templates──▶ HTML\n```\n\n- **Shape** — a dynamic object with a `ShapeType` and `Metadata`. The shape type (e.g. `TextField`, `Parts_Contents_Publish`, `ContentPart`) selects the template and the placement rule.\n- **Driver** — `DisplayDriver` / `ContentPartDisplayDriver` / `ContentFieldDisplayDriver`. Returns `IDisplayResult`s from `Display`, `Edit`, `UpdateAsync`, each with a default `.Location(...)`.\n- **Placement** — `placement.json` at the **root** of a module or theme. Filters shapes and rewrites their location/visibility/template.\n- **Zone** — named region of the layout (`Content`, `Header`, `Footer`, `Actions`, …) or an editor group (tab/card/column).\n\n## Decide: driver vs placement\n\n| Want to… | Use |\n|----------|-----|\n| Reposition / hide a shape someone else built | `placement.json` |\n| Move a shape to a layout zone | `placement.json` (`place` starts with `/`) | \n| Swap the template or add alternates | `placement.json` (`shape` / `alternates`) |\n| Lay out the admin editor (tabs/cards/columns) | `placement.json` groupings |\n| Produce a *new* shape from your part/field | display driver |\n| Read/write the editor model | display driver (`Edit` / `UpdateAsync`) |\n\nPrefer `placement.json` for layout — no rebuild needed, theme can override.\n\n## Workflow A: place a shape\n\n### Step 1: Find the shape type and differentiator\n\nThe placement property name is the **shape type**, not the part name. A part **without** its own driver renders as `ContentPart` (differentiator = part name). A field renders as its field type (`TextField`) with differentiator `{PartName}-{FieldName}`.\n\nSee `references/placement.md` for the full differentiator table.\n\n### Step 2: Add a rule to `placement.json`\n\n```json\n{\n  \"TextField\": [\n    {\n      \"differentiator\": \"Article-Subtitle\",\n      \"displayType\": \"Detail\",\n      \"place\": \"Content:2\"\n    }\n  ]\n}\n```\n\n### Step 3: Verify\n\nRun the site, view the content item. Use `?display` debug shapes or the template's shape tracing to confirm the shape type. Wrong type → no rule matches.\n\n## Workflow B: write a display driver\n\n### Step 1: Inherit the right base\n\n| Base class | For |\n|------------|-----|\n| `ContentPartDisplayDriver<TPart>` | a content part |\n| `ContentFieldDisplayDriver<TField>` | a content field |\n| `SectionDisplayDriver<TModel, TSection>` | settings sections |\n| `DisplayDriver<TModel>` | anything else |\n\n### Step 2: Implement Display / Edit / Update\n\n```csharp\npublic sealed class TextFieldDisplayDriver : ContentFieldDisplayDriver<TextField>\n{\n    public override IDisplayResult Display(TextField field, BuildFieldDisplayContext context)\n    {\n        return Initialize<DisplayTextFieldViewModel>(GetDisplayShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        })\n        .Location(OrchardCoreConstants.DisplayType.Detail, \"Content\")\n        .Location(OrchardCoreConstants.DisplayType.Summary, \"Content\");\n    }\n\n    public override IDisplayResult Edit(TextField field, BuildFieldEditorContext context)\n    {\n        return Initialize<EditTextFieldViewModel>(GetEditorShapeType(context), model =>\n        {\n            model.Field = field;\n            model.Part = context.ContentPart;\n            model.PartFieldDefinition = context.PartFieldDefinition;\n        });\n    }\n\n    public override async Task<IDisplayResult> UpdateAsync(TextField field, UpdateFieldEditorContext context)\n    {\n        await context.Updater.TryUpdateModelAsync(field, Prefix, f => f.Text);\n        return Edit(field, context);\n    }\n}\n```\n\n### Step 3: Register the driver\n\nIn `Startup.cs`:\n\n```csharp\nservices.AddContentPartDisplayDriver<MyPart, MyPartDisplayDriver>();\n// or for a field:\nservices.AddContentFieldDisplayDriver<TextField, TextFieldDisplayDriver>();\n```\n\nSee `references/drivers.md` for shape-type helpers, the fluent `.Location` API, and registration variants.\n\n## Quick Reference\n\n### Placement rule keys\n\n| Key | Purpose |\n|-----|---------|\n| `place` | `Zone:Position`. `-` hides, `/Zone` targets a layout zone |\n| `displayType` | Match `Detail`, `Summary`, `SummaryAdmin`, `SummaryAdminGrid`, `DetailAdmin`, `Edit` |\n| `differentiator` | Distinguish reused shapes (field/part name) |\n| `contentType` | Match content type / stereotype (`Art*` wildcard, or array) |\n| `contentPart` | Match content items containing a part |\n| `path` | Match request path |\n| `alternates` | Add alternate shape types |\n| `wrappers` | Wrap the shape in other shapes |\n| `shape` | Substitute a different shape type |\n\n### Position values\n\n`before` < `0`/empty < `1` < `1.1` < `2` < `10` < `after`. Numeric, not alphabetic. Dot notation inserts between (`5.1` is between 5 and 6).\n\n### Editor grouping modifiers\n\n| Modifier | Group | Example |\n|----------|-------|---------|\n| `#` | Tab | `Parts:0#Media;1` |\n| `%` | Card | `Parts:0%Details;2` |\n| `\\|` | Column | `Parts:0\\|Left_9;1` (`_9` = width, `;1` = order) |\n\n`;N` after a group name = group order. `_N` after a column name = Bootstrap grid width (12-col).\n\n### Common zones\n\n| Zone | Purpose |\n|------|---------|\n| `Content` | Main content shapes |\n| `Header` / `Footer` | Layout regions |\n| `Actions` | Admin list row buttons (`SummaryAdmin` and `SummaryAdminGrid`) |\n| `Parts` | Editor parts container |\n| `Meta` | Metadata block |\n\n## Gotchas\n\n- Hiding a whole editor row → target `ContentPart_Edit` with differentiator `{ContentType}-{PartName}`, not the inner `XxxPart_Edit`.\n- Shape type ≠ part type. No driver → shape is `ContentPart`. Summary display of a dynamic part → `ContentPart_Summary`.\n- Field display modes are stricter: shape type needs `_Display`, differentiator is `{Part}-{Field}-{FieldType}_Display__{Mode}`.\n- Placement precedence: startup project → active theme → modules (by dependency). A theme can override any module's placement.\n- Admin list rows use `SummaryAdmin` in the List layout and `SummaryAdminGrid` in the Grid layout, where each zone is a column. Display types do not inherit placement, so a rule or a driver location for `SummaryAdmin` does not reach the grid; see `orchardcore-admin-index-views`.\n- Same position value → registration order decides; use distinct positions when order matters.\n\n## References\n\n- `references/placement.md` — placement.json filters, differentiators, layout zones, editor groupings\n- `references/drivers.md` — driver base classes, shape helpers, fluent Location API, registration\n- `src/docs/reference/modules/Placement/README.md` (repo) — official placement reference\n- `src/docs/reference/modules/DisplayManagement/README.md` (repo) — display management reference\n- `src/docs/reference/modules/Templates/README.md` (repo) — shape differentiators and alternates\n- `AGENTS.md` (repo root) — build commands\n\n<!-- File: .agents/skills/orchardcore-docs-writer/references/docs-structure.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-docs-writer/references/docs-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-docs-writer/references/docs-structure.md)\n\n<!-- File: .agents/skills/orchardcore-localization/references/localization.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-localization/references/localization.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-localization/references/localization.md)\n\n<!-- File: .agents/skills/orchardcore-localization/SKILL.md (Tokens: ~3376 | Category: anthropic-skill) -->\n---\nname: orchardcore-localization\ndescription: Localizes OrchardCore apps — UI strings (IStringLocalizer/IViewLocalizer S, T, H), LocalizationSource, updating and exporting checked-in POT templates, localization analyzers, PO files and pluralization, content translation (LocalizationPart, culture picker), and supported-culture configuration. Use when adding or changing localizable strings, updating POT files, fixing OCLOC diagnostics, translating strings, adding a culture, or making content multilingual.\n---\n\n# OrchardCore Localization\n\nThis skill guides you through OrchardCore localization following project conventions.\n\nThree layers, often confused:\n\n- **UI/code localization** — translate hard-coded strings in C#/Razor/Liquid via `IStringLocalizer` (`S`), `IViewLocalizer` (`T`), `IHtmlLocalizer` (`H`). Checked-in **POT templates** describe the source strings and are embedded in assemblies; translated **PO files** supply runtime translations.\n- **Content localization** — translate *content items* across cultures via `LocalizationPart` and the `OrchardCore.ContentLocalization` module.\n- **Culture configuration** — which cultures the site supports and how the request culture is chosen.\n\n## Decide which layer\n\n| Goal | Layer |\n|------|-------|\n| Translate a label/message in code or a view | UI localization (`S`/`T`/`H`) + PO file |\n| Pluralize (\"1 item\" / \"{0} items\") | `S.Plural(...)` + PO `msgid_plural` |\n| Add, change, remove, or move a source string | Update the owning project's checked-in POT template (Workflow D) |\n| Export source strings for Crowdin or another translation platform | Export POT templates (Workflow D), then deploy translated PO files |\n| Translate a blog post into French | Content localization (`LocalizationPart`) |\n| Add a supported language / set default | Localization settings (`LocalizationSettings`) |\n| Translate data-driven values (e.g. taxonomy terms) | Data Localization (`DataLocalization` module) |\n\n## Workflow A: localize UI strings\n\n### Step 1: Inject the right localizer\n\n| Localizer | Convention | Where |\n|-----------|-----------|-------|\n| `IStringLocalizer<T>` | field `S` | drivers, services, controllers |\n| `IViewLocalizer` | property `T` | Razor views (already on `RazorPage`) |\n| `IHtmlLocalizer<T>` | field `H` | when the translation contains HTML |\n\n```csharp\npublic sealed class AliasPartDisplayDriver : ContentPartDisplayDriver<AliasPart>\n{\n    internal readonly IStringLocalizer S;\n\n    public AliasPartDisplayDriver(IStringLocalizer<AliasPartDisplayDriver> localizer)\n        => S = localizer;\n}\n```\n\nIn Razor: `@T[\"Welcome to Orchard Core\"]`. In a class: `S[\"A value is required for {0}.\", name]`. Keep keys as compile-time constants and pass values as format arguments, not interpolated keys. In Liquid: `{{ \"Welcome to Orchard Core\" | t }}`.\n\n### Step 2: Pluralize when counting\n\n```csharp\nS.Plural(count, \"{0} book\", \"{0} books\");\n// IHtmlLocalizer: H.Plural(count, \"{0} book\", \"{0} books\")\n```\n\n### Step 3: Reuse shared sources for strings declared without a localizer\n\nStrings declared before a localizer is available, such as deployment step categories and titles, are `LocalizationSource` instances translated at display time with `IStringLocalizerFactory.Localize()`. Don't declare them in the class that uses them:\n\n- Use `CommonLocalizationSources` (`OrchardCore.Abstractions`, namespace `OrchardCore.Localization`) for text that several projects use with the same meaning, such as `CommonLocalizationSources.ContentManagement`, or that is general to every module, such as `CommonLocalizationSources.Settings`. Look for an existing member before adding one. Add a member only when another project uses the text too; a module name or a category used by one project goes in that project's class.\n- Declare the other strings in an `internal static class {Area}LocalizationSources` at the root of the module, for example `LuceneLocalizationSources`, with `LocalizationSource.Create(\"...\", typeof({Area}LocalizationSources))`, which extraction tools can find.\n- Keep permission descriptions in their `*Permissions` class, and strings specific to a view in the view, with `T`.\n\n```csharp\n[JsonIgnore] // Not inherited from the base property; LocalizationSource can't be serialized.\npublic override LocalizationSource Category => CommonLocalizationSources.Search;\n\n[JsonIgnore]\npublic override LocalizationSource Title => LuceneLocalizationSources.LuceneSearchIndexes;\n```\n\nThe PO context is the class that declares the source, for example `msgctxt \"OrchardCore.Localization.CommonLocalizationSources\"`.\n\nResolve a source at display time with `stringLocalizerFactory.Localize(source, args)` or `htmlLocalizerFactory.Localize(source, args)`, using the current tenant's factory. Pass arguments to the factory extension, not to `LocalizationSource.Create`; never cache a translated result in a static field. For plurals, use `stringLocalizerFactory.Plural(count, source, \"{0} items\")` (or the HTML factory equivalent), with a constant plural form. The singular source must have a context; `count` supplies `{0}` automatically.\n\n### Step 4: Provide PO translations\n\nPO files are discovered (in order) from each module's `Localization/`, a global `/Localization`, the tenant's `App_Data/Sites/{tenant}/Localization`, and culture folders. A PO entry:\n\n```po\nmsgctxt \"OrchardCore.Admin.Views.AdminDashboard\"\nmsgid \"Welcome to Orchard Core\"\nmsgstr \"Bienvenue sur Orchard Core\"\n```\n\n`msgctxt` = `{Namespace}.{Class}` (services) or `{Namespace}.{ViewPath}` (views). Plurals:\n\n```po\nmsgctxt \"OrchardCore.AdminMenu.Views.Menu.List\"\nmsgid \"1 item\"\nmsgid_plural \"{0} items\"\nmsgstr[0] \"{0} élément\"\nmsgstr[1] \"{0} éléments\"\n```\n\nSee `references/localization.md` for the full PO discovery order and headers.\n\n### Step 5: Update and commit the source templates\n\nAfter adding, changing, removing, or moving a localized string, update the affected project's POT file using Workflow D below. Context changes and moving shared sources can change translation identities even when the displayed text stays the same.\n\n## Workflow B: localize content\n\n### Step 1: Enable the feature, attach the part\n\nEnable `OrchardCore.ContentLocalization` (Content Localization). Attach `LocalizationPart` to the content type (admin or recipe). It carries:\n\n```csharp\npublic class LocalizationPart : ContentPart, ILocalizable\n{\n    public string LocalizationSet { get; set; } // links all translations\n    public string Culture { get; set; }         // this version's culture\n}\n```\n\nOn create, the part handler generates a unique `LocalizationSet` and sets the default culture automatically.\n\n### Step 2: Translate\n\nFrom the editor's Localization, \"Translate\" creates a sibling content item sharing the same `LocalizationSet` with a different `Culture`. Programmatically:\n\n```csharp\nvar translated = await _contentLocalizationManager.LocalizeAsync(contentItem, \"fr-FR\");\n```\n\n### Step 3: Surface translations / culture picker\n\n`IContentLocalizationManager` fetches across a set:\n\n```csharp\nawait _contentLocalizationManager.GetContentItemAsync(localizationSet, \"fr\");\nawait _contentLocalizationManager.GetItemsForSetAsync(localizationSet);\n```\n\nLiquid: `{{ Model.ContentItem.Content.LocalizationPart.LocalizationSet | localization_set: \"en\" }}`. The Content Culture Picker feature adds a front-end language switcher and a `ContentRequestCultureProvider` that derives the culture from the localized route.\n\n## Workflow C: configure cultures\n\nRecipe `settings` step:\n\n```json\n{\n  \"name\": \"settings\",\n  \"LocalizationSettings\": {\n    \"DefaultCulture\": \"en-US\",\n    \"SupportedCultures\": [ \"en-US\", \"fr-FR\", \"es-ES\" ],\n    \"FallBackToParentCulture\": true\n  }\n}\n```\n\nThe Localization module reads these at startup and calls `app.UseRequestLocalization(...)` with the configured default + supported cultures.\n\n## Workflow D: update and export POT templates\n\n### Step 1: Use the existing build integration\n\nIn this repository, `OrchardCore.Commons.props` already imports the localization targets for framework projects, modules, and themes. Do not add redundant imports or package references. Outside the repository, each project whose strings need extraction must reference `OrchardCore.Localization.Build` with `PrivateAssets=\"all\"` and a version matching Orchard Core.\n\nEach project's template is `<AssemblyName>.pot`, next to its project file. A **normal build only embeds an existing template** as `<AssemblyName>.Localization.pot`; it neither extracts strings nor refreshes a stale template. A project without a template embeds nothing. `GenerateLocalizationCatalog=true` enables this integration, not automatic extraction.\n\n### Step 2: Regenerate from source\n\nRun commands from the repository root. Prefer updating just the affected project:\n\n```powershell\ndotnet msbuild src\\OrchardCore.Modules\\OrchardCore.Admin\\OrchardCore.Admin.csproj -t:UpdateLocalizationCatalog\n```\n\nUse `-restore` with `dotnet msbuild` when dependencies need restoring. The target updates only that project's template, not those of its references. For an application and its project references, or the entire solution:\n\n```powershell\ndotnet build src\\OrchardCore.Cms.Web\\OrchardCore.Cms.Web.csproj -p:UpdateLocalizationCatalogs=true\ndotnet build OrchardCore.slnx -p:UpdateLocalizationCatalogs=true\n```\n\nChoose the appropriate scope, rather than running every command. Use the SDK selected by `global.json`: extraction loads that SDK's Roslyn and Razor compilers. The source-tree worker is built on demand; no separate PoExtractor installation is needed.\n\nUpdates create or rewrite the template, or remove it if no messages remain. Unchanged inputs reuse the cache under `obj\\`; unchanged output is not rewritten. Do not hand-edit generated entries, headers, or references. Templates use deterministic ordering, UTF-8 and LF, no timestamps, and file references without line numbers by default. Multi-targeted projects extract using their first target framework; `DEBUG` and `TRACE` are excluded during extraction.\n\n### Step 3: Review the result and diagnostics\n\n```powershell\ngit status --short -- '*.pot'\ngit --no-pager diff -- '*.pot'\n```\n\nReview new files too (a normal `git diff` does not show untracked templates). Check the expected `msgctxt`, `msgid`, `msgid_plural`, source references, format flags, and format-argument comments. Keep `msgstr` values empty in POT files; translations belong in culture-specific PO files. Commit added, modified, and deleted POT files with their source changes. CI updates the solution's templates and fails if any are uncommitted or out of date.\n\nExtraction covers C#, Razor, literal Liquid `t` inputs, context-bearing `LocalizationSource` declarations, and explicit constant DataAnnotations validation messages. It does not extract JavaScript/TypeScript directly: expose their strings through a C# `IJSLocalizer` implementation. Fix dynamic keys or unresolved contexts at the source instead of inserting guessed POT entries.\n\nIDE/build analyzers report `OCLOC101` (constant source missing a context), `OCLOC102` (different declaring context, informational), `OCLOC103` (nonconstant source value), and `OCLOC104` (nonconstant localizer key or plural forms). The update worker reports `OCLOC001`-`OCLOC009`; see the reference for their meanings. Extraction errors always fail; `-p:LocalizationStrict=true` also fails on extraction warnings.\n\nUse `[SkipLocalizationExtraction]` only on forwarding infrastructure whose keys are declared elsewhere; it excludes marked classes/methods and nested declarations from extraction and analyzer diagnostics, not application calls to those helpers. For an intentionally dynamic application call, locally suppress `OCLOC104` with an explanation of where its keys originate. Do not hide missing application strings with broad suppressions.\n\n### Step 4: Export templates when requested\n\nBuild with an **absolute**, dedicated output directory to collect existing templates without regenerating them:\n\n```powershell\n$output = Join-Path $PWD 'artifacts\\localization'\ndotnet build OrchardCore.slnx \"-p:LocalizationCatalogOutputPath=$output\"\n```\n\nFiles are copied to `<output>\\<TargetFramework>\\<AssemblyName>.pot`. Add `-p:UpdateLocalizationCatalogs=true` only when an update is also intended. Use an empty export directory for an exact current snapshot; export does not remove stale files. Only built projects contribute templates, not prebuilt NuGet dependencies. Upload POT files to the translation platform, preserve contexts/plurals/placeholders, and download translated PO files to a supported runtime localization location. Embedded POT files alone do not change runtime translations.\n\nFor configuration properties, custom Liquid inputs, extraction diagnostics, and library entry points, see `references/localization.md`.\n\n## Quick Reference\n\n### Localizers\n\n| Symbol | Type | Output |\n|--------|------|--------|\n| `S` | `IStringLocalizer<T>` | plain string |\n| `T` | `IViewLocalizer` | encoded view string |\n| `H` | `IHtmlLocalizer<T>` | trusted translation markup; format arguments are HTML-encoded |\n\n### Content localization APIs\n\n| Member | Purpose |\n|--------|---------|\n| `IContentLocalizationManager.LocalizeAsync(item, culture)` | create a translation |\n| `.GetContentItemAsync(set, culture)` | one translation |\n| `.GetItemsForSetAsync(set)` | all translations |\n| `.GetItemsForSetsAsync(sets, culture)` | batch |\n| `ILocalizationService.GetDefaultCultureAsync()` / `GetSupportedCulturesAsync()` | culture config |\n\n### Request culture providers\n\n| Provider | Picks culture from |\n|----------|--------------------|\n| `ContentRequestCultureProvider` | the localized content route (added first) |\n| `AdminCookieCultureProvider` | `admin_culture_*` cookie, admin paths only |\n| cookie / querystring / Accept-Language | ASP.NET Core defaults |\n\n### Liquid filters\n\n`localization_set: \"fr\"` — translated item; `switch_culture_url` — culture-switch URL.\n\n## Gotchas\n\n- `msgctxt` must exactly match the namespace+class (or namespace+view path). Wrong context → string stays untranslated.\n- `IStringLocalizer<T>` context comes from `T`'s type — inject the localizer typed to the class that owns the strings.\n- `LocalizationSet` links translations; never share one set across unrelated items, and don't overwrite it.\n- Add a culture to `SupportedCultures` before content can be translated into it.\n- DataAnnotations need `AddDataAnnotationsPortableObjectLocalization()` (the Localization module registers it).\n- Ordinary builds do not refresh POT templates; always explicitly update and commit them with string/context changes.\n- A gettext source plural has exactly two constant forms; translated PO files can have more forms, selected by the culture's plural rule.\n- Set `GenerateLocalizationCatalog=false` only when intentionally disabling both embedding and updating, not as a fix for extraction errors.\n\n## References\n\n- `references/localization.md` — POT configuration and diagnostics, extraction APIs, PO discovery order, contexts, plural forms, content localization internals, startup wiring\n- `src/docs/reference/modules/Localize/README.md` (repo)\n- `src/docs/reference/modules/ContentLocalization/README.md` (repo)\n- `src/docs/reference/modules/DataLocalization/README.md` (repo)\n- `src/docs/reference/modules/Localize/javascript-localization.md` (repo) — `IJSLocalizer` bridge; no direct JavaScript extraction\n- `AGENTS.md` (repo root) — build commands\n\n<!-- File: .agents/skills/orchardcore-module-creator/references/examples.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/examples.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/examples.md)\n\n<!-- File: .agents/skills/orchardcore-module-creator/references/module-structure.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/module-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/module-structure.md)\n\n<!-- File: .agents/skills/orchardcore-module-creator/references/patterns.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-module-creator/references/patterns.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-module-creator/references/patterns.md)\n\n<!-- File: .agents/skills/orchardcore-module-creator/SKILL.md (Tokens: ~1046 | Category: anthropic-skill) -->\n---\nname: orchardcore-module-creator\ndescription: Creates new OrchardCore modules with proper structure, manifest, startup, and patterns. Use when the user needs to create a new module, add content parts, fields, drivers, handlers, or admin functionality.\n---\n\n# OrchardCore Module Creator\n\nThis skill guides you through creating new OrchardCore modules following project conventions.\n\n## Prerequisites\n\n- OrchardCore repository at `D:\\orchardcore`\n- .NET SDK 10.0+ installed\n\n## Module Creation Workflow\n\n### Step 1: Determine Module Type\n\n**What kind of module are you creating?**\n\n| Type | Description | Key Components |\n|------|-------------|----------------|\n| **Content Part** | Adds data/behavior to content items | Part, Driver, Views |\n| **Content Field** | Custom field type | Field, Driver, Views |\n| **Settings** | Site-wide configuration | SiteSettings, Driver |\n| **Admin Feature** | Admin pages/tools | Controller, Views, Menu |\n| **API** | REST endpoints | ApiController |\n| **Background Task** | Scheduled jobs | IBackgroundTask |\n\n### Step 2: Create Module Directory\n\n```bash\n# Create module folder\nmkdir src/OrchardCore.Modules/OrchardCore.YourModule\ncd src/OrchardCore.Modules/OrchardCore.YourModule\n```\n\n### Step 3: Create Required Files\n\nEvery module needs these three files:\n\n1. **Manifest.cs** - Module metadata\n2. **Startup.cs** - Service registration  \n3. **OrchardCore.YourModule.csproj** - Project file\n\nSee `references/module-structure.md` for templates.\n\n### Step 4: Add Components Based on Type\n\n**For Content Part modules:**\n```\nModels/YourPart.cs\nViewModels/YourPartViewModel.cs\nDrivers/YourPartDisplayDriver.cs\nViews/YourPart.cshtml\nViews/YourPart_Edit.cshtml\n```\n\n**For Admin modules:**\n```\nControllers/AdminController.cs\nViews/Admin/Index.cshtml\nAdminMenu.cs\nPermissionProvider.cs\n```\n\n**For Data-storing modules:**\n```\nMigrations.cs\nIndexes/YourIndex.cs\n```\n\nSee `references/patterns.md` for code templates.\n\n### Step 5: Register in Startup.cs\n\n```csharp\npublic override void ConfigureServices(IServiceCollection services)\n{\n    // Content part\n    services.AddContentPart<YourPart>()\n        .UseDisplayDriver<YourPartDisplayDriver>();\n    \n    // Services\n    services.AddScoped<IYourService, YourService>();\n    \n    // Migrations (if storing data)\n    services.AddDataMigration<Migrations>();\n    \n    // Permissions (if securing features)\n    services.AddPermissionProvider<PermissionProvider>();\n    \n    // Navigation (if adding admin menu)\n    services.AddNavigationProvider<AdminMenu>();\n}\n```\n\n### Step 6: Build and Test\n\n```bash\n# Build the module\ncd D:\\orchardcore\ndotnet build src/OrchardCore.Modules/OrchardCore.YourModule\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n\n# Enable the feature in Admin → Features\n```\n\n## Quick Reference\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Module folder | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Namespace | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Feature ID | `OrchardCore.ModuleName` | `OrchardCore.Rating` |\n| Content Part | `NamePart` | `RatingPart` |\n| Driver | `NamePartDisplayDriver` | `RatingPartDisplayDriver` |\n| View | `PartName.cshtml` | `RatingPart.cshtml` |\n| Edit View | `PartName_Edit.cshtml` | `RatingPart_Edit.cshtml` |\n\n### Common Dependencies\n\nAdd to `.csproj` as needed:\n\n```xml\n<!-- Core module support -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Module.Targets\\OrchardCore.Module.Targets.csproj\" />\n\n<!-- Content management -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.ContentManagement\\OrchardCore.ContentManagement.csproj\" />\n\n<!-- Admin UI -->\n<ProjectReference Include=\"..\\..\\OrchardCore\\OrchardCore.Admin\\OrchardCore.Admin.csproj\" />\n```\n\n### Feature Categories\n\nUse in `Manifest.cs`:\n- `Content Management`\n- `Content`\n- `Navigation`\n- `Security`\n- `Infrastructure`\n- `Theming`\n- `Developer`\n\n## References\n\n- `references/module-structure.md` - Directory layout and file templates\n- `references/patterns.md` - Code patterns (parts, drivers, handlers, etc.)\n- `references/examples.md` - Complete module examples\n- `AGENTS.md` (repo root) - Coding conventions and build commands\n\n<!-- File: .agents/skills/orchardcore-nswag-regenerate/SKILL.md (Tokens: ~1864 | Category: anthropic-skill) -->\n---\nname: orchardcore-nswag-regenerate\ndescription: Regenerate the OrchardCore.OpenApi module's NSwag-generated C#/TypeScript API clients, and verify the regeneration produces a stable (non-reshuffled) diff. Use when asked to \"regenerate the NSwag client(s)\", update Services/OpenApiClient.cs or .scripts/bloom/services/OpenApiClient.ts, or investigate noisy diffs from NSwag regeneration.\n---\n\n# OrchardCore NSwag Regenerate\n\nRegenerates the OpenApi module's NSwag-generated C#/TypeScript API clients and verifies the regeneration produces a stable, non-reshuffled diff.\n\n## Preferred: `tools/OpenApiClientGenerator` (automated, no browser/dev-server needed)\n\nA console project that does the whole pipeline headlessly:\n\n```bash\ndotnet run --project tools/OpenApiClientGenerator -c Release\nyarn build   # refreshes the Vue app's bundled JS against the new TS client\n```\n\nIt boots the CMS in-process on an ephemeral port, uses `OrchardCore.AutoSetup` with\n`src/OrchardCore.Cms.Web/Recipes/openapi-generation-setup.recipe.json` (`issetuprecipe: true`)\nto provision the **Default** tenant with the same feature set as `openapi-generation.recipe.json`\nbelow, fetches `swagger.json`, writes a scratch `.nswag` config pointing at that capture, and\nshells out to the `nswag` CLI (same prerequisite as the manual path: `dotnet tool install -g\nNSwag.ConsoleCore`). Source: `tools/OpenApiClientGenerator/Program.cs`.\n\n**Why Default tenant specifically**: `OrchardCore.Tenants` (and `.Distributed`,\n`.FeatureProfiles`) have `DefaultTenantOnly = true` in their `[Feature]` manifest attribute —\nthey can only be enabled on the Default/Host shell, never a secondary tenant. A Blog-recipe\nfunctional-test tenant cannot enable `OrchardCore.Tenants`, which is why this tool targets the\nDefault shell via `AutoSetup` rather than reusing the functional-test harness's tenant pattern.\n\nThis tool only regenerates the clients against whatever API surface the current source produces\n— it does not add a CI drift-check gate (that was scoped out; see `nswag-automation-plan.md` in\ngit stash history for the fuller original proposal, \"Part 1\" of which this tool implements).\n\nThe manual procedure below still applies if you need to regenerate against a real running dev\nserver (e.g. to inspect `swagger.json` interactively) or investigate the tool's own behavior.\n\n## Where things live\n\n- Config: `src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag` — **this file already exists**, do not assume it's missing. It defines both generators:\n  - `openApiToCSharpClient` → outputs `Services/OpenApiClient.cs` (relative to the module folder)\n  - `openApiToTypeScriptClient` → outputs `../../../.scripts/bloom/services/OpenApiClient.ts`\n- Source document: `documentGenerator.fromDocument.url` points at a running instance's Swashbuckle endpoint, `https://localhost:5001/swagger/v1/swagger.json`. NSwag also accepts a **local file path** in this same field instead of a URL — useful for reproducible/offline generation (see below).\n- Recipe: `src/OrchardCore.Modules/OrchardCore.OpenApi/Recipes/openapi-generation.recipe.json` (name `OpenApiGeneration`) enables every feature that exposes an API endpoint: `OrchardCore.Contents`, `OrchardCore.Queries`, `OrchardCore.Tenants`, `OrchardCore.Search.Lucene`, `OrchardCore.Search.Elasticsearch`, `OrchardCore.OpenApi`. It also contains a `Settings` step setting `OpenApiSettings.AllowAnonymousSchemaAccess: true` — anonymous schema access is **disabled by default** (the middleware returns 401 for unauthenticated `swagger.json` fetches), and both NSwag paths below fetch the schema anonymously. The setup recipe used by the generator tool (`openapi-generation-setup.recipe.json`) has the same step. Note the `Settings` step replaces the whole stored `OpenApiSettings` object, wiping any OAuth configuration on the tenant it runs on — fine for throwaway generation tenants, worth knowing on a configured one.\n  - It has `\"issetuprecipe\": false`, so it does **not** appear in the new-tenant/site-setup recipe dropdown. Run it against an already-set-up tenant instead, via **Admin ▸ Configuration ▸ Recipes**, click \"Run\" next to \"OpenApi Generation\", confirm the modal. (`/Admin/Recipes`, `AdminController.Execute` in `OrchardCore.Recipes`.)\n\n## Manual procedure (fallback)\n\n### NSwag CLI\n\nInstalled as a dotnet tool: `~/.dotnet/tools/nswag` (or `dotnet tool install -g NSwag.ConsoleCore` if missing). Run with:\n\n```bash\nnswag run src/OrchardCore.Modules/OrchardCore.OpenApi/OrchardCore.OpenApi.nswag\n```\n\nThis requires a live server at the configured `url`. For a real regeneration that updates the committed files, run the actual local dev server, set it up with the `OpenApiGeneration` recipe, and run the command above unmodified.\n\n## Reproducible / offline generation (for testing determinism, without touching committed files)\n\n1. Fetch `swagger.json` from a running instance into a scratch file.\n2. Copy the `.nswag` config, and in the copy only change `documentGenerator.fromDocument.url` to the scratch file's path, and both generators' `output` to scratch paths. Do this with a small Python/jq one-liner rather than hand-editing — the config is plain JSON.\n3. `nswag run <scratch-config>`.\n\nThis lets you diff two independent generations without ever touching the real generated files or needing HTTPS/dev-cert setup.\n\n## Determinism: why regeneration used to reshuffle unrelated methods\n\nTwo independent root causes were found and fixed (skrypt/openapi branch):\n\n1. **Swashbuckle's operation order wasn't pinned.** Without an explicit sort, `swagger.json` operations come out in whatever order ASP.NET Core's action discovery enumerates them — which depends on assembly/feature load order in OrchardCore's modular architecture, not source order. Fixed in `OrchardCore.OpenApi/Startup.cs`'s `AddSwaggerGen` call:\n   ```csharp\n   c.OrderActionsBy(apiDesc => $\"{apiDesc.RelativePath}_{apiDesc.HttpMethod}\");\n   ```\n   Route path + verb is fixed at compile time and unique per operation, so this is stable regardless of load order.\n\n2. **A duplicate `operationId` across two operations.** `QueryApiController` used to have one action handling both `GET` and `POST` on `api/queries/{name}` under a single `[EndpointName(\"ApiExecuteQuery\")]`. OpenAPI requires `operationId` to be unique per operation — reusing one across two operations forced NSwag to invent a disambiguating suffix internally, and that suffix logic wasn't deterministic across runs (e.g. `ApiExecuteQueryPOSTAsync` vs `ApiExecuteQueryPOSTPOSTAsync`). Fixed by splitting into two actions with distinct names (`ApiExecuteQueryGet` / `ApiExecuteQueryPost`), delegating to a shared private method — the pattern already used by e.g. `ElasticsearchApiController` (`ApiGetElasticsearchContent`/`ApiPostElasticsearchContent`).\n\n**Lesson for any future controller/endpoint added to the OpenApi-exposed surface**: never reuse the same `[EndpointName]`/`operationId` across two different HTTP verbs on the same route. Give each verb its own action and its own name, even if they share implementation via a private helper.\n\n## Verifying stability empirically\n\nBoot two independently-provisioned tenants (each gets its own `ShellContext` and fresh feature/extension discovery — this is what actually varies, not wall-clock time), run the `OpenApiGeneration` recipe on each, fetch `swagger.json` from each, generate against both, and diff. A stable setup produces byte-identical output except for the tenant's own base-URL prefix embedded in the client's default `baseUrl` (an expected, real difference between distinct tenants — not a bug).\n\nThe existing functional test suite (`test/OrchardCore.Tests.Functional/Tests/Cms/OpenApiTests.cs`) already exercises the relevant plumbing (feature enablement, swagger.json access) if you need a template for scripting this via Playwright/`CmsTestBase`. Any throwaway verification test written for this should be deleted afterward — it's not meant to be a permanent part of the suite.\n\n<!-- File: .agents/skills/orchardcore-query-indexing/references/indexing.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-query-indexing/references/indexing.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-query-indexing/references/indexing.md)\n\n<!-- File: .agents/skills/orchardcore-query-indexing/SKILL.md (Tokens: ~1781 | Category: anthropic-skill) -->\n---\nname: orchardcore-query-indexing\ndescription: Works with OrchardCore queries and indexing — SQL/Lucene/Elasticsearch queries, full-text index profiles, content part/field index handlers, and search. Use when the user needs to define a query, index content for search, write an index handler, register an indexing source, or add query/index recipe steps.\n---\n\n# OrchardCore Query & Indexing\n\nThis skill guides you through OrchardCore's query and indexing systems following project conventions.\n\nTwo distinct systems, often confused:\n\n- **Queries** (`OrchardCore.Queries`) — named, stored queries executed on demand (`SQL`, `Lucene`, `Elasticsearch` sources). Return raw rows or content items.\n- **Indexing** (`OrchardCore.Indexing` + providers) — build full-text **index profiles** that mirror content into Lucene/Elasticsearch/Azure AI Search for searching. Driven by **index handlers**.\n\nSeparate again from **YesSql SQL index tables** (`MapIndex` + `SchemaBuilder`) — those are relational indexes for fast DB queries, covered by the `orchardcore-data-migration` skill, not here.\n\n## Decide what you need\n\n| Goal | System |\n|------|--------|\n| Run a stored SQL/Lucene query, expose via GraphQL/API | Queries |\n| Make content searchable (full-text) | Indexing (index profile + provider) |\n| Index a custom part/field's data into search | `IContentPartIndexHandler` / `IContentFieldIndexHandler` |\n| Fast relational lookup by a column | YesSql `MapIndex` → see `orchardcore-data-migration` |\n| Run a search box over an index | Search (`ISearchService`) |\n\n## Workflow A: define and run a query\n\n### Step 1: Pick a source\n\n`Query` has `Name`, `Source` (`Sql`/`Lucene`/`Elasticsearch`), `ReturnContentItems`, and source-specific metadata (e.g. a SQL `Template`).\n\n### Step 2: Create via recipe or manager\n\nRecipe step:\n\n```json\n{\n  \"name\": \"queries\",\n  \"Queries\": [\n    {\n      \"Source\": \"Sql\",\n      \"Name\": \"RecentPosts\",\n      \"Template\": \"select DocumentId from ContentItemIndex where ContentType = 'BlogPost'\",\n      \"ReturnContentItems\": true\n    }\n  ]\n}\n```\n\nProgrammatically:\n\n```csharp\nvar query = await _queryManager.NewAsync(\"Sql\", data);\nawait _queryManager.SaveAsync(query);\n```\n\n### Step 3: Execute\n\n```csharp\nvar query = await _queryManager.GetQueryAsync(\"RecentPosts\");\nvar results = await _queryManager.ExecuteQueryAsync(query, new Dictionary<string, object>\n{\n    [\"maxResults\"] = 10,\n});\nforeach (var item in results.Items) { /* JsonObject or ContentItem */ }\n```\n\nSQL templates are Liquid-rendered (parameters become Liquid vars), parsed by `SqlParser`, then run via Dapper. `ReturnContentItems = true` loads `ContentItem`s by `DocumentId`; otherwise rows are returned as `JsonObject`.\n\n## Workflow B: index content for search\n\n### Step 1: Create an index profile\n\nA profile binds a provider (`Lucene`/`Elasticsearch`/`AzureAISearch`) + a type (`Content`) + which content types to index.\n\n```json\n{\n  \"name\": \"CreateOrUpdateIndexProfile\",\n  \"indexes\": [\n    {\n      \"Name\": \"BlogPostsLucene\",\n      \"IndexName\": \"blogposts\",\n      \"ProviderName\": \"Lucene\",\n      \"Type\": \"Content\",\n      \"Properties\": {\n        \"ContentIndexMetadata\": {\n          \"IndexLatest\": false,\n          \"IndexedContentTypes\": [ \"BlogPost\" ],\n          \"Culture\": \"any\"\n        },\n        \"LuceneIndexMetadata\": { \"AnalyzerName\": \"standard\", \"StoreSourceData\": true }\n      }\n    }\n  ]\n}\n```\n\nContent changes raise indexing tasks; `ContentIndexingService` resolves the content item and calls every part/field index handler to build a `DocumentIndex`, which the provider stores.\n\n### Step 2: Index custom part/field data\n\nFor a custom part, implement `ContentPartIndexHandler<TPart>`:\n\n```csharp\npublic class AliasPartIndexHandler : ContentPartIndexHandler<AliasPart>\n{\n    public override Task BuildIndexAsync(AliasPart part, BuildPartIndexContext context)\n    {\n        var options = DocumentIndexOptions.Keyword | DocumentIndexOptions.Store;\n        foreach (var key in context.Keys)\n        {\n            context.DocumentIndex.Set(key, part.Alias, options);\n        }\n        return Task.CompletedTask;\n    }\n}\n```\n\nFields use `ContentFieldIndexHandler<TField>` with `BuildFieldIndexContext`. `context.Keys` are the index field names to populate; `DocumentIndexOptions` flags control storage:\n\n| Flag | Effect |\n|------|--------|\n| `Store` | keep the value retrievable from the index |\n| `Keyword` | index as a non-analyzed keyword (exact match) |\n| `Sanitize` | strip HTML before indexing |\n| `None` | analyzed full-text (default text) |\n\n### Step 3: Register the handler\n\n```csharp\nservices.AddScoped<IContentPartIndexHandler, AliasPartIndexHandler>();\n// field:\nservices.AddScoped<IContentFieldIndexHandler, MyFieldIndexHandler>();\n```\n\n### Step 4: Reset / rebuild\n\nRecipe steps `ResetIndex` (re-sync from last position) and `RebuildIndex` (drop + rebuild) manage existing indexes. The admin Search/Indexing UI exposes the same.\n\n## Quick Reference\n\n### Query APIs\n\n| Type | Member |\n|------|--------|\n| `IQueryManager` | `NewAsync`, `SaveAsync`, `GetQueryAsync`, `ExecuteQueryAsync`, `ListQueriesAsync` |\n| `IQuerySource` | `Name`, `ExecuteQueryAsync(query, parameters)` — implement to add a source |\n| `IQueryResults` | `IEnumerable<object> Items` |\n\n### Indexing APIs\n\n| Type | Member |\n|------|--------|\n| `IIndexProfileManager` | `NewAsync`, `CreateAsync`, `UpdateAsync`, `FindByNameAsync`, `SynchronizeAsync`, `ResetAsync` |\n| `IDocumentIndexHandler` | `BuildIndexAsync`, `DocumentsAddedOrUpdatedAsync`, `DocumentsDeletedAsync` |\n| `IContentPartIndexHandler` / `IContentFieldIndexHandler` | `BuildIndexAsync(...)` to populate `DocumentIndex` |\n| `IIndexManager` (per provider) | `CreateAsync`, `RebuildAsync`, `DeleteAsync`, `ExistsAsync` |\n| `IDocumentIndexManager` | `AddOrUpdateDocumentsAsync`, `DeleteDocumentsAsync` |\n| `DocumentIndex` | `Set(name, value, options)` overloads; `Types` enum |\n\n### Register an indexing source\n\n```csharp\nservices.AddIndexingSource<TIndexManager, TDocumentIndexManager, TIndexNameProvider>(\n    providerName, implementationType);\n// e.g. the Lucene module: services.AddLuceneIndexingSource(implementationType);\n```\n\n### Search\n\n`ISearchService.SearchAsync(IndexProfile index, string term, int start, int size)` → `SearchResult`. Providers: `OrchardCore.Search.Lucene`, `.Elasticsearch`, `.AzureAI`.\n\n## Gotchas\n\n- \"Index\" is overloaded: full-text **index profiles** (this skill) vs YesSql **MapIndex tables** (`orchardcore-data-migration`). Don't mix them.\n- Query recipe step is `queries`; index profile step is `CreateOrUpdateIndexProfile`. Older `lucene-index` / `ElasticIndexSettings` steps are deprecated — prefer `CreateOrUpdateIndexProfile`.\n- `IndexLatest: true` indexes draft (latest) versions; `false` indexes published. Match to your search UI.\n- SQL query `Template` is Liquid first, then SQL — parameters are Liquid variables, not ADO placeholders.\n\n## References\n\n- `references/indexing.md` — index profiles, handlers, DocumentIndex, providers, recipe steps\n- `src/docs/reference/modules/Queries/README.md` (repo)\n- `src/docs/reference/modules/Indexing/README.md` (repo)\n- `src/docs/reference/modules/{Lucene,Elasticsearch,SQLIndexing}/README.md` (repo)\n- `AGENTS.md` (repo root) — build commands\n\n<!-- File: .agents/skills/orchardcore-recipe-creator/references/recipe-structure.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/recipe-structure.md)\n\n<!-- File: .agents/skills/orchardcore-recipe-creator/references/steps.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-recipe-creator/references/steps.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-recipe-creator/references/steps.md)\n\n<!-- File: .agents/skills/orchardcore-recipe-creator/SKILL.md (Tokens: ~1684 | Category: anthropic-skill) -->\n---\nname: orchardcore-recipe-creator\ndescription: Creates OrchardCore setup recipes (.recipe.json) that provision a tenant on setup — enabling features, setting themes, defining content types, importing content, roles, and settings. Use when the user needs to create a setup recipe, a starter recipe, or automate tenant configuration.\n---\n\n# OrchardCore Setup Recipe Creator\n\nThis skill guides you through creating a **setup recipe** for OrchardCore. A setup recipe is a `.recipe.json` file that runs when a tenant is created, provisioning everything the site needs: enabled features, active themes, content definitions, content items, roles, and settings.\n\n## Prerequisites\n\n- OrchardCore repository (working directory)\n- A module or theme to host the recipe (recipes live in a `Recipes/` folder)\n- Basic knowledge of the features your site needs\n\n## What makes a recipe a *setup* recipe\n\nA setup recipe is a normal recipe with `\"issetuprecipe\": true` in its header. That flag makes it appear in the setup screen's recipe dropdown so a user can pick it when creating a tenant. Without it, the recipe is still executable from the admin (Configuration → Recipes) but won't show during setup.\n\n## Setup Recipe Workflow\n\n### Step 1: Choose the host and create the Recipes folder\n\nRecipes are discovered from a `Recipes/` folder inside any enabled module or theme:\n\n```bash\nmkdir src/OrchardCore.Themes/YourTheme/Recipes\n```\n\nThe file name must end with `.recipe.json`:\n\n```\nsrc/OrchardCore.Themes/YourTheme/Recipes/yoursite.recipe.json\n```\n\n### Step 2: Write the recipe header\n\n```json\n{\n  \"name\": \"YourSite\",\n  \"displayName\": \"Your Site\",\n  \"description\": \"Provisions a Your Site tenant with content and theme.\",\n  \"author\": \"The Orchard Core Team\",\n  \"website\": \"https://orchardcore.net\",\n  \"version\": \"1.0.0\",\n  \"issetuprecipe\": true,\n  \"categories\": [ \"default\" ],\n  \"tags\": [ \"yoursite\" ],\n  \"variables\": {\n    \"homeContentItemId\": \"[js:uuid()]\"\n  },\n  \"steps\": []\n}\n```\n\n`variables` are evaluated on first access and reused across steps — use them for content item ids so steps can reference the same item. Recipe JSON may contain `// comments`.\n\n### Step 3: Add the core steps in order\n\nSteps run **top to bottom**. A typical setup recipe order:\n\n1. `feature` — enable the modules and theme features the site needs.\n2. `themes` — set the active site and admin themes.\n3. `settings` — site-wide settings (home route, culture, time zone).\n4. `Roles` — roles and their permissions.\n5. `ContentDefinition` — content types and parts.\n6. `content` — the actual content items (pages, menus, widgets).\n7. `media`, `queries`, `layers`, `Templates`, `AdminMenu` — as needed.\n\nMinimum viable setup recipe:\n\n```json\n\"steps\": [\n  {\n    \"name\": \"feature\",\n    \"enable\": [\n      \"OrchardCore.Admin\",\n      \"OrchardCore.Contents\",\n      \"OrchardCore.ContentTypes\",\n      \"OrchardCore.Title\",\n      \"OrchardCore.Autoroute\",\n      \"OrchardCore.Alias\",\n      \"OrchardCore.Themes\",\n      \"OrchardCore.HomeRoute\",\n      \"TheTheme\",\n      \"YourTheme\"\n    ]\n  },\n  {\n    \"name\": \"themes\",\n    \"admin\": \"TheAdmin\",\n    \"site\": \"YourTheme\"\n  }\n]\n```\n\n!!! warning\n    Always enable your own theme's feature in the `feature` step. If you set it as the `site` theme without enabling it, the layout won't render after setup.\n\n### Step 4: Reference the full step catalog\n\nFor every built-in step (`feature`, `themes`, `settings`, `ContentDefinition`, `content`, `media`, `Roles`, `layers`, `queries`, `Templates`, `AdminMenu`, `WorkflowType`, `deployment`, `custom-settings`, `recipes`, search indexes), see `references/steps.md`.\n\n### Step 5: Use recipe helpers for dynamic values\n\nValues can embed expressions with `[helper:...]` syntax:\n\n| Helper | Example | Use |\n|--------|---------|-----|\n| `js` | `\"[js:variables('homeContentItemId')]\"` | Reference variables / JS expressions |\n| `uuid` | `\"[js:uuid()]\"` | Generate a new content item id |\n| `file` | `\"[file:text('Snippets/home.liquid')]\"` | Inline an external file's text |\n| `env` | `\"[env:MY_VAR]\"` | Read an environment variable |\n| `appsettings` | `\"[appsettings:OrchardCore:SiteName]\"` | Read appsettings config |\n| `localization` | `\"[localization:WelcomeTitle]\"` | Localized string |\n| `base64` / `gzip` | `\"[js:base64('...')]\"` | Decode embedded binary/text |\n\n### Step 6: Test the recipe\n\n```bash\n# Build the host module/theme\ndotnet build src/OrchardCore.Themes/YourTheme\n\n# Run the application\ncd src/OrchardCore.Cms.Web\ndotnet run -f net10.0\n```\n\nThen create a new tenant (or reset the default one) and pick **Your Site** in the setup screen's recipe list. Verify features, theme, and content provisioned correctly.\n\nTo re-run a non-setup recipe without a fresh tenant: Admin → Configuration → Recipes → Run.\n\n## Quick Reference\n\n### Header properties\n\n| Property | Purpose |\n|----------|---------|\n| `name` | Unique internal id (used by the `recipes` step to call it) |\n| `displayName` | Friendly name in setup/admin UI |\n| `description` | Short summary shown to the user |\n| `issetuprecipe` | `true` makes it selectable during tenant setup |\n| `categories` / `tags` | Grouping/filtering in the UI |\n| `variables` | Reusable values, evaluated once on first access |\n| `steps` | Ordered actions to execute |\n\n### Common feature ids\n\n| Area | Features |\n|------|----------|\n| Core | `OrchardCore.Admin`, `OrchardCore.HomeRoute`, `OrchardCore.Themes`, `OrchardCore.Settings`, `OrchardCore.Recipes` |\n| Content | `OrchardCore.Contents`, `OrchardCore.ContentTypes`, `OrchardCore.ContentFields`, `OrchardCore.Title`, `OrchardCore.Autoroute`, `OrchardCore.Alias`, `OrchardCore.Html`, `OrchardCore.Flows`, `OrchardCore.Lists`, `OrchardCore.Widgets` |\n| Media/Menu | `OrchardCore.Media`, `OrchardCore.Menu`, `OrchardCore.Layers` |\n| Security | `OrchardCore.Users`, `OrchardCore.Roles`, `OrchardCore.Security` |\n\n### Naming Conventions\n\n| Item | Convention | Example |\n|------|------------|---------|\n| Recipe file | `name.recipe.json` (lowercase) | `blog.recipe.json` |\n| `name` property | PascalCase | `Blog` |\n| Location | `Recipes/` in a module or theme | `Themes/TheBlogTheme/Recipes/` |\n\n## References\n\n- `references/recipe-structure.md` - File layout, header, variables, helpers\n- `references/steps.md` - Every built-in recipe step with examples\n- `reference/modules/Recipes/README.md` (docs) - Full recipe documentation\n- Example recipes: `src/OrchardCore.Themes/*/Recipes/*.recipe.json`\n\n<!-- File: .agents/skills/orchardcore-tenants/references/shells.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/orchardcore-tenants/references/shells.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/references/shells.md)\n\n<!-- File: .agents/skills/orchardcore-tenants/SKILL.md (Tokens: ~300 | Category: anthropic-skill) -->\n# orchardcore-tenants\n\nPath: `.agents/skills/orchardcore-tenants/SKILL.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/OrchardCMS/OrchardCore/HEAD/.agents/skills/orchardcore-tenants/SKILL.md)\n\n<!-- ... +24 more modular skills available via /api/skills/OrchardCMS/OrchardCore?path={path} -->\n</yakaai_skills>"}