Skip to content

Commit da4ce48

Browse files
committed
feat(backend)!: the AI section exists, and editor surfaces live in it
ADR-119 deferred the placement with a named revisit trigger, and #812 carried that open question so the next editor surface would not settle it by accident. The question was put and answered rather than triggered. nr_llm no longer registers a `nrllm` container under Administration. A top-level section `netresearch_ai` holds five entries: the editor task module, the overview, and three subject containers (nrllm_setup, nrllm_authoring, nrllm_operation) holding the fourteen former flat entries. Four of the five come from those fourteen, which is the "three or four entries" ADR-119 asks for; nrllm_aitasks is the fifth and was never one of them -- it sat under `web`. THE ACCESS CHECK IS THE WHOLE MECHANIC. The module menu hides a top-level module whose own access check fails, together with its children (ADR-131). That is why nrllm_aitasks was parented to `web`: under the admin-only container it would have been invisible to the editors it exists for, and every further editor surface would have landed flat under `web` for the same reason. A section carries no `access` key -- exactly like the core's own sections -- so it never filters and its children filter individually. Admin-only and editor-facing modules share one place for the first time. The nesting depth is unchanged: tools -> nrllm -> nrllm_providers was already three levels. BREAKING CHANGE: `nrllm` is no longer a registered module identifier and the container URL /module/nrllm has no successor. Submodule identifiers and explicit paths are unchanged, and nrllm_overview carries `'aliases' => ['nrllm']`, so backend shortcuts keep resolving. A foreign module anchored `['after' => 'nrllm']` also keeps its position: read rather than assumed, ModuleFactory::adaptAliasMappingFromModuleConfiguration() rewrites `position.before` and `position.after` through aliases, not only `parent`, so t3_cowriter needs no change. Scope, measured rather than estimated. A raw grep for `nrllm` answers 1912 hits in 305 files, which conflates four namespaces: the module identifier, the tx_nrllm_ table prefix, the cache and dashboard group names, and a uniqid prefix. Only the first is a module reference. It came to three setShortcutContext routeIdentifiers, repointed to nrllm_overview as ADR-119 pre-settled, and five Playwright navigations to the retired container path. Every Fluid template link still resolves to a registered module, asserted rather than eyeballed. OverviewCardCoverageTest asserted card coverage for children of the single `nrllm` parent; that parent set is now three containers. The list is a constant rather than one name because a fourth container will be added, and a coverage check that silently covers one third of the modules is worse than none. The required card set is unchanged at thirteen, so the overview template needed no edit. ADR-183 records the decision and amends ADR-119, whose status and `:Amended:` field are edited in the same change. It also withdraws one of ADR-119's two reasons for the name: "integrators look for AI" aims at the wrong audience, since ADR-131 establishes the section exists for editors. The conclusion survives on accuracy instead -- of the four products sharing the section, nr_repurpose generates audio and images, nr_ai_search fuses BM25 with embeddings, and the cowriter drives CKEditor. NOT IN THIS CHANGE: the sixteen backend screenshots still show `Administration > LLM` in the module menu and are now stale. Recapturing them needs a running instance and seed data matching each image, which is its own task rather than a mechanical re-shoot. Gates: unit 7318 tests / 24703 assertions, functional 1765 / 17831 (sqlite), cgl and phpstan level 10 -- all green on PHP 8.4. Rector was NOT run: it is pinned to PHP 8.2 and this worktree's .Build is resolved at 8.4, so platform_check.php fatals. CI covers that leg. Closes #812 Refs ADR-119, ADR-131, ADR-183 Assisted-by: claude-code:claude-opus-5 Agent-Session: https://claude.ai/code/session_01MNg1MysJVugv1xo2husknU Agent-Host: 0493f0 Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
1 parent adf57dc commit da4ce48

35 files changed

Lines changed: 453 additions & 88 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- **BREAKING: the backend modules moved into a shared `AI` section** — nr_llm no longer registers a `nrllm` container under Administration. A top-level section `netresearch_ai` now holds five entries: the editor task module, the overview, and three subject containers (`nrllm_setup`, `nrllm_authoring`, `nrllm_operation`) holding the fourteen former flat entries. A section carries no access check of its own, which is the point: the module menu hides a top-level module whose access check fails together with all its children, so an admin-only container could never hold an editor surface — `nrllm_aitasks` had to sit under `web` for that reason, and every further editor surface would have landed there too. Submodule identifiers and paths are unchanged; `nrllm_overview` carries `'aliases' => ['nrllm']`, so backend shortcuts keep resolving and a foreign module anchored `['after' => 'nrllm']` keeps its position (the core rewrites `position` through aliases, not only `parent`). What does NOT survive is the container's own URL `/module/nrllm`, which had no successor by design. ADR-183 amends ADR-119; closes #812.
12+
913
## [0.33.0] - 2026-08-21
1014

1115
### Added

Classes/Controller/Backend/LlmModuleController.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public function indexAction(): ResponseInterface
103103

104104
if (method_exists($moduleTemplate->getDocHeaderComponent(), 'setShortcutContext')) {
105105
$moduleTemplate->getDocHeaderComponent()->setShortcutContext(
106-
routeIdentifier: 'nrllm',
106+
routeIdentifier: 'nrllm_overview',
107107
displayName: 'LLM - Dashboard',
108108
);
109109
}
@@ -219,7 +219,7 @@ public function testAction(): ResponseInterface
219219

220220
if (method_exists($moduleTemplate->getDocHeaderComponent(), 'setShortcutContext')) {
221221
$moduleTemplate->getDocHeaderComponent()->setShortcutContext(
222-
routeIdentifier: 'nrllm',
222+
routeIdentifier: 'nrllm_overview',
223223
displayName: 'LLM - Test',
224224
arguments: ['action' => 'test'],
225225
);
@@ -305,7 +305,7 @@ public function governanceAction(): ResponseInterface
305305

306306
if (method_exists($moduleTemplate->getDocHeaderComponent(), 'setShortcutContext')) {
307307
$moduleTemplate->getDocHeaderComponent()->setShortcutContext(
308-
routeIdentifier: 'nrllm',
308+
routeIdentifier: 'nrllm_overview',
309309
displayName: 'LLM - Governance',
310310
arguments: ['action' => 'governance'],
311311
);

Configuration/Backend/Modules.php

Lines changed: 103 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,54 +29,110 @@
2929
/**
3030
* Backend module registration for nr_llm.
3131
*
32-
* Structure: Main module under 'tools', sub-modules as children of main module.
33-
* Sub-modules only appear in docheader dropdown, not in main navigation.
32+
* Structure (ADR-119, decided in #812):
3433
*
35-
* Uses 'tools' as parent for v13+v14 compatibility:
36-
* - v13: 'tools' exists natively as the admin tools group
37-
* - v14: 'tools' is an alias for the new 'admin' group
34+
* netresearch_ai top-level section, shared with the sibling
35+
* extensions; no access check of its own
36+
* ├── nrllm_aitasks editor surface (access => user)
37+
* ├── nrllm_overview landing page (access => admin)
38+
* ├── nrllm_setup providers, models, configurations, use-case
39+
* ├── nrllm_authoring tasks, skills, snippets
40+
* └── nrllm_operation tools, MCP, playground, runs, analytics
3841
*
39-
* Pattern follows TYPO3 Styleguide extension:
40-
* - Main module identifier without prefix (e.g., 'nrllm' not 'tools_nrllm')
41-
* - Child modules with parent as prefix (e.g., 'nrllm_providers')
42-
* - Nested paths under main module path
42+
* The section replaces 'tools' as the top-level parent; the depth is unchanged
43+
* (a section held one container holding fourteen entries before, and now holds
44+
* five entries holding their own).
4345
*
44-
* v13 compatibility: 'nrllm_overview' is registered as first submodule so that
45-
* v13 (which redirects to the first submodule) shows the overview page.
46-
* v14 uses 'showSubmoduleOverview' on the parent module for the same effect.
46+
* WHY A SECTION AND NOT A CONTAINER UNDER ADMINISTRATION. The module menu drops
47+
* every top-level module whose own access check FAILS, together with all its
48+
* children (ADR-131). Under the admin-only 'nrllm' container an editor surface
49+
* was therefore invisible, and 'nrllm_aitasks' had to be parented to 'web' to
50+
* be reachable at all. Every further editor surface would have landed there for
51+
* the same reason, one flat entry at a time. A section carries no 'access' key
52+
* of its own — exactly like the core's own sections — so it never filters; its
53+
* children filter individually, and admin-only and editor-facing modules can
54+
* finally live in one place.
55+
*
56+
* WHY THE IDENTIFIER IS VENDOR-SCOPED. Module identifiers merge last-package-
57+
* wins. A bare 'ai' would be a shared namespace with no owner: the label and
58+
* icon would depend on package load order, and removing the owning extension
59+
* would strip the routes of any foreign submodule parented to it.
60+
*
61+
* OLD ROUTES. 'nrllm' is no longer a registered identifier, so 'nrllm_overview'
62+
* carries it as an alias — an alias is shadowed by a real module of the same
63+
* name, which is why the container had to go first. Backend shortcuts store the
64+
* module identifier, so they resolve through that alias. ModuleFactory also
65+
* rewrites 'position' references through aliases, so a foreign module anchored
66+
* with ['after' => 'nrllm'] keeps its place without changing.
67+
*
68+
* Submodule identifiers and explicit paths are unchanged on purpose: a regroup
69+
* that also renamed the leaves would break every bookmark for no gain.
70+
*
71+
* v13 compatibility: each container registers 'nrllm_*' children and carries
72+
* both 'dependsOnSubmodules' (v13 redirects to the first submodule) and
73+
* 'showSubmoduleOverview' (v14 renders an overview instead).
4774
*/
4875
return [
49-
// Main dashboard module (parent container)
50-
'nrllm' => [
51-
'parent' => 'tools',
52-
'position' => ['after' => 'styleguide'],
53-
'access' => 'admin',
76+
// The shared top-level section. No 'parent', no 'path', no 'access' and no
77+
// 'controllerActions' — that is the shape of a section rather than a module,
78+
// and it is what the core's own sections look like. The absent access check
79+
// is load-bearing, not an omission: see the header.
80+
//
81+
// Positioned after 'media' rather than at the end with the admin sections,
82+
// because the audience that makes the section necessary is editors.
83+
'netresearch_ai' => [
84+
'position' => ['after' => 'media'],
5485
'iconIdentifier' => 'module-nrllm',
55-
'path' => '/module/nrllm',
56-
'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod.xlf',
86+
'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_section.xlf',
87+
],
88+
// Setup: what has to exist before anything can run.
89+
'nrllm_setup' => [
90+
'parent' => 'netresearch_ai',
91+
'access' => 'admin',
92+
'iconIdentifier' => 'module-nrllm-provider',
93+
'path' => '/module/nrllm/setup',
94+
'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_setup.xlf',
5795
'extensionName' => 'NrLlm',
5896
'appearance' => [
5997
'dependsOnSubmodules' => true,
6098
],
61-
// v14+: Show overview page for parent module
6299
'showSubmoduleOverview' => true,
63-
'controllerActions' => [
64-
LlmModuleController::class => [
65-
'index',
66-
'test',
67-
'executeTest',
68-
'governance',
69-
'help',
70-
],
100+
],
101+
// Authoring: what the models are asked to do.
102+
'nrllm_authoring' => [
103+
'parent' => 'netresearch_ai',
104+
'access' => 'admin',
105+
'iconIdentifier' => 'module-nrllm-snippet',
106+
'path' => '/module/nrllm/authoring',
107+
'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_authoring.xlf',
108+
'extensionName' => 'NrLlm',
109+
'appearance' => [
110+
'dependsOnSubmodules' => true,
111+
],
112+
'showSubmoduleOverview' => true,
113+
],
114+
// Operation: what is running, and what it cost.
115+
'nrllm_operation' => [
116+
'parent' => 'netresearch_ai',
117+
'access' => 'admin',
118+
'iconIdentifier' => 'module-nrllm-runs',
119+
'path' => '/module/nrllm/operation',
120+
'labels' => 'LLL:EXT:nr_llm/Resources/Private/Language/locallang_mod_operation.xlf',
121+
'extensionName' => 'NrLlm',
122+
'appearance' => [
123+
'dependsOnSubmodules' => true,
71124
],
125+
'showSubmoduleOverview' => true,
72126
],
73-
// Overview submodule - v13 compatibility
74-
// In v13, dependsOnSubmodules redirects to the first submodule.
75-
// This ensures the overview page is shown instead of providers.
76-
// In v14, showSubmoduleOverview on the parent handles this natively.
127+
// The section's landing page, and the holder of the old container's
128+
// identifier. 'aliases' resolves backend shortcuts stored against 'nrllm'
129+
// and re-anchors foreign modules positioned ['after' => 'nrllm'] — both
130+
// only work because 'nrllm' is no longer registered as a real module, which
131+
// would shadow the alias.
77132
'nrllm_overview' => [
78-
'parent' => 'nrllm',
133+
'parent' => 'netresearch_ai',
79134
'position' => ['before' => '*'],
135+
'aliases' => ['nrllm'],
80136
'access' => 'admin',
81137
'iconIdentifier' => 'module-nrllm',
82138
'path' => '/module/nrllm/overview',
@@ -99,7 +155,7 @@
99155
// Provider management - child of main module
100156
// Note: AJAX actions (toggleActive, testConnection) are registered via AjaxRoutes.php
101157
'nrllm_providers' => [
102-
'parent' => 'nrllm',
158+
'parent' => 'nrllm_setup',
103159
'access' => 'admin',
104160
'iconIdentifier' => 'module-nrllm-provider',
105161
'path' => '/module/nrllm/providers',
@@ -114,7 +170,7 @@
114170
// Model management - child of main module
115171
// Note: AJAX actions (toggleActive, setDefault, etc.) are registered via AjaxRoutes.php
116172
'nrllm_models' => [
117-
'parent' => 'nrllm',
173+
'parent' => 'nrllm_setup',
118174
'access' => 'admin',
119175
'iconIdentifier' => 'module-nrllm-model',
120176
'path' => '/module/nrllm/models',
@@ -129,7 +185,7 @@
129185
// Configuration management - child of main module
130186
// Note: AJAX actions (toggleActive, setDefault, testConfiguration) are registered via AjaxRoutes.php
131187
'nrllm_configurations' => [
132-
'parent' => 'nrllm',
188+
'parent' => 'nrllm_setup',
133189
'access' => 'admin',
134190
'iconIdentifier' => 'module-nrllm',
135191
'path' => '/module/nrllm/configurations',
@@ -146,7 +202,7 @@
146202
// Task management - child of main module
147203
// Note: new/edit/save/delete use FormEngine (record_edit route), AJAX actions via AjaxRoutes.php
148204
'nrllm_tasks' => [
149-
'parent' => 'nrllm',
205+
'parent' => 'nrllm_authoring',
150206
'access' => 'admin',
151207
'iconIdentifier' => 'module-nrllm-task',
152208
'path' => '/module/nrllm/tasks',
@@ -170,7 +226,7 @@
170226
// Prompt snippet library - child of main module
171227
// Note: new/edit/save/delete use FormEngine (record_edit route)
172228
'nrllm_snippets' => [
173-
'parent' => 'nrllm',
229+
'parent' => 'nrllm_authoring',
174230
'access' => 'admin',
175231
'iconIdentifier' => 'module-nrllm-snippet',
176232
'path' => '/module/nrllm/snippets',
@@ -190,7 +246,7 @@
190246
// Shares the wizard's icon deliberately: the two are one entry path, and a
191247
// second wizard-family glyph would suggest a second kind of thing.
192248
'nrllm_usecase' => [
193-
'parent' => 'nrllm',
249+
'parent' => 'nrllm_setup',
194250
'access' => 'admin',
195251
'iconIdentifier' => 'module-nrllm-wizard',
196252
'path' => '/module/nrllm/use-case',
@@ -207,7 +263,7 @@
207263
// Setup wizard - child of main module
208264
// Note: AJAX actions (detect, test, discover, generate, save) are registered via AjaxRoutes.php
209265
'nrllm_wizard' => [
210-
'parent' => 'nrllm',
266+
'parent' => 'nrllm_setup',
211267
'access' => 'admin',
212268
'iconIdentifier' => 'module-nrllm-wizard',
213269
'path' => '/module/nrllm/wizard',
@@ -222,7 +278,7 @@
222278
// Skills management - child of main module
223279
// Note: AJAX actions (sync, toggleSkill, setToken) are registered via AjaxRoutes.php
224280
'nrllm_skills' => [
225-
'parent' => 'nrllm',
281+
'parent' => 'nrllm_authoring',
226282
'access' => 'admin',
227283
'iconIdentifier' => 'module-nrllm-skill',
228284
'path' => '/module/nrllm/skills',
@@ -240,7 +296,7 @@
240296
// (nrllm_tool_toggle) and additionally guards itself with
241297
// RequiresBackendAdminTrait (ADR-037).
242298
'nrllm_tools' => [
243-
'parent' => 'nrllm',
299+
'parent' => 'nrllm_operation',
244300
'access' => 'admin',
245301
'iconIdentifier' => 'module-nrllm-tool',
246302
'path' => '/module/nrllm/tools',
@@ -260,7 +316,7 @@
260316
// RequiresBackendAdminTrait because a backend route bypasses this
261317
// module's access setting (ADR-037).
262318
'nrllm_mcp' => [
263-
'parent' => 'nrllm',
319+
'parent' => 'nrllm_operation',
264320
'access' => 'admin',
265321
'iconIdentifier' => 'module-nrllm-tool',
266322
'path' => '/module/nrllm/mcp',
@@ -278,7 +334,7 @@
278334
// Note: the AJAX runAction is registered via AjaxRoutes.php (nrllm_tool_run)
279335
// and additionally guards itself with RequiresBackendAdminTrait.
280336
'nrllm_playground' => [
281-
'parent' => 'nrllm',
337+
'parent' => 'nrllm_operation',
282338
'access' => 'admin',
283339
'iconIdentifier' => 'module-nrllm-tool',
284340
'path' => '/module/nrllm/playground',
@@ -297,7 +353,7 @@
297353
// gates the three list/write actions, and `show` is additionally authorised
298354
// per run by the runtime (AGENT_READ).
299355
'nrllm_runs' => [
300-
'parent' => 'nrllm',
356+
'parent' => 'nrllm_operation',
301357
'access' => 'admin',
302358
'iconIdentifier' => 'module-nrllm-runs',
303359
'path' => '/module/nrllm/runs',
@@ -317,7 +373,7 @@
317373
],
318374
// Usage analytics dashboard - child of main module
319375
'nrllm_analytics' => [
320-
'parent' => 'nrllm',
376+
'parent' => 'nrllm_operation',
321377
'access' => 'admin',
322378
'iconIdentifier' => 'module-nrllm-analytics',
323379
'path' => '/module/nrllm/analytics',
@@ -340,7 +396,7 @@
340396
// ticked in be_groups; the tasks_use/agent_approve grants are checked per
341397
// action on top — the module switch alone never grants execution.
342398
'nrllm_aitasks' => [
343-
'parent' => 'web',
399+
'parent' => 'netresearch_ai',
344400
'access' => 'user',
345401
'iconIdentifier' => 'module-nrllm-task',
346402
'path' => '/module/web/nrllm-aitasks',

Documentation/Administration/AgentRuns.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ wants to make, or to supply a piece of typed input it asked for. The
1212
:guilabel:`Agent Runs` module is the inbox where you make those decisions and
1313
review runs that have finished.
1414

15-
The admin inbox lives in :guilabel:`Admin Tools > LLM > Agent Runs`;
15+
The admin inbox lives in :guilabel:`AI > Operation > Agent Runs`;
1616
the same actions are also reachable through the editor module
1717
:guilabel:`Web > AI Tasks` (:ref:`ADR-131 <adr-131>`). Visibility is
1818
actor-scoped: an administrator or a holder of the *Approve suspended AI

Documentation/Administration/Analytics.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ against this month's budget.
2828
Opening the module
2929
==================
3030

31-
Open :guilabel:`Admin Tools > LLM > Analytics`. The submodule sits next
31+
Open :guilabel:`AI > Operation > Analytics`. The submodule sits next
3232
to the other LLM sections in the left-hand navigation and is
3333
admin-only, like the rest of the module.
3434

Documentation/Administration/Configurations.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ configurations by identifier in their code.
2525
Adding a configuration manually
2626
===============================
2727

28-
1. Navigate to :guilabel:`Admin Tools > LLM >
28+
1. Navigate to :guilabel:`AI > Setup >
2929
Configurations`.
3030
2. Click :guilabel:`Add Configuration`.
3131
3. Fill in the required fields:

Documentation/Administration/Governance.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ puts the values in force on one page.
1818
Where the values are shown
1919
==========================
2020

21-
:guilabel:`Admin Tools > LLM > Overview`, docheader tab
21+
:guilabel:`AI > Overview`, docheader tab
2222
:guilabel:`Governance` (admin-only, like every other nr_llm admin surface).
2323
The table has three columns: the setting, the value the runtime applies
2424
right now, and the class that resolved it.

Documentation/Administration/Index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The LLM backend module
1717
======================
1818

1919
All AI **management** happens in
20-
:guilabel:`Admin Tools > LLM`; editors run prepared tasks and decide
20+
:guilabel:`AI`; editors run prepared tasks and decide
2121
approvals in the separate :guilabel:`Web > AI Tasks` module
2222
(:ref:`administration-permissions`). The **Overview** is a guided starting point:
2323

Documentation/Administration/Models.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ through a provider (e.g., ``gpt-5``,
2424
Adding a model manually
2525
=======================
2626

27-
1. Navigate to :guilabel:`Admin Tools > LLM >
27+
1. Navigate to :guilabel:`AI > Setup >
2828
Models`.
2929
2. Click :guilabel:`Add Model`.
3030
3. Fill in the required fields:

Documentation/Administration/PromptSnippets.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ model binding.
2424
Adding a snippet
2525
================
2626

27-
1. Navigate to :guilabel:`Admin Tools > LLM >
27+
1. Navigate to :guilabel:`AI > Authoring >
2828
Snippets`.
2929
2. Click :guilabel:`New Snippet`.
3030
3. Fill in the fields:
@@ -90,7 +90,7 @@ request made with that configuration then carries
9090
them — chat, single-prompt completion, streaming and
9191
agent runs alike — without any extension code.
9292

93-
1. Navigate to :guilabel:`Admin Tools > LLM >
93+
1. Navigate to :guilabel:`AI > Setup >
9494
Configurations` and edit a configuration.
9595
2. Open the :guilabel:`Parameters` tab.
9696
3. Tick the wanted tags under :guilabel:`Prompt

0 commit comments

Comments
 (0)