|
| 1 | +--- |
| 2 | +description: Architektur und Konventionen für packages/tree (Moox Tree / filament-tree-index) |
| 3 | +globs: packages/tree/** |
| 4 | +alwaysApply: false |
| 5 | +--- |
| 6 | + |
| 7 | +# Moox Tree Package |
| 8 | + |
| 9 | +Internes Filament-Resource-Index-UI für hierarchische Eloquent-Modelle (Baum links, Inspector rechts). Namespace: `Moox\Tree`. Views/Config-Tag: `filament-tree-index`. |
| 10 | + |
| 11 | +## Zentrale Tree-Funktionen (Single Source of Truth) |
| 12 | + |
| 13 | +**Alle Baum-Funktionen liegen in `packages/tree`** und werden von Consumer-Resources nur **konfiguriert**, nicht neu implementiert: |
| 14 | + |
| 15 | +| Bereich | Zentrale Klassen/Komponenten | |
| 16 | +|--------|------------------------------| |
| 17 | +| CRUD / Verschieben | `Actions/Tree/*` | |
| 18 | +| Baumstruktur | `Support/TreeStructure` | |
| 19 | +| UI / Interaktion | `TreeIndexListRecords` + Concerns, `tree-index-content` Blade, Alpine `$store.filamentTreeIndex` | |
| 20 | +| Filament-Anbindung | `TreeIndexListRecords`, `Contracts\ConfiguresTreeIndex`, `HostsInlineResourceForm`, `InteractsWithResourceTreeIndex`, `InteractsWithTreeResourceInspectorForm` | |
| 21 | +| Inline Form-Actions | `TreeInlineFormResourceAdapter`, `ProvidesInlineResourceFormActions` (generiert, nicht im Consumer) | |
| 22 | +| Inspector-Persistenz | `PersistTreeResourceCreateAction`, `PersistTreeResourceUpdateAction`, `TreeResourcePageExecutor` | |
| 23 | +| Anpassung | `TreeIndexConfiguration` (Spalten, Modus, Hooks, Labels, Toolbar) | |
| 24 | +| Locale / Toolbar-i18n | `Support/TreeLocale`, `toolbarLocalizedTranslations()` | |
| 25 | +| List-Forwarding | `forwardFromResource()`, `Support/ResourceListForwarder` | |
| 26 | + |
| 27 | +**Flexibel nutzbar** heißt: gleicher Code für jedes hierarchische Model — Unterschiede nur über `TreeIndexConfiguration::make()` (Spaltennamen, `nestedSet()`, `reorderable`, `inspectorPage`, Closures). Fehlt eine generische Fähigkeit (z. B. Toolbar-Suche), **im Package erweitern** und per Config/API freischalten — nicht in `category`, `menu-builder` o. Ä. duplizieren. |
| 28 | + |
| 29 | +## Architektur (nicht verletzen) |
| 30 | + |
| 31 | +- **Geschäftslogik in Actions**, nicht in Livewire oder Blade: `CreateTreeNodeAction`, `UpdateTreeNodeAction`, `MoveTreeNodeAction`, `DeleteTreeNodeAction`. Bei `nestedSet()` delegieren Create/Move an `*NestedSet*`-Actions (Kalnoy `NodeTrait`). |
| 32 | +- **`TreeIndexListRecords`** (einziger Livewire-Host) orchestriert nur (Auth, Query, Delegation an Actions, Events). Baum-UI via `table()->content(tree-index-content)` — **kein** separates Livewire-Component, **keine** Livewire-Aliase im Provider. |
| 33 | +- **Baumaufbau** nur über `TreeStructure` + `TreeIndexConfiguration`. Konfiguration ist **immutable** (Fluent API mit `cloneWith`); neue Optionen brauchen Unit-Tests in `TreeIndexConfigurationTest`. |
| 34 | +- **Registry**: List-Pages registrieren Config unter dem **Resource-Klassennamen** (`TreeIndexConfigurationRegistry`). Schlüssel nicht umbenennen ohne Migration aller Aufrufer. |
| 35 | + |
| 36 | +## Zwei Baum-Modi |
| 37 | + |
| 38 | +| Modus | Spalten | Konfiguration | |
| 39 | +|-------|---------|---------------| |
| 40 | +| Adjacency List (Default) | `parent_id`, `sort_order`, Label-Spalte | Standard-`make()` | |
| 41 | +| Nested Set | `_lft`, `_rgt`, optional `parent_id` | `->nestedSet()->sortColumn('_lft')`, Model mit `NodeTrait` | |
| 42 | + |
| 43 | +Adjacency- und Nested-Set-Logik **nicht mischen** in einer Action. |
| 44 | + |
| 45 | +## UI & Frontend |
| 46 | + |
| 47 | +- Blade unter `resources/views`, Prefix `filament-tree-index::`. |
| 48 | +- **Filament-Komponenten** (`x-filament::*`) und `fi-*`-Klassen; Layout/Scroll in `resources/css/tree.css` (Klassen `fi-tree-*`). Kein separates Theme-CSS außerhalb des Packages für Tree-Layout. |
| 49 | +- Alpine-Baumzustand nur über **`$store.filamentTreeIndex`** (`scripts/alpine-tree-store.blade.php`). Keinen zweiten Store oder duplizierte Expand/Collapse-Logik in Partials. |
| 50 | +- Drag & Drop nur wenn `reorderable(true)`; Verschiebe-Validierung (nicht unter sich selbst / eigenes Kind) beibehalten. |
| 51 | + |
| 52 | +## Eloquent-Models (Vertrag) |
| 53 | + |
| 54 | +Das Package darf **keine eigenen Model-Methoden, Interfaces oder Contracts** voraussetzen. Nutzung nur über: |
| 55 | + |
| 56 | +- **Spalten/Attribute**: `parent_id`, Sort-Spalte, Label-Spalte (konfigurierbar), bei Nested Set `_lft`/`_rgt` |
| 57 | +- **Eloquent-Standard**: `getKey()`, `getAttribute()`, `update()`, `delete()`, `setAttribute()` |
| 58 | +- **Nested Set**: ausschließlich Kalnoy `NodeTrait` (`appendToNode`, `beforeNode`, …) — kein zusätzliches Tree-Trait im Moox-Package |
| 59 | + |
| 60 | +Neue Features im Package müssen über **Config/Actions/Resource-Hooks** lösbar sein, nicht über `if (method_exists($model, …))` oder Model-APIs. |
| 61 | + |
| 62 | +## Package-Grenzen |
| 63 | + |
| 64 | +- **Keine domänenspezifischen Daten** im Package (keine `Category`-, `Localization`- oder Mandanten-Queries). Domänenfilter nur über **konfigurierbare Closures** (`modifyQuery`, `applySearchUsing`, `applyLanguageUsing`). |
| 65 | +- **Generische Tree-UI und -Abläufe** (Toolbar, Reorder, Expand, Inspector-Einbettung, Validierung) gehören ins Package — auch wenn heute nur eine Resource sie nutzt. |
| 66 | +- Abhängigkeit `moox/core` ist erlaubt; weitere Moox-Packages nur wenn unvermeidbar und zyklusfrei. |
| 67 | + |
| 68 | +## Code-Stil |
| 69 | + |
| 70 | +- `declare(strict_types=1);` in jeder PHP-Datei. |
| 71 | +- PHP ^8.3, Laravel ^12, Filament ^4/5 wie im Host-Projekt, Livewire ^3/4. |
| 72 | +- Typisierte Closures für Query-Hooks: `fn (Builder $query): Builder`. |
| 73 | + |
| 74 | +## Tests |
| 75 | + |
| 76 | +- Tests nur unter `packages/tree/tests/` (Pest). Feature = `TreeIndexListRecords`; isolierte Baum-Logik = `TestTreeIndexHost` (nutzt `InteractsWithResourceTreeIndex`); Unit = Config, `TreeStructure`, `TreeInlineFormResourceAdapter`, Registry. |
| 77 | +- Auth in Tests: `config(['filament-tree-index.authorization.enabled' => false])` und Registry-Register mit Test-Key. |
| 78 | +- Nach Verhalten ändern: `php artisan test --compact packages/tree/tests` |
| 79 | + |
| 80 | +## Referenz-Integration (außerhalb des Packages) |
| 81 | + |
| 82 | +Consumer-Resources (`implements ConfiguresTreeIndex`) gehören **nicht** in dieses Package — siehe Regel `moox-tree-integration`. Referenz: `CategoryTreeResource`, `TreeListCategories`, `TreeInspectorCategory`. |
| 83 | + |
| 84 | +## Skill-Sync (nach Package-Änderungen) |
| 85 | + |
| 86 | +`packages/tree` ist in `.cursor/skills/registry.yaml` eingetragen. Nach Änderungen an **öffentlicher API**, **Installation/Assets** (`TreeServiceProvider`, CSS, Alpine-Store, `filament:assets`) oder **integrator-relevantem Verhalten**: Regel **`moox-package-skill-sync`** anwenden und Skill **`moox-tree`** (`installation.md`, `integration.md`, `decisions.md`, `SKILL.md`) auf den aktuellen Stand bringen. |
| 87 | + |
| 88 | +## UI-Schaltflächen & Formular (verbindlich) |
| 89 | + |
| 90 | +- **Keine neuen Buttons, Footer-Actions oder Blade-`wire:click`-Schaltflächen** im Tree-Package oder in Consumer-Tree-Pages hinzufügen, ändern oder duplizieren — **ohne vorherige Rückfrage und ausdrückliche Bestätigung** durch den Nutzer. |
| 91 | +- Inspector-Formular = **unverändert** `Resource::form()` der Quell-Resource (Felder + Actions 1:1). **Keine** Action-Umverdrahtung, **keine** zusätzlichen Buttons. |
| 92 | +- Inspector inline auf `TreeIndexListRecords`: `InteractsWithTreeResourceInspectorForm`, `TreeInlineFormResourceAdapter` (Form-Actions ohne Redirect), `PersistTreeResourceCreateAction` / `PersistTreeResourceUpdateAction`. Consumer-Resources **ohne** zusätzliche Traits; Forward-Resource **nicht** `final`. |
| 93 | +- Create inline: `usesResourceCreateInspector()` wenn `inspectorPage` + Create-Route in `getPages()`; sonst `stubCreate()` oder `CreateTreeNodeAction`. |
| 94 | +- Standalone Route `tree-inspector`: `RendersAsTreeIndexInspector` + `RendersAsTreeIndexEmbeddedPage` (Redirect-Suppression, `lang`). Nur für direkte URL — Hauptpfad ist inline auf der List-Page. |
0 commit comments