From c2cdaef72c45bce0a688cc54bb5491935fa2ae1e Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 4 Sep 2026 16:47:30 +0200 Subject: [PATCH] Add boost skills, fix boost core --- resources/boost/guidelines/core.blade.php | 20 +++- .../skills/sharp-crud-scaffolding/SKILL.md | 108 ++++++++++++++++++ .../boost/skills/sharp-field-catalog/SKILL.md | 69 +++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 resources/boost/skills/sharp-crud-scaffolding/SKILL.md create mode 100644 resources/boost/skills/sharp-field-catalog/SKILL.md diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php index 9dec5e723..f55606896 100644 --- a/resources/boost/guidelines/core.blade.php +++ b/resources/boost/guidelines/core.blade.php @@ -6,6 +6,22 @@ ### Patterns Use `make()` static methods to initialize fields, columns, and other components. +#### Entities +An Entity List, Form, Show Page, and Dashboard aren't wired into Sharp on their own: each one belongs to an `Entity` class (`Code16\Sharp\Utils\Entities\SharpEntity`, or `SharpDashboardEntity` for dashboards) that ties them together and gets registered in `SharpAppServiceProvider::configureSharp()`, usually via `->discoverEntities()` (scans `app/Sharp/Entities`) or explicitly via `->addEntity('key', MyEntity::class)`. When scaffolding a new resource, always create/update the Entity class and confirm it's discoverable — don't stop at the List/Form/Show classes. See the `sharp-crud-scaffolding` skill for the full workflow. +@verbatim + +use Code16\Sharp\Utils\Entities\SharpEntity; + +class UserEntity extends SharpEntity +{ + protected string $label = 'User'; + protected ?string $list = UserList::class; + protected ?string $form = UserForm::class; + protected ?string $show = UserShow::class; +} + +@endverbatim + #### Entity Lists Entity Lists are used to display a list of records. @verbatim @@ -210,8 +226,8 @@ public function buildFormConfig(): void { $this ->configureDisplayShowPageAfterCreation() - ->configureCreateFormTitle('Create new user') - ->configureEditFormTitle('Edit user'); + ->configureCreateTitle('Create new user') + ->configureEditTitle('Edit user'); } // Show Configuration diff --git a/resources/boost/skills/sharp-crud-scaffolding/SKILL.md b/resources/boost/skills/sharp-crud-scaffolding/SKILL.md new file mode 100644 index 000000000..db1ef20b4 --- /dev/null +++ b/resources/boost/skills/sharp-crud-scaffolding/SKILL.md @@ -0,0 +1,108 @@ +--- +name: sharp-crud-scaffolding +description: Scaffold a full Sharp resource (Entity List, Form, Show Page) and wire it into Sharp - generator commands, the Entity class, config registration, and adding it to the menu. Use whenever adding a new Sharp-managed resource, not just when writing field code. +--- + +# Sharp CRUD Scaffolding + +## When to use this skill +Use this when the user asks to add a new resource/model to the Sharp admin (e.g. "add a Sharp entity for Product", "make this model manageable in Sharp"). Writing an Entity List/Form/Show class alone is not enough - a Sharp resource only appears in the admin once its `Entity` class is registered and (usually) added to a menu. + +## The pieces of one Sharp resource +1. **Entity List** (`Code16\Sharp\EntityList\SharpEntityList`) - the listing/table page. +2. **Form** (`Code16\Sharp\Form\SharpForm`) - create/edit page. +3. **Show** (`Code16\Sharp\Show\SharpShow`) - read-only detail page. Optional if edit-in-place via Form is enough. +4. **Entity** (`Code16\Sharp\Utils\Entities\SharpEntity`) - the glue class that declares which List/Form/Show/Policy belong together, and the label shown in the UI/breadcrumb. +5. **Policy** (optional) - `Code16\Sharp\Auth\SharpEntityPolicy` implementation, or an inline anonymous class returned from `getPolicy()`, to restrict view/update/delete. + +## Prefer the generators +Sharp ships Artisan generators that produce these classes with the correct structure - use them instead of writing files by hand when scaffolding from scratch. There's also an interactive prompt-based wizard (`php artisan sharp:generator`), but it can't be driven non-interactively, so as an agent run the individual commands below instead: + +```bash +php artisan sharp:make:entity-list ProductList --model="App\Models\Product" +php artisan sharp:make:form ProductForm --model="App\Models\Product" +php artisan sharp:make:show-page ProductShow --model="App\Models\Product" +php artisan sharp:make:policy ProductPolicy + +# An Entity List is always included; add --form/--show for the pieces you generated above, +# and --policy if you generated a policy. There is no --list flag. +php artisan sharp:make:entity ProductEntity --label="Product" --form --show --policy +``` + +`--model` pre-fills the model class references in the generated List/Form/Show and offers to scaffold the Eloquent model if it doesn't exist yet. Generated classes land under `app/Sharp/` by convention (e.g. `app/Sharp/Entities/ProductEntity.php`), which is what `discoverEntities()` scans by default. + +## The Entity class +Naming convention: singular, CamelCase, `Entity` suffix (e.g. `ProductEntity`). + +```php +namespace App\Sharp\Entities; + +use App\Sharp\ProductForm; +use App\Sharp\ProductList; +use App\Sharp\ProductShow; +use Code16\Sharp\Utils\Entities\SharpEntity; + +class ProductEntity extends SharpEntity +{ + protected string $label = 'Product'; + protected ?string $list = ProductList::class; + protected ?string $show = ProductShow::class; + protected ?string $form = ProductForm::class; + protected ?string $policy = ProductPolicy::class; +} +``` + +If you need to compute the label/classes dynamically (e.g. conditional policy), override the getter methods instead of the properties: `getLabel()`, `getList()`, `getShow()`, `getForm()`, `getPolicy()`. + +For a dashboard-only entity, extend `SharpDashboardEntity` and set `$view` (not `$list`/`$form`/`$show`): + +```php +use Code16\Sharp\Utils\Entities\SharpDashboardEntity; + +class SalesDashboardEntity extends SharpDashboardEntity +{ + protected ?string $view = SalesDashboard::class; +} +``` + +## Registering the entity +This is the step most likely to be forgotten. In the app's `SharpAppServiceProvider::configureSharp()`: + +```php +protected function configureSharp(SharpConfigBuilder $config): void +{ + $config + ->setName('My project') + // Preferred: auto-discovers every *Entity class under app/Sharp/Entities + ->discoverEntities(); + + // Or register explicitly (needed for entities living elsewhere, or to + // control the key/icon/label used in menu links / entity maps): + // ->addEntity('product', ProductEntity::class, 'lucide-package', 'Products'); +} +``` + +If the app already calls `discoverEntities()` and the new Entity class is under the scanned path (default `app/Sharp/Entities`, or extra paths passed as an array argument), no further registration is needed - just confirm the class name ends in `Entity` and sits in a scanned directory. + +## Adding it to the menu +Registering an entity makes it usable, but it won't show up in the sidebar until referenced in the app's `SharpMenu` implementation: + +```php +class MySharpMenu extends Code16\Sharp\Utils\Menu\SharpMenu +{ + public function build(): self + { + return $this + ->addEntityLink(ProductEntity::class, 'Products', icon: 'lucide-package'); + } +} +``` + +This menu class must itself be declared once in `configureSharp()` via `->setSharpMenu(MySharpMenu::class)` - it isn't auto-discovered like entities are. + +## Checklist when scaffolding a new resource +- [ ] List/Form/Show classes created (generators preferred) +- [ ] Entity class created, pointing at List/Form/Show +- [ ] Entity registered (`discoverEntities()` covers it, or explicit `addEntity()`) +- [ ] Policy attached if the resource needs authorization beyond the default Gate +- [ ] Menu entry added if the resource should be reachable from the sidebar diff --git a/resources/boost/skills/sharp-field-catalog/SKILL.md b/resources/boost/skills/sharp-field-catalog/SKILL.md new file mode 100644 index 000000000..739562c69 --- /dev/null +++ b/resources/boost/skills/sharp-field-catalog/SKILL.md @@ -0,0 +1,69 @@ +--- +name: sharp-field-catalog +description: Field type catalog for Sharp Entity Lists, Forms, Show Pages, and Dashboard widgets, plus how validation actually works (rules(), not per-field setRequired()). Use when adding/choosing a field or widget class, or when validating form input. +--- + +# Sharp Field Catalog + +## When to use this skill +Use this when deciding which field class fits a piece of data (e.g. "add a status dropdown", "let users upload a PDF", "show related orders on the show page") - to avoid guessing a class name or a fluent method that doesn't exist. + +## Validation is not per-field +There is no `setRequired()` / `setRules()` on form fields. Validation happens in `SharpForm::rules()` (returned as standard Laravel validation rules, keyed by field name) or inline via `$this->validate($data, [...])` inside `update()`/`store()`. A field's `make()`/`setLabel()` chain only controls how it's *displayed and edited*, never whether it's required. + +```php +public function rules(): array +{ + return [ + 'name' => ['required', 'string', 'max:150'], + 'email' => ['required', 'email'], + ]; +} +``` + +## Form fields (`Code16\Sharp\Form\Fields\...`) +| Class | Use for | Notable config | +|---|---|---| +| `SharpFormTextField` | short text | `setMaxLength()` | +| `SharpFormTextareaField` | multi-line plain text | `setMaxLength()` | +| `SharpFormEditorField` | rich text (WYSIWYG/markdown) | `setToolbar()`, `setRenderContentAsMarkdown()`, `setMaxLength()` | +| `SharpFormHtmlField` | static read-only HTML block, not bound to data | - | +| `SharpFormNumberField` | numeric input | `setMin()`, `setMax()`, `setStep()` | +| `SharpFormCheckField` | single boolean checkbox | `setText()` | +| `SharpFormDateField` | date/datetime picker | `setHasTime()`, `setDisplayFormat()` | +| `SharpFormSelectField` | dropdown / radio / checkbox list from a **fixed** option list | options passed as 2nd arg to `make($key, $options)`; `setMultiple()`, `setDisplayAsList()`/`setDisplayAsDropdown()` | +| `SharpFormAutocompleteLocalField` | search-as-you-type over a **local, already-loaded** dataset | `setLocalValues()`, `setLocalSearchKeys()` | +| `SharpFormAutocompleteRemoteField` | search-as-you-type hitting a **remote endpoint/DB query** | `setRemoteEndpoint()` or `setRemoteCallback()`, `setRemoteSearchAttribute()` | +| `SharpFormTagsField` | multiple free-form or creatable tags | `setCreatable()`, `setMaxTagCount()` | +| `SharpFormListField` | repeatable group of sub-fields (an "array" of items) | `setAddable()`, `setSortable()`, add sub-fields via `addItemField()` | +| `SharpFormUploadField` | file/image upload | `setImageOnly()`, `setMaxFileSize()`, `setImageCropRatio()` | +| `SharpFormGeolocationField` | lat/lng picker on a map | `setGeocoding()`, `setApiKey()` | + +Use `->addField(SharpFormSelectField::make('status', ['draft' => 'Draft', 'published' => 'Published']))` for a fixed-choice field - options are an associative array of `value => label` passed directly to `make()`, not set via a separate method. + +## Show fields (`Code16\Sharp\Show\Fields\...`) +| Class | Use for | +|---|---| +| `SharpShowTextField` | plain/formatted text | +| `SharpShowPictureField` | image display | +| `SharpShowFileField` | downloadable file/document | +| `SharpShowListField` | repeated group of sub-values (mirrors `SharpFormListField`) | +| `SharpShowEntityListField` | embed another entity's Entity List inside this show page (e.g. an order's line items) | +| `SharpShowDashboardField` | embed a Dashboard view inside a show page | + +## Entity List fields (`Code16\Sharp\EntityList\Fields\...`) +| Class | Use for | +|---|---| +| `EntityListField` | generic column (text, formatted value via a transformer) | +| `EntityListBadgeField` | colored badge/pill (e.g. status) | +| `EntityListStateField` | the entity's state selector column, when using [Entity States](https://sharp.code16.fr/docs/guide/entity-states) | + +## Dashboard widgets (`Code16\Sharp\Dashboard\Widgets\...`) +| Class | Use for | +|---|---| +| `SharpFigureWidget` | a single KPI number, set via `setFigureData($key, $value)` in `buildWidgetsData()` | +| `SharpPanelWidget` | free-form HTML/content panel | +| `SharpOrderedListWidget` | a small ranked/ordered list (e.g. top products) | +| `SharpLineGraphWidget` / `SharpBarGraphWidget` / `SharpAreaGraphWidget` / `SharpPieGraphWidget` | time series / categorical charts, data supplied as one or more `SharpGraphWidgetDataSet` in `buildWidgetsData()` | + +All widgets are declared in `buildWidgets()` and positioned in `buildDashboardLayout()`, but their actual values are only set in `buildWidgetsData()` - a widget with no matching `setFigureData()`/dataset call in that method will render empty.