Skip to content

Assisted Templates - #20154

Open
urbanit wants to merge 13 commits into
OrchardCMS:mainfrom
urbanit:AssistedTemplates-upstream
Open

urbanit wants to merge 13 commits into
OrchardCMS:mainfrom
urbanit:AssistedTemplates-upstream

Conversation

@urbanit

@urbanit urbanit commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Adds an opt-in Assisted Templates feature (OrchardCore.Templates.Assisted) to the Templates module. It helps editors find, view and override the shape templates of the themes and features from the Templates and Admin Templates pages, without opening the source code.

What it adds

When the feature is enabled, the Templates list (and the Admin Templates list when that feature is enabled) gets these buttons:

  • Theme Templates: lists the templates of a theme, the active one by default, including those of its base themes, with a dropdown to pick another theme. Each template shows the file it comes from and its status:
    • Clone copies a Liquid template as a local template.
    • Cloned templates are monitored: the list shows whether they were edited since, and whether the theme's file changed since, e.g. after an update. Reset and Delete are available.
    • Razor templates can only be viewed, as a reference for a Liquid override with the same name.
  • Feature Templates: lists the templates of the enabled features, all of them or those of one feature, with the theme or feature that overrides each one.
    • The lists can be filtered by status, by language (Liquid or Razor), and by what the templates render (front end or admin, guessed from their names).
    • Bulk actions clone, reset or delete several templates.
  • Alternates: for a template, lists the alternates of its shape for the content types, parts and fields of the site, with the content types and display types each one applies to, to create or clone a template for them.
  • Inspect Content, and an Inspect templates action on the content items: shows the shapes a content item renders in a display type, as a tree, with the template that renders each of them and all their alternates, the most specific first, including those of the content item itself (e.g. Content__Alias__[Alias], Content__Slug__[Slug]). The template rendering a shape, or matching an alternate, can be viewed and, if it's Liquid, cloned from there.

Modules can list the templates of other extensions by registering an ITemplateSource, and suggest alternates with an ITemplateAlternatesProvider.

Also in this PR: Add Template is the primary button of the templates lists.

The feature is documented in the Templates module docs (Assisted Templates section) and in the 4.0.0 release notes.

Known limitations

These came up in review. They are design-level, so I left them for follow-up rather than widening this PR:

  • Inspect Content (front end) builds and renders the content item in the admin request. The display drivers' shapes and alternates are those of the front end, but placement comes from the admin theme, so a site theme's placement.json (hidden shapes, extra alternates) isn't reflected. Shapes a site theme template renders in its own markup aren't listed (the page says so).
  • The inspection renders inside the admin page's request, so what the rendered templates push to the layout (e.g. a title segment, a script) also applies to the Inspect Content page.
  • While the feature is enabled, a shape display event handler is registered for the tenant. It does nothing outside an inspection, but runs on every shape render.
  • Feature Templates follows the feature order to tell which template wins. It doesn't apply the shape table's special case that moves the application module last.
  • The template names are computed like BasicShapeTemplateHarvester does, in a copy of its logic. Sharing a case-preserving helper would need a change outside the module.
  • The source lists are enumerated per request, without caching across requests.

Testing

  • Unit tests (test/OrchardCore.Tests/Modules/OrchardCore.Templates): template sources, clone monitoring, alternates, shape inspection and authorization (47 tests).
  • Functional tests:
    • AssistedTemplatesTests checks that the buttons, pages and content item action are absent until the feature is enabled, and present afterwards.
    • AdminListLayoutTests now enables the feature and renders the Theme Templates, Feature Templates and Alternates lists as grids.
  • Checked manually in the browser with the Blog recipe, in both the site and the admin, including cloning from Inspect Content.

A "Theme Templates" button next to "Add Template" lists the templates of
the active site theme (or admin theme for Admin Templates), and of the
themes it is based on, read from the theme's shape table.

- Liquid templates can be cloned as local templates, reset to the theme
  template, or deleted, one by one or as bulk actions.
- Razor templates are view only, with a "Create override" shortcut that
  opens the Add Template editor with the template name.
- Cloned templates are monitored: the hash of the cloned content tells
  whether the local template was edited, and whether the theme template
  changed since. The list can be filtered by these statuses.
- Templates are listed through ITemplateSource, so other sources, e.g.
  the templates of features, can be added as more buttons later.

WIP: unit tests and the .pot translation template still to be verified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
The ancestor of the template source view now links back to the list, and
OrchardCore.Templates.pot carries the strings of the Theme Templates pages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
A dropdown of the Theme Templates list selects one of the site themes, or
of the admin themes, the active theme by default, also when there is only
one. The templates of a theme are read from its files and those of the
themes it is based on, so themes that are not enabled can be listed too.

The templates of a theme that is not active can only be viewed: a local
template applies at once to the active theme, so cloning another theme's
template would render the active theme with it.

A local template is only compared to the file it was cloned from. After
the active theme changes, the templates cloned from the previous theme
show as "Cloned from" that theme, instead of as edited or changed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
A Feature Templates button lists the templates of the enabled features,
all of them by default, or those of the feature selected in the dropdown,
which has a search box. They are almost all Razor templates, listed as a
reference with Create override, which gives the shape name to override.

- A template that is not the one rendering its shape shows "Overridden
  by" the active theme or the feature whose template wins, read from the
  shape table of the active theme.
- A "Renders" filter splits front end and admin templates, guessed from
  the words of their names (Edit, SummaryAdmin, Design, Deployment...):
  front end on Templates and admin on Admin Templates by default.
- Each template source describes its list, and the first scope is the
  default one, e.g. the active theme or all the features.
- The source buttons and the scope dropdown share one row of the action
  bar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
Alternates: an Alternates button on the templates of the Theme and Feature
Templates lists shows the alternates of their shape for the content types
of the site, from the content items (Content, or the stereotype, e.g.
Widget), their parts and their fields, in Detail and Summary, or in
DetailAdmin and SummaryAdmin for the admin templates. The part and field
alternates come from ContentPartAlternatesFactory and
ContentFieldAlternatesFactory, so they match those the drivers add. Each
alternate tells whether a local template or the theme has it, and can be
created, cloned from a Liquid template of the active theme, or viewed when
the theme already renders it. Modules can suggest other alternates with an
ITemplateAlternatesProvider.

Inspect Content: from the templates lists, or the actions of a content
item, renders a content item in a display type while recording the shapes
it renders, then shows them as a tree with the template rendering each of
them, resolved with the site theme and the templates (or the admin theme
and the admin templates) the way the display manager resolves them, and
all their alternates, including those of the content item only.

ActiveThemes gives the active site or admin theme to the sources, the
alternates and the inspection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
The Inspect Content page lists View and Clone actions next to the template
of the active theme or of a feature that renders a shape or is bound to one
of its alternates, on the site and on the admin. Add Template is the primary
button of the templates lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
The Theme and Feature Templates lists, the alternates and Inspect Content
are part of the new OrchardCore.Templates.Assisted feature of the Templates
module, which is not enabled by default. The Templates and Admin Templates
lists only show their buttons when it is enabled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
The Theme Templates and Feature Templates lists filter their templates by
language, Liquid or Razor. The page viewing a template only shows its
Alternates button when its shape has alternates, like the lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
- Forget the clone record of a template deleted, renamed or bulk deleted
  from the Templates lists, so a template written later with its name isn't
  taken for a clone.
- Keep the inspected content item in the picker when it's older than the
  recent ones, build its display inside the error handling, and write the
  indentation of the shapes with the invariant culture.
- Show the alternates generated for every display type as applying to all
  of them.
- Describe a template cloned from another source's file without guessing
  it was a theme active before.
- Return 404 when deleting an override without a name.
- Use collection expressions and document the public view models.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
@urbanit urbanit changed the title Assisted templates upstream Assisted templates Oct 28, 2026
@urbanit urbanit changed the title Assisted templates Assisted Templates Oct 28, 2026
@sebastienros

Copy link
Copy Markdown
Member

Can you put a link to the youtube video at the correct timestamp to recall what the feature was about?

@urbanit

urbanit commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

https://youtu.be/4nIjSBtQgx0?si=WVi8bbg1Q2aDidCC&t=2683

original discussion is here: https://github.com/orgs/OrchardCMS/discussions/19709

I will update the same discussion with new implementation

@urbanit

urbanit commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@github-actions

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts. Please resolve those before requesting a review.

@grandua

grandua commented Oct 31, 2026

Copy link
Copy Markdown

Hi @urbanit — I ran this PR through an automated review harness and two issues surfaced:

  1. Authorization gap in the inspector's recent-content picker:
    GetRecentContentItemsAsync returns the 50 most recently modified items
    on index.Latest (so including unpublished) without item-level ViewContent
    filtering — a template manager who can't view a restricted item still sees
    its title and ID in the picker.
  2. Missing OrchardCore.Contents dependency: OrchardCore.Templates.Assisted
    declares only OrchardCore.Templates, but the inspector and content-alternates
    provider need IContentManager/IContentDefinitionManager — enabling the
    feature on a tenant without Contents enabled fails at DI time when opening
    the inspector/source pages.

Full line-by-line breakdown:
grandua/dw-public-reviews#16
— hope this helps polish Assisted Templates.

- The new OrchardCore.Templates.ShapeTracing feature, which depends on
  OrchardCore.Contents, holds what needs the content services: Inspect
  Content, the Inspect templates action of the content items and the
  alternates suggested for the content types. Assisted Templates only
  depends on Templates, so its template sources work without Contents.
- The picker of the inspector only lists the content items the user can
  view, checking ViewContent on each of them, and has a search box filtering
  them by their display text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
@urbanit

urbanit commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review @grandua, both were real. Fixed in d946efd:

Picker authorization

The inspector’s picker now only lists content items the user can view (ViewContent on each item), paging through the latest items until it has 50, and has a search box to find older ones by display text. I looked at reusing the Content Picker, but its search endpoint returns every match and only flags whether each one is viewable, so it wouldn’t have closed the gap.

Contents dependency

Everything that needs the content services moved to a new OrchardCore.Templates.ShapeTracing feature that depends on OrchardCore.Contents: Inspect Content, the Inspect templates action and the content-type alternates provider. That provider also fed the Theme/Feature Templates lists, so those pages failed without Contents too. Assisted Templates now only depends on Templates, and a functional test checks its pages on a tenant without Contents (the SaaS recipe).

- The inspector picks its content item with the content picker of the
  content picker fields, searching the content items as you type, so Shape
  Tracing depends on OrchardCore.ContentFields.
- A content item is listed and inspected in its published version when it
  has one. A draft that was never published requires PreviewContent too,
  and is shown as not published.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117DzPPRjTuatDtobb9oDKj
@urbanit

urbanit commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

Content picker UI The picker’s search box and select are replaced by the content picker of the content picker fields, the same component, searching as you type. It queries its own endpoint, TemplateInspector/SearchContentItems, which applies the same authorization, so it doesn’t use the content picker’s search endpoint mentioned above. Because of this, Shape Tracing also depends on OrchardCore.ContentFields.

Drafts A content item is now listed, and inspected, in its published version when it has one, with ViewContent. A draft that was never published also requires PreviewContent, and is shown as not published. The same check applies when a content item is opened by its id in the URL. Without it, every signed-in user could see the titles of drafts, since the Authenticated role grants ViewContent by default.

Tests The functional test now checks the content picker and its search endpoint. Checked manually with a role without PreviewContent: a draft is neither listed nor inspectable by its id, and it is listed, as not published, once the role can preview content.

@grandua

grandua commented Nov 1, 2026 •

Copy link
Copy Markdown

Thanks for the review @grandua, both were real. Fixed in d946efd:

Picker authorization

The inspector’s picker now only lists content items the user can view (ViewContent on each item), paging through the latest items until it has 50, and has a search box to find older ones by display text. I looked at reusing the Content Picker, but its search endpoint returns every match and only flags whether each one is viewable, so it wouldn’t have closed the gap.

Contents dependency

Everything that needs the content services moved to a new OrchardCore.Templates.ShapeTracing feature that depends on OrchardCore.Contents: Inspect Content, the Inspect templates action and the content-type alternates provider. That provider also fed the Theme/Feature Templates lists, so those pages failed without Contents too. Assisted Templates now only depends on Templates, and a functional test checks its pages on a tenant without Contents (the SaaS recipe).

-Thanks for the quick turnaround, @urbanit — I've re-run the automated
review against the updated head (d946efd, which includes your
picker-authorization and clone fixes).

Verdict: your authorization fix no longer shows up in the findings,
but the re-review still flags two MEDIUM correctness issues on the new
head — notably, clone provenance can survive template deletion while
Assisted Templates is disabled, leaving stale provenance that
misclassifies a recreated template as a clone. Details:
grandua/dw-public-reviews#16 (review)

Heads-up: this pass covers head d946efd — anything pushed after it
needs a fresh run. Ping me and I'll kick one off, or @sebastienros can
install the app on the repo — https://github.com/apps/digitalworker-reviewer
— free during early access, to get fresh reviews automatically on every
push.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants