Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions resources/boost/guidelines/core.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
<code-snippet name="Sharp Entity" lang="php">
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;
}
</code-snippet>
@endverbatim

#### Entity Lists
Entity Lists are used to display a list of records.
@verbatim
Expand Down Expand Up @@ -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
Expand Down
108 changes: 108 additions & 0 deletions resources/boost/skills/sharp-crud-scaffolding/SKILL.md
Original file line number Diff line number Diff line change
@@ -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
69 changes: 69 additions & 0 deletions resources/boost/skills/sharp-field-catalog/SKILL.md
Original file line number Diff line number Diff line change
@@ -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.
Loading