From 4d8218baf45a4d83b0a688d537f870d831c0ff07 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 2 Feb 2026 17:51:01 +0100 Subject: [PATCH 1/4] Add guidelines --- .../boost/guidelines/blueprint.blade.php | 112 +++++++++++++++++ resources/boost/guidelines/core.blade.php | 118 ++++++++++++++++++ 2 files changed, 230 insertions(+) create mode 100644 resources/boost/guidelines/blueprint.blade.php create mode 100644 resources/boost/guidelines/core.blade.php diff --git a/resources/boost/guidelines/blueprint.blade.php b/resources/boost/guidelines/blueprint.blade.php new file mode 100644 index 000000000..3661f332c --- /dev/null +++ b/resources/boost/guidelines/blueprint.blade.php @@ -0,0 +1,112 @@ +## Sharp Blueprint +A Sharp Blueprint is a detailed implementation plan that helps AI agents write correct Sharp code. It bridges the gap between requirements and implementation by providing a structured specification for Sharp's unique patterns. + +### Core Philosophy +Sharp separates the data structure from its visual representation. A blueprint should clearly map domain models to Sharp components: +- **Entity Lists**: The primary data table. +- **Forms**: Data entry and validation. +- **Show Pages**: Detailed read-only views. +- **Commands**: Business logic triggers. +- **State Handlers**: Lifecycle management. + +### Blueprint Structure +When asking an AI to create a blueprint, it should produce: +1. **User Flows**: Step-by-step navigation through the feature. +2. **Commands**: Required Artisan commands for scaffolding. +3. **Models**: Detailed attribute, relationship, and cast definitions. +4. **Sharp Components**: Specific configurations for Lists, Forms, Shows, and Commands. + +--- + +## SaaS Invoicing System - Sharp Implementation Plan + +### Overview +A single-tenant admin panel for managing customers, products, invoices with line items, and tracking payments. + +### User Flows +#### Flow 1: Creating an Invoice +1. User navigates to Invoices List -> "New" +2. User selects a Customer (Select field with autocomplete) +3. User sets invoice date and due date +4. User adds line items via List field: + - Select Product + - Enter quantity + - Line total auto-calculates (handled in model/transformer) +5. Tax rate is entered +6. Invoice saves as Draft status + +#### Flow 2: Sending an Invoice +1. User views a Draft invoice in its Show Page +2. User clicks "Send" (Instance Command) +3. Confirmation modal appears +4. On confirm: status -> Sent, notification sent + +### Models +#### Model: Customer +- **Attributes**: `name`, `email`, `phone`, `address`, `notes` +- **Relationships**: `hasMany` Invoices + +#### Model: Invoice +- **Attributes**: `invoice_number`, `status` (Enum), `invoice_date`, `due_date`, `tax_rate` +- **Relationships**: `belongsTo` Customer, `hasMany` InvoiceItem, `hasMany` Payment + +### Sharp Components + +#### InvoiceEntity +- **Location**: `App\Sharp\Entities\InvoiceEntity` +- **Config**: + - `list`: `InvoiceList` + - `show`: `InvoiceShow` + - `form`: `InvoiceForm` + - `state`: `InvoiceStateHandler` + +#### InvoiceList +- **Columns**: + - `invoice_number`: label "Number", sortable + - `customer:name`: label "Customer" + - `invoice_date`: label "Date", sortable + - `total`: label "Total", money format + - `state`: label "Status", state badge +- **Filters**: `InvoiceStatusFilter`, `DateRangeFilter` + +#### InvoiceForm +- **Layout**: + - Section "General": `customer_id`, `invoice_date`, `due_date` + - Section "Items": `items` (List field) + - Section "Totals": `tax_rate`, `notes` +- **Fields**: +@verbatim + +SharpFormListField::make('items') + ->setLabel('Items') + ->addItemField( + SharpFormSelectField::make('product_id', $products)->setLabel('Product') + ) + ->addItemField( + SharpFormTextField::make('quantity')->setLabel('Quantity')->setInputModeNumeric() + ) + +@endverbatim + +#### InvoiceShow +- **Sections**: + - Header: Number, Status, Customer Info + - Content: `items` (Entity List section or List field) + - Sidebar: `payments` (Entity List section) +- **Commands**: `SendInvoiceCommand`, `RecordPaymentCommand` + +#### InvoiceStateHandler +@verbatim + +class InvoiceStateHandler extends SharpEntityStateHandler +{ + public function buildStates(): void + { + $this->addState('draft', 'Draft', 'gray') + ->addState('sent', 'Sent', 'blue') + ->addState('paid', 'Paid', 'green') + ->addState('cancelled', 'Cancelled', 'red'); + } +} + +@endverbatim diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php new file mode 100644 index 000000000..e57c0cc24 --- /dev/null +++ b/resources/boost/guidelines/core.blade.php @@ -0,0 +1,118 @@ +## Sharp +- Sharp is used by this application. Follow existing conventions for how and where it's implemented. +- Sharp is a content management framework for Laravel that allows you to define user interfaces in PHP using structured configuration objects. +- Sharp allows you to build Entity Lists, Forms, Show Pages, and Dashboards. + +### Patterns +Use `make()` static methods to initialize fields, columns, and other components. + +#### Entity Lists +Entity Lists are used to display a list of records. +@verbatim + +use Code16\Sharp\EntityList\Fields\EntityListField; +use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; +use Code16\Sharp\EntityList\SharpEntityList; + +class UserList extends SharpEntityList +{ + protected function buildList(EntityListFieldsContainer $fields): void + { + $fields + ->addField( + EntityListField::make('name') + ->setLabel('Name') + ->setSortable() + ) + ->addField( + EntityListField::make('email') + ->setLabel('Email') + ); + } +} + +@endverbatim + +#### Forms +Forms are used to create or edit records. +@verbatim + +use Code16\Sharp\Form\Fields\SharpFormTextField; +use Code16\Sharp\Form\Fields\SharpFormEditorField; +use Code16\Sharp\Form\SharpForm; +use Code16\Sharp\Utils\Fields\FieldsContainer; + +class UserForm extends SharpForm +{ + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields + ->addField( + SharpFormTextField::make('name') + ->setLabel('Name') + ->setRequired() + ) + ->addField( + SharpFormEditorField::make('bio') + ->setLabel('Biography') + ); + } +} + +@endverbatim + +#### Show Pages +Show Pages are used to display details of a single record. +@verbatim + +use Code16\Sharp\Show\Fields\SharpShowTextField; +use Code16\Sharp\Show\Layout\ShowLayout; +use Code16\Sharp\Show\SharpShow; +use Code16\Sharp\Utils\Fields\FieldsContainer; + +class UserShow extends SharpShow +{ + protected function buildShowFields(FieldsContainer $showFields): void + { + $showFields + ->addField(SharpShowTextField::make('name')->setLabel('Name')) + ->addField(SharpShowTextField::make('email')->setLabel('Email')); + } + + protected function buildShowLayout(ShowLayout $showLayout): void + { + $showLayout + ->addSection('General', function ($section) { + $section->addColumn(6, function ($column) { + $column->withFields('name', 'email'); + }); + }); + } +} + +@endverbatim + +### Transformers +Sharp uses Transformers to map your model data to the format expected by the UI. +@verbatim + +public function find($id): array +{ + return $this + ->setCustomTransformer('name', function($value, $user) { + return strtoupper($value); + }) + ->transform(User::findOrFail($id)); +} + +@endverbatim + +### Common Classes & Namespaces +- **Entity Lists:** `Code16\Sharp\EntityList\SharpEntityList` +- **Forms:** `Code16\Sharp\Form\SharpForm` +- **Show Pages:** `Code16\Sharp\Show\SharpShow` +- **Dashboards:** `Code16\Sharp\Dashboard\SharpDashboard` +- **Form Fields:** `Code16\Sharp\Form\Fields\...` +- **Show Fields:** `Code16\Sharp\Show\Fields\...` +- **Entity List Fields:** `Code16\Sharp\EntityList\Fields\...` +- **Eloquent Updater:** `Code16\Sharp\Form\Eloquent\WithSharpFormEloquentUpdater` From a614b2dcd43d631e37c70ef8a7be0dc5eb670005 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 2 Feb 2026 18:25:25 +0100 Subject: [PATCH 2/4] Update guidelines --- .../boost/guidelines/blueprint.blade.php | 922 ++++++++++++++++-- resources/boost/guidelines/core.blade.php | 143 ++- 2 files changed, 967 insertions(+), 98 deletions(-) diff --git a/resources/boost/guidelines/blueprint.blade.php b/resources/boost/guidelines/blueprint.blade.php index 3661f332c..e79cc0a43 100644 --- a/resources/boost/guidelines/blueprint.blade.php +++ b/resources/boost/guidelines/blueprint.blade.php @@ -1,111 +1,863 @@ -## Sharp Blueprint +# Sharp Blueprint + A Sharp Blueprint is a detailed implementation plan that helps AI agents write correct Sharp code. It bridges the gap between requirements and implementation by providing a structured specification for Sharp's unique patterns. -### Core Philosophy +## Core Philosophy + Sharp separates the data structure from its visual representation. A blueprint should clearly map domain models to Sharp components: -- **Entity Lists**: The primary data table. -- **Forms**: Data entry and validation. -- **Show Pages**: Detailed read-only views. -- **Commands**: Business logic triggers. -- **State Handlers**: Lifecycle management. +- **Entity Lists**: The primary data table view +- **Forms**: Data entry and validation +- **Show Pages**: Detailed read-only views +- **Commands**: Business logic triggers (instance, entity, wizard) +- **State Handlers**: Lifecycle management +- **Filters**: Data filtering for entity lists + +## Blueprint Structure -### Blueprint Structure When asking an AI to create a blueprint, it should produce: -1. **User Flows**: Step-by-step navigation through the feature. -2. **Commands**: Required Artisan commands for scaffolding. -3. **Models**: Detailed attribute, relationship, and cast definitions. -4. **Sharp Components**: Specific configurations for Lists, Forms, Shows, and Commands. +1. **Overview**: Brief description of the system and key decisions +2. **User Flows**: Step-by-step navigation through features +3. **Commands**: Required Artisan commands for scaffolding +4. **Models**: Detailed attributes, relationships, casts, and methods +5. **Sharp Resources**: Specific configurations for Lists, Forms, Shows, Commands, and State Handlers --- -## SaaS Invoicing System - Sharp Implementation Plan +# SaaS Invoicing System - Sharp Implementation Plan + +## Overview + +A single-tenant admin panel for managing customers, products, invoices with line items, and tracking payments. All authenticated users can access all data. + +**Key decisions:** +- Single-tenant (all authenticated users see all data) +- Manual invoice sending (mark as sent, no email automation) +- Partial payments supported (multiple payments until balance = 0) +- In-app Sharp notifications only -### Overview -A single-tenant admin panel for managing customers, products, invoices with line items, and tracking payments. +--- + +## User Flows -### User Flows -#### Flow 1: Creating an Invoice -1. User navigates to Invoices List -> "New" -2. User selects a Customer (Select field with autocomplete) +### Flow 1: Creating an Invoice +1. User navigates to Invoices → Create +2. User selects a Customer (autocomplete field with search) 3. User sets invoice date and due date 4. User adds line items via List field: - - Select Product + - Select Product (autocomplete, shows name + price) - Enter quantity - - Line total auto-calculates (handled in model/transformer) -5. Tax rate is entered -6. Invoice saves as Draft status + - Line total auto-calculates (quantity × unit_price) +5. Tax rate is entered (percentage) +6. Subtotal, tax amount, and total auto-calculate +7. Invoice saves as Draft status with auto-generated invoice number -#### Flow 2: Sending an Invoice -1. User views a Draft invoice in its Show Page -2. User clicks "Send" (Instance Command) +### Flow 2: Sending an Invoice +1. User views a Draft invoice in Show Page +2. User clicks "Mark as Sent" (Instance Command) 3. Confirmation modal appears -4. On confirm: status -> Sent, notification sent - -### Models -#### Model: Customer -- **Attributes**: `name`, `email`, `phone`, `address`, `notes` -- **Relationships**: `hasMany` Invoices - -#### Model: Invoice -- **Attributes**: `invoice_number`, `status` (Enum), `invoice_date`, `due_date`, `tax_rate` -- **Relationships**: `belongsTo` Customer, `hasMany` InvoiceItem, `hasMany` Payment - -### Sharp Components - -#### InvoiceEntity -- **Location**: `App\Sharp\Entities\InvoiceEntity` -- **Config**: - - `list`: `InvoiceList` - - `show`: `InvoiceShow` - - `form`: `InvoiceForm` - - `state`: `InvoiceStateHandler` - -#### InvoiceList -- **Columns**: - - `invoice_number`: label "Number", sortable - - `customer:name`: label "Customer" - - `invoice_date`: label "Date", sortable - - `total`: label "Total", money format - - `state`: label "Status", state badge -- **Filters**: `InvoiceStatusFilter`, `DateRangeFilter` - -#### InvoiceForm -- **Layout**: - - Section "General": `customer_id`, `invoice_date`, `due_date` - - Section "Items": `items` (List field) - - Section "Totals": `tax_rate`, `notes` -- **Fields**: +4. On confirm: status → Sent, sent_at → now() +5. Success notification shown + +### Flow 3: Recording a Payment +1. User views a Sent invoice (or Overdue) +2. User clicks "Record Payment" (Instance Command) +3. Modal form appears with: + - Amount (defaults to balance due, validates ≤ balance) + - Payment method (select) + - Reference (optional) + - Payment date +4. On submit: Payment record created +5. If total payments ≥ invoice total: status → Paid, paid_at → now() +6. Success notification shown + +### Flow 4: Viewing Invoice Status +1. User navigates to Invoices list +2. Table shows status badges (Draft/Sent/Paid/Overdue/Cancelled) +3. User can filter by status, customer, date range +4. User can search by invoice number or customer name + +--- + +## Commands + +Run these in order: + +```bash +# 1. Create models with migrations and factories +php artisan make:model Customer -mf --no-interaction +php artisan make:model Product -mf --no-interaction +php artisan make:model Invoice -mf --no-interaction +php artisan make:model InvoiceItem -mf --no-interaction +php artisan make:model Payment -mf --no-interaction + +# 2. Create enums +php artisan make:enum InvoiceStatus +php artisan make:enum PaymentMethod + +# 3. Create Sharp resources +php artisan sharp:make:entity-list InvoiceList --model=Invoice +php artisan sharp:make:form InvoiceForm --model=Invoice +php artisan sharp:make:show InvoiceShow --model=Invoice +php artisan sharp:make:entity-list CustomerList --model=Customer +php artisan sharp:make:form CustomerForm --model=Customer +php artisan sharp:make:show CustomerShow --model=Customer +php artisan sharp:make:entity-list ProductList --model=Product +php artisan sharp:make:form ProductForm --model=Product + +# 4. Create state handler +php artisan sharp:make:state-handler InvoiceStateHandler + +# 5. Create commands +php artisan sharp:make:instance-command SendInvoiceCommand +php artisan sharp:make:instance-command RecordPaymentCommand + +# 6. Create filters +php artisan sharp:make:entity-list-filter InvoiceStatusFilter +php artisan sharp:make:entity-list-filter CustomerFilter +php artisan sharp:make:entity-list-filter InvoiceDateRangeFilter +``` + +--- + +## Models + +### Enum: InvoiceStatus + +``` +Enum: InvoiceStatus + Location: App\Enums\InvoiceStatus + Type: string backed enum + Cases: + - Draft = 'draft' + - Sent = 'sent' + - Paid = 'paid' + - Overdue = 'overdue' + - Cancelled = 'cancelled' + Methods: + - label(): string (Draft, Sent, Paid, Overdue, Cancelled) + - color(): string (gray, blue, green, red, orange) +``` + +### Enum: PaymentMethod + +``` +Enum: PaymentMethod + Location: App\Enums\PaymentMethod + Type: string backed enum + Cases: + - Cash = 'cash' + - Check = 'check' + - BankTransfer = 'bank_transfer' + - CreditCard = 'credit_card' + - Other = 'other' + Methods: + - label(): string +``` + +### Model: Customer + +``` +Model: Customer + Table: customers + Attributes: + - id: bigint, primary + - name: string, required + - email: string, required + - phone: string, nullable + - address_line_1: string, nullable + - address_line_2: string, nullable + - city: string, nullable + - state: string, nullable + - postal_code: string, nullable + - country: string, nullable + - notes: text, nullable + - created_at: timestamp + - updated_at: timestamp + - deleted_at: timestamp, nullable + Relationships: + - hasMany: Invoice via customer_id + Traits: + - SoftDeletes +``` + +### Model: Product + +``` +Model: Product + Table: products + Attributes: + - id: bigint, primary + - name: string, required + - sku: string, nullable, unique + - description: text, nullable + - unit_price: integer, required (stored in cents) + - is_active: boolean, default:true + - created_at: timestamp + - updated_at: timestamp + - deleted_at: timestamp, nullable + Relationships: + - hasMany: InvoiceItem via product_id + Traits: + - SoftDeletes + Casts: + - unit_price: integer + - is_active: boolean +``` + +### Model: Invoice + +``` +Model: Invoice + Table: invoices + Attributes: + - id: bigint, primary + - customer_id: bigint, foreign(customers.id), required + - invoice_number: string, required, unique + - status: string, default:'draft' (uses InvoiceStatus enum) + - invoice_date: date, required + - due_date: date, required + - subtotal: integer, default:0 (cents) + - tax_rate: decimal(5,2), default:0 + - tax_amount: integer, default:0 (cents) + - total: integer, default:0 (cents) + - amount_paid: integer, default:0 (cents) + - notes: text, nullable + - sent_at: timestamp, nullable + - paid_at: timestamp, nullable + - created_at: timestamp + - updated_at: timestamp + - deleted_at: timestamp, nullable + Relationships: + - belongsTo: Customer via customer_id + - hasMany: InvoiceItem via invoice_id + - hasMany: Payment via invoice_id + Traits: + - SoftDeletes + Casts: + - status: InvoiceStatus::class + - invoice_date: date + - due_date: date + - tax_rate: decimal:2 + - sent_at: datetime + - paid_at: datetime + Accessors: + - balance_due: int (total - amount_paid) + Methods: + - generateInvoiceNumber(): string (format: INV-YYYYMM-XXXX) + - recalculateTotals(): void (sum line items, apply tax) + - markAsSent(): void + - markAsPaid(): void + - recordPayment(int $amount, PaymentMethod $method, ?string $reference, Carbon $date): Payment +``` + +### Model: InvoiceItem + +``` +Model: InvoiceItem + Table: invoice_items + Attributes: + - id: bigint, primary + - invoice_id: bigint, foreign(invoices.id), required, onDelete:cascade + - product_id: bigint, foreign(products.id), nullable + - description: string, required + - quantity: integer, required, default:1 + - unit_price: integer, required (cents) + - total: integer, required (cents, quantity × unit_price) + - sort_order: integer, default:0 + - created_at: timestamp + - updated_at: timestamp + Relationships: + - belongsTo: Invoice via invoice_id + - belongsTo: Product via product_id (nullable) + Casts: + - quantity: integer + - unit_price: integer + - total: integer + - sort_order: integer +``` + +### Model: Payment + +``` +Model: Payment + Table: payments + Attributes: + - id: bigint, primary + - invoice_id: bigint, foreign(invoices.id), required, onDelete:cascade + - amount: integer, required (cents) + - method: string, required (uses PaymentMethod enum) + - reference: string, nullable + - payment_date: date, required + - notes: text, nullable + - created_at: timestamp + - updated_at: timestamp + Relationships: + - belongsTo: Invoice via invoice_id + Casts: + - method: PaymentMethod::class + - amount: integer + - payment_date: date +``` + +--- + +## Sharp Resources + +### CustomerList + @verbatim - -SharpFormListField::make('items') - ->setLabel('Items') - ->addItemField( - SharpFormSelectField::make('product_id', $products)->setLabel('Product') - ) - ->addItemField( - SharpFormTextField::make('quantity')->setLabel('Quantity')->setInputModeNumeric() - ) - +``` +Resource: CustomerList + Location: App\Sharp\Customers\CustomerList + Extends: Code16\Sharp\EntityList\SharpEntityList + Docs: https://sharp.code16.fr/docs/guide/building-entity-list + + Method: buildList(EntityListFieldsContainer $fields): void + Fields: + - EntityListField::make('name') + ->setLabel('Name') + ->setSortable() + + - EntityListField::make('email') + ->setLabel('Email') + ->setSortable() + + - EntityListField::make('phone') + ->setLabel('Phone') + ->hideOnSmallScreens() + + - EntityListField::make('city') + ->setLabel('City') + ->hideOnSmallScreens() + + - EntityListField::make('invoices_count') + ->setLabel('Invoices') + ->setSortable() + + Method: buildListConfig(): void + Config: + - configureSearchable() + - configureDefaultSort('name', 'asc') + - configureReorderable(false) + - configurePaginated() + + Method: getListData(EntityListQueryParams $params): array + - Query: Customer::query()->withCount('invoices') + - Search: name, email, phone + - Transform: id, name, email, phone, city, invoices_count +``` +@endverbatim + +### CustomerForm + +@verbatim +``` +Resource: CustomerForm + Location: App\Sharp\Customers\CustomerForm + Extends: Code16\Sharp\Form\SharpForm + Docs: https://sharp.code16.fr/docs/guide/building-form + + Method: buildFormFields(FieldsContainer $formFields): void + Fields: + - SharpFormTextField::make('name') + ->setLabel('Name') + ->setMaxLength(255) + + - SharpFormTextField::make('email') + ->setLabel('Email') + ->setMaxLength(255) + + - SharpFormTextField::make('phone') + ->setLabel('Phone') + ->setMaxLength(50) + + - SharpFormTextField::make('address_line_1') + ->setLabel('Address Line 1') + ->setMaxLength(255) + + - SharpFormTextField::make('address_line_2') + ->setLabel('Address Line 2') + ->setMaxLength(255) + + - SharpFormTextField::make('city') + ->setLabel('City') + ->setMaxLength(100) + + - SharpFormTextField::make('state') + ->setLabel('State') + ->setMaxLength(100) + + - SharpFormTextField::make('postal_code') + ->setLabel('Postal Code') + ->setMaxLength(20) + + - SharpFormTextField::make('country') + ->setLabel('Country') + ->setMaxLength(100) + + - SharpFormTextareaField::make('notes') + ->setLabel('Notes') + ->setRowCount(4) + + Method: buildFormLayout(FormLayout $formLayout): void + Layout: + - Column 8: + - Fieldset "Contact Information": + - name (full width) + - Row: email, phone + - Fieldset "Address": + - address_line_1 (full width) + - address_line_2 (full width) + - Row: city, state, postal_code + - country (full width) + - Column 4: + - Fieldset "Additional Information": + - notes (full width) + + Method: create(): array + - Return: empty customer data array + + Method: update(mixed $id, array $data): bool + - Find/create Customer model + - Save attributes + - Return true + + Method: find(mixed $id): array + - Find Customer by id + - Transform to array +``` @endverbatim -#### InvoiceShow -- **Sections**: - - Header: Number, Status, Customer Info - - Content: `items` (Entity List section or List field) - - Sidebar: `payments` (Entity List section) -- **Commands**: `SendInvoiceCommand`, `RecordPaymentCommand` +### InvoiceList + +@verbatim +``` +Resource: InvoiceList + Location: App\Sharp\Invoices\InvoiceList + Extends: Code16\Sharp\EntityList\SharpEntityList + Docs: https://sharp.code16.fr/docs/guide/building-entity-list + + Method: buildList(EntityListFieldsContainer $fields): void + Fields: + - EntityListField::make('invoice_number') + ->setLabel('Number') + ->setSortable() + + - EntityListField::make('customer:name') + ->setLabel('Customer') + ->setSortable() + + - EntityListField::make('invoice_date') + ->setLabel('Date') + ->setSortable() + + - EntityListField::make('due_date') + ->setLabel('Due Date') + ->setSortable() + ->hideOnSmallScreens() + + - EntityListField::make('total') + ->setLabel('Total') + ->setSortable() + + - EntityListStateField::make() + ->setLabel('Status') + + Method: buildListConfig(): void + Config: + - configureSearchable() + - configureDefaultSort('invoice_date', 'desc') + - configureEntityState('status', InvoiceStateHandler::class) + - configurePaginated() + + Filters: + - InvoiceStatusFilter + - CustomerFilter + - InvoiceDateRangeFilter + + Method: getListData(EntityListQueryParams $params): array + - Query: Invoice::with('customer') + - Search: invoice_number, customer.name + - Filters: status, customer_id, date range + - Transform: id, invoice_number, customer:name, invoice_date, due_date, total (formatted), status +``` +@endverbatim + +### InvoiceForm + +@verbatim +``` +Resource: InvoiceForm + Location: App\Sharp\Invoices\InvoiceForm + Extends: Code16\Sharp\Form\SharpForm + Docs: https://sharp.code16.fr/docs/guide/building-form + + Method: buildFormFields(FieldsContainer $formFields): void + Fields: + - SharpFormAutocompleteField::make('customer_id', 'remote') + ->setLabel('Customer') + ->setRemoteEndpoint('/sharp/api/autocomplete/customers') + ->setResultItemInlineTemplate('{{ $name }} - {{ $email }}') + ->setListItemInlineTemplate('{{ $name }}') + + - SharpFormTextField::make('invoice_number') + ->setLabel('Invoice Number') + ->setReadOnly() + ->setMaxLength(50) + + - SharpFormDateField::make('invoice_date') + ->setLabel('Invoice Date') + + - SharpFormDateField::make('due_date') + ->setLabel('Due Date') + + - SharpFormListField::make('items') + ->setLabel('Line Items') + ->setAddable() + ->setRemovable() + ->setSortable() + ->setOrderAttribute('sort_order') + ->addItemField( + SharpFormAutocompleteField::make('product_id', 'remote') + ->setLabel('Product') + ->setRemoteEndpoint('/sharp/api/autocomplete/products') + ->setResultItemInlineTemplate('{{ $name }} - {{ $unit_price }}') + ) + ->addItemField( + SharpFormTextField::make('description') + ->setLabel('Description') + ->setMaxLength(255) + ) + ->addItemField( + SharpFormTextField::make('quantity') + ->setLabel('Quantity') + ) + ->addItemField( + SharpFormTextField::make('unit_price') + ->setLabel('Unit Price') + ) + ->addItemField( + SharpFormTextField::make('total') + ->setLabel('Total') + ->setReadOnly() + ) + + - SharpFormTextField::make('tax_rate') + ->setLabel('Tax Rate (%)') + ->setInputTypeNumber() + ->setStep(0.01) + + - SharpFormTextField::make('subtotal') + ->setLabel('Subtotal') + ->setReadOnly() + + - SharpFormTextField::make('tax_amount') + ->setLabel('Tax Amount') + ->setReadOnly() + + - SharpFormTextField::make('total') + ->setLabel('Total') + ->setReadOnly() + + - SharpFormTextareaField::make('notes') + ->setLabel('Notes') + ->setRowCount(4) + + Method: buildFormLayout(FormLayout $formLayout): void + Layout: + - Column 8: + - Fieldset "General": + - customer_id (full width) + - invoice_number (full width) + - Row: invoice_date, due_date + - Fieldset "Line Items": + - items (full width) + - Fieldset "Notes": + - notes (full width) + - Column 4: + - Fieldset "Totals": + - tax_rate (full width) + - subtotal (full width) + - tax_amount (full width) + - total (full width) + + Method: create(): array + - Generate invoice number + - Set default dates + - Return empty invoice data + + Method: update(mixed $id, array $data): bool + - Find/create Invoice model + - Save attributes and relationships + - Recalculate totals + - Return true + + Method: find(mixed $id): array + - Find Invoice with items + - Transform to array with calculated totals +``` +@endverbatim + +### InvoiceShow + +@verbatim +``` +Resource: InvoiceShow + Location: App\Sharp\Invoices\InvoiceShow + Extends: Code16\Sharp\Show\SharpShow + Docs: https://sharp.code16.fr/docs/guide/building-show-page + + Method: buildShowFields(FieldsContainer $showFields): void + Fields: + - SharpShowTextField::make('invoice_number') + ->setLabel('Invoice Number') + + - SharpShowTextField::make('status') + ->setLabel('Status') + + - SharpShowTextField::make('customer') + ->setLabel('Customer') + + - SharpShowTextField::make('invoice_date') + ->setLabel('Invoice Date') + + - SharpShowTextField::make('due_date') + ->setLabel('Due Date') + + - SharpShowListField::make('items') + ->setLabel('Line Items') + ->addItemField(SharpShowTextField::make('description')->setLabel('Description')) + ->addItemField(SharpShowTextField::make('quantity')->setLabel('Quantity')) + ->addItemField(SharpShowTextField::make('unit_price')->setLabel('Unit Price')) + ->addItemField(SharpShowTextField::make('total')->setLabel('Total')) + + - SharpShowTextField::make('subtotal') + ->setLabel('Subtotal') + + - SharpShowTextField::make('tax_rate') + ->setLabel('Tax Rate') + + - SharpShowTextField::make('tax_amount') + ->setLabel('Tax Amount') + + - SharpShowTextField::make('total') + ->setLabel('Total') + + - SharpShowTextField::make('amount_paid') + ->setLabel('Amount Paid') + + - SharpShowTextField::make('balance_due') + ->setLabel('Balance Due') + + - SharpShowEntityListField::make('payments', PaymentEntity::class) + ->setLabel('Payments') + ->hideFilterWithValue('invoice', fn($instanceId) => $instanceId) + + - SharpShowTextField::make('notes') + ->setLabel('Notes') + + Method: buildShowLayout(ShowLayout $showLayout): void + Layout: + - Section "Invoice Details": + - Column 8: + - invoice_number, status, customer + - invoice_date, due_date + - Column 4: (empty for spacing) + - Section "Line Items": + - Column 12: + - items (full width) + - Section "Totals": + - Column 8: (empty) + - Column 4: + - subtotal, tax_rate, tax_amount, total + - amount_paid, balance_due + - Section "Payments": + - Column 12: + - payments (full width) + - Section "Additional Information": + - Column 12: + - notes (full width) + + Method: find(mixed $id): array + - Find Invoice with customer, items, payments + - Transform to array with formatted values + + Commands: + - SendInvoiceCommand (instance) + - RecordPaymentCommand (instance) +``` +@endverbatim + +### InvoiceStateHandler -#### InvoiceStateHandler @verbatim -class InvoiceStateHandler extends SharpEntityStateHandler +namespace App\Sharp\Invoices; + +use App\Enums\InvoiceStatus; +use Code16\Sharp\EntityList\Commands\EntityState; + +class InvoiceStateHandler extends EntityState { - public function buildStates(): void + protected function buildStates(): void + { + $this + ->addState(InvoiceStatus::Draft->value, InvoiceStatus::Draft->label(), InvoiceStatus::Draft->color()) + ->addState(InvoiceStatus::Sent->value, InvoiceStatus::Sent->label(), InvoiceStatus::Sent->color()) + ->addState(InvoiceStatus::Paid->value, InvoiceStatus::Paid->label(), InvoiceStatus::Paid->color()) + ->addState(InvoiceStatus::Overdue->value, InvoiceStatus::Overdue->label(), InvoiceStatus::Overdue->color()) + ->addState(InvoiceStatus::Cancelled->value, InvoiceStatus::Cancelled->label(), InvoiceStatus::Cancelled->color()); + } + + protected function updateState(mixed $instanceId, string $stateId): array { - $this->addState('draft', 'Draft', 'gray') - ->addState('sent', 'Sent', 'blue') - ->addState('paid', 'Paid', 'green') - ->addState('cancelled', 'Cancelled', 'red'); + $invoice = \App\Models\Invoice::findOrFail($instanceId); + + $invoice->update([ + 'status' => InvoiceStatus::from($stateId), + ]); + + if ($stateId === InvoiceStatus::Sent->value && !$invoice->sent_at) { + $invoice->update(['sent_at' => now()]); + } + + if ($stateId === InvoiceStatus::Paid->value && !$invoice->paid_at) { + $invoice->update(['paid_at' => now()]); + } + + return $this->reload(); + } +} + +@endverbatim + +### SendInvoiceCommand + +@verbatim + +namespace App\Sharp\Invoices\Commands; + +use App\Enums\InvoiceStatus; +use App\Models\Invoice; +use Code16\Sharp\EntityList\Commands\InstanceCommand; + +class SendInvoiceCommand extends InstanceCommand +{ + public function label(): string + { + return 'Mark as Sent'; + } + + public function execute(mixed $instanceId, array $data = []): array + { + $invoice = Invoice::findOrFail($instanceId); + + if ($invoice->status !== InvoiceStatus::Draft) { + return $this->error('Only draft invoices can be sent.'); + } + + $invoice->markAsSent(); + + return $this->reload(); + } + + public function authorizeFor(mixed $instanceId): bool + { + $invoice = Invoice::find($instanceId); + + return $invoice && $invoice->status === InvoiceStatus::Draft; + } +} + +@endverbatim + +### RecordPaymentCommand + +@verbatim + +namespace App\Sharp\Invoices\Commands; + +use App\Enums\PaymentMethod; +use App\Models\Invoice; +use Code16\Sharp\EntityList\Commands\InstanceCommand; +use Code16\Sharp\Form\Fields\SharpFormDateField; +use Code16\Sharp\Form\Fields\SharpFormSelectField; +use Code16\Sharp\Form\Fields\SharpFormTextareaField; +use Code16\Sharp\Form\Fields\SharpFormTextField; +use Code16\Sharp\Utils\Fields\FieldsContainer; + +class RecordPaymentCommand extends InstanceCommand +{ + public function label(): string + { + return 'Record Payment'; + } + + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields + ->addField( + SharpFormTextField::make('amount') + ->setLabel('Amount') + ) + ->addField( + SharpFormSelectField::make('method', collect(PaymentMethod::cases())->map(fn($case) => [ + 'id' => $case->value, + 'label' => $case->label(), + ])->all()) + ->setLabel('Payment Method') + ) + ->addField( + SharpFormTextField::make('reference') + ->setLabel('Reference') + ->setMaxLength(255) + ) + ->addField( + SharpFormDateField::make('payment_date') + ->setLabel('Payment Date') + ) + ->addField( + SharpFormTextareaField::make('notes') + ->setLabel('Notes') + ->setRowCount(3) + ); + } + + public function initialData(mixed $instanceId): array + { + $invoice = Invoice::findOrFail($instanceId); + + return [ + 'amount' => $invoice->balance_due / 100, + 'payment_date' => now()->format('Y-m-d'), + ]; + } + + public function execute(mixed $instanceId, array $data = []): array + { + $invoice = Invoice::findOrFail($instanceId); + + $amountInCents = (int) ($data['amount'] * 100); + + if ($amountInCents > $invoice->balance_due) { + return $this->error('Payment amount cannot exceed balance due.'); + } + + $invoice->recordPayment( + $amountInCents, + PaymentMethod::from($data['method']), + $data['reference'] ?? null, + \Carbon\Carbon::parse($data['payment_date']) + ); + + return $this->reload(); + } + + public function authorizeFor(mixed $instanceId): bool + { + $invoice = Invoice::find($instanceId); + + return $invoice && $invoice->balance_due > 0; } } diff --git a/resources/boost/guidelines/core.blade.php b/resources/boost/guidelines/core.blade.php index e57c0cc24..9dec5e723 100644 --- a/resources/boost/guidelines/core.blade.php +++ b/resources/boost/guidelines/core.blade.php @@ -10,9 +10,11 @@ Entity Lists are used to display a list of records. @verbatim + use Code16\Sharp\EntityList\Fields\EntityListField; use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; use Code16\Sharp\EntityList\SharpEntityList; +use Illuminate\Contracts\Support\Arrayable; class UserList extends SharpEntityList { @@ -29,6 +31,29 @@ protected function buildList(EntityListFieldsContainer $fields): void ->setLabel('Email') ); } + + public function buildListConfig(): void + { + $this + ->configureSearchable() + ->configureDefaultSort('name', 'asc'); + } + + public function getListData(): array|Arrayable + { + $users = User::query() + ->when($this->queryParams->hasSearch(), function ($query) { + foreach ($this->queryParams->searchWords() as $word) { + $query->where('name', 'like', $word); + } + }) + ->orderBy( + $this->queryParams->sortedBy() ?: 'name', + $this->queryParams->sortedDir() ?: 'asc' + ); + + return $this->transform($users->paginate(30)); + } } @endverbatim @@ -38,7 +63,9 @@ protected function buildList(EntityListFieldsContainer $fields): void @verbatim use Code16\Sharp\Form\Fields\SharpFormTextField; -use Code16\Sharp\Form\Fields\SharpFormEditorField; +use Code16\Sharp\Form\Fields\SharpFormTextareaField; +use Code16\Sharp\Form\Layout\FormLayout; +use Code16\Sharp\Form\Layout\FormLayoutColumn; use Code16\Sharp\Form\SharpForm; use Code16\Sharp\Utils\Fields\FieldsContainer; @@ -50,13 +77,41 @@ public function buildFormFields(FieldsContainer $formFields): void ->addField( SharpFormTextField::make('name') ->setLabel('Name') - ->setRequired() + ->setMaxLength(150) ) ->addField( - SharpFormEditorField::make('bio') + SharpFormTextareaField::make('bio') ->setLabel('Biography') + ->setMaxLength(500) + ); + } + + public function buildFormLayout(FormLayout $formLayout): void + { + $formLayout + ->addColumn(6, fn (FormLayoutColumn $column) => $column + ->withField('name') + ->withField('bio') ); } + + public function find($id): array + { + return $this->transform(User::findOrFail($id)); + } + + public function update($id, array $data) + { + $this->validate($data, [ + 'name' => ['required', 'string', 'max:150'], + ]); + + $user = $id ? User::findOrFail($id) : new User(); + $user->fill($data); + $user->save(); + + return $user->id; + } } @endverbatim @@ -67,6 +122,8 @@ public function buildFormFields(FieldsContainer $formFields): void use Code16\Sharp\Show\Fields\SharpShowTextField; use Code16\Sharp\Show\Layout\ShowLayout; +use Code16\Sharp\Show\Layout\ShowLayoutColumn; +use Code16\Sharp\Show\Layout\ShowLayoutSection; use Code16\Sharp\Show\SharpShow; use Code16\Sharp\Utils\Fields\FieldsContainer; @@ -75,34 +132,94 @@ class UserShow extends SharpShow protected function buildShowFields(FieldsContainer $showFields): void { $showFields - ->addField(SharpShowTextField::make('name')->setLabel('Name')) - ->addField(SharpShowTextField::make('email')->setLabel('Email')); + ->addField( + SharpShowTextField::make('name') + ->setLabel('Name') + ) + ->addField( + SharpShowTextField::make('email') + ->setLabel('Email') + ) + ->addField( + SharpShowTextField::make('bio') + ->setLabel('Biography') + ); } protected function buildShowLayout(ShowLayout $showLayout): void { $showLayout - ->addSection('General', function ($section) { - $section->addColumn(6, function ($column) { - $column->withFields('name', 'email'); - }); - }); + ->addSection(fn (ShowLayoutSection $section) => $section + ->addColumn(6, fn (ShowLayoutColumn $column) => $column + ->withField('name') + ->withField('email') + ->withField('bio') + ) + ); + } + + public function find(mixed $id): array + { + return $this->transform(User::findOrFail($id)); + } + + public function delete($id): void + { + User::findOrFail($id)->delete(); } } @endverbatim ### Transformers -Sharp uses Transformers to map your model data to the format expected by the UI. +Sharp uses Transformers to map your model data to the format expected by the UI. Use `setCustomTransformer()` to transform field values before displaying them. @verbatim -public function find($id): array +// In Entity Lists, Forms, or Show Pages +public function getListData(): array|Arrayable { + $users = User::with('company')->get(); + return $this ->setCustomTransformer('name', function($value, $user) { return strtoupper($value); }) - ->transform(User::findOrFail($id)); + ->setCustomTransformer('company:name', function($value, $user) { + return $value ?? 'N/A'; + }) + ->transform($users); +} + +@endverbatim + +### Common Configuration Methods +@verbatim + +// Entity List Configuration +public function buildListConfig(): void +{ + $this + ->configureSearchable() + ->configureDefaultSort('created_at', 'desc') + ->configureDelete(confirmationText: 'Delete this item?') + ->configureCreateButtonLabel('Add new...'); +} + +// Form Configuration +public function buildFormConfig(): void +{ + $this + ->configureDisplayShowPageAfterCreation() + ->configureCreateFormTitle('Create new user') + ->configureEditFormTitle('Edit user'); +} + +// Show Configuration +public function buildShowConfig(): void +{ + $this + ->configureBreadcrumbCustomLabelAttribute('name') + ->configurePageTitle('name'); } @endverbatim From 0b9d64d503107304b719280ff13a09ce25bbac84 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 2 Feb 2026 18:58:21 +0100 Subject: [PATCH 3/4] add boost to demo --- composer.json | 3 +- demo/.ai/guidelines | 1 + demo/.gitignore | 14 +++ demo/composer.json | 1 + demo/composer.lock | 202 +++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 219 insertions(+), 2 deletions(-) create mode 120000 demo/.ai/guidelines diff --git a/composer.json b/composer.json index 88f784e35..c23d20709 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,8 @@ "test": "vendor/bin/pest --parallel", "typescript:generate": "php demo/artisan sharp:typescript-generate", "post-autoload-dump": [ - "git config core.hooksPath scripts/hooks" + "git config core.hooksPath scripts/hooks", + "@php artisan boost:update --ansi" ] }, "extra": { diff --git a/demo/.ai/guidelines b/demo/.ai/guidelines new file mode 120000 index 000000000..45f9e054c --- /dev/null +++ b/demo/.ai/guidelines @@ -0,0 +1 @@ +../../resources/boost/guidelines \ No newline at end of file diff --git a/demo/.gitignore b/demo/.gitignore index acda100fc..6f85a790f 100644 --- a/demo/.gitignore +++ b/demo/.gitignore @@ -10,3 +10,17 @@ node_modules .DS_Store .phpunit.result.cache .env + +# AI +AGENTS.md +boost.json +.claude +CLAUDE.md +.cursor +.gemini +GEMINI.md +.github +.junie +opencode.json +.mcp.json +.vscode diff --git a/demo/composer.json b/demo/composer.json index ef55e31f4..847e2894f 100644 --- a/demo/composer.json +++ b/demo/composer.json @@ -24,6 +24,7 @@ "require-dev": { "fakerphp/faker": "^1.9.1", "itsgoingd/clockwork": "^5.1", + "laravel/boost": "^1.8", "laravel/sail": "^1.0.1", "mockery/mockery": "^1.4.4", "nunomaduro/collision": "^8.6", diff --git a/demo/composer.lock b/demo/composer.lock index 148d524d8..4578bbcfe 100644 --- a/demo/composer.lock +++ b/demo/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b1313d765c8b8fbe765f35c0e151e138", + "content-hash": "479e7666753599f88fecc36d866c8ae9", "packages": [ { "name": "bacon/bacon-qr-code", @@ -7494,6 +7494,206 @@ ], "time": "2025-09-14T15:34:49+00:00" }, + { + "name": "laravel/boost", + "version": "v1.8.10", + "source": { + "type": "git", + "url": "https://github.com/laravel/boost.git", + "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/boost/zipball/aad8b2a423b0a886c2ce7ee92abbfde69992ff32", + "reference": "aad8b2a423b0a886c2ce7ee92abbfde69992ff32", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^7.9", + "illuminate/console": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/support": "^10.49.0|^11.45.3|^12.41.1", + "laravel/mcp": "^0.5.1", + "laravel/prompts": "0.1.25|^0.3.6", + "laravel/roster": "^0.2.9", + "php": "^8.1" + }, + "require-dev": { + "laravel/pint": "^1.20.0", + "mockery/mockery": "^1.6.12", + "orchestra/testbench": "^8.36.0|^9.15.0|^10.6", + "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5", + "phpstan/phpstan": "^2.1.27", + "rector/rector": "^2.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Boost\\BoostServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Boost\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Laravel Boost accelerates AI-assisted development by providing the essential context and structure that AI needs to generate high-quality, Laravel-specific code.", + "homepage": "https://github.com/laravel/boost", + "keywords": [ + "ai", + "dev", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/boost/issues", + "source": "https://github.com/laravel/boost" + }, + "time": "2026-01-14T14:51:16+00:00" + }, + { + "name": "laravel/mcp", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/laravel/mcp.git", + "reference": "39b9791b989927642137dd5b55dde0529f1614f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/mcp/zipball/39b9791b989927642137dd5b55dde0529f1614f9", + "reference": "39b9791b989927642137dd5b55dde0529f1614f9", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "illuminate/console": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/container": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/http": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/json-schema": "^12.41.1", + "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/support": "^10.49.0|^11.45.3|^12.41.1", + "illuminate/validation": "^10.49.0|^11.45.3|^12.41.1", + "php": "^8.1" + }, + "require-dev": { + "laravel/pint": "^1.20", + "orchestra/testbench": "^8.36|^9.15|^10.8", + "pestphp/pest": "^2.36.0|^3.8.4|^4.1.0", + "phpstan/phpstan": "^2.1.27", + "rector/rector": "^2.2.4" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Mcp": "Laravel\\Mcp\\Server\\Facades\\Mcp" + }, + "providers": [ + "Laravel\\Mcp\\Server\\McpServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Laravel\\Mcp\\": "src/", + "Laravel\\Mcp\\Server\\": "src/Server/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Rapidly build MCP servers for your Laravel applications.", + "homepage": "https://github.com/laravel/mcp", + "keywords": [ + "laravel", + "mcp" + ], + "support": { + "issues": "https://github.com/laravel/mcp/issues", + "source": "https://github.com/laravel/mcp" + }, + "time": "2026-01-26T10:25:21+00:00" + }, + { + "name": "laravel/roster", + "version": "v0.2.9", + "source": { + "type": "git", + "url": "https://github.com/laravel/roster.git", + "reference": "82bbd0e2de614906811aebdf16b4305956816fa6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/roster/zipball/82bbd0e2de614906811aebdf16b4305956816fa6", + "reference": "82bbd0e2de614906811aebdf16b4305956816fa6", + "shasum": "" + }, + "require": { + "illuminate/console": "^10.0|^11.0|^12.0", + "illuminate/contracts": "^10.0|^11.0|^12.0", + "illuminate/routing": "^10.0|^11.0|^12.0", + "illuminate/support": "^10.0|^11.0|^12.0", + "php": "^8.1|^8.2", + "symfony/yaml": "^6.4|^7.2" + }, + "require-dev": { + "laravel/pint": "^1.14", + "mockery/mockery": "^1.6", + "orchestra/testbench": "^8.22.0|^9.0|^10.0", + "pestphp/pest": "^2.0|^3.0", + "phpstan/phpstan": "^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Laravel\\Roster\\RosterServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Roster\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Detect packages & approaches in use within a Laravel project", + "homepage": "https://github.com/laravel/roster", + "keywords": [ + "dev", + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/roster/issues", + "source": "https://github.com/laravel/roster" + }, + "time": "2025-10-20T09:56:46+00:00" + }, { "name": "laravel/sail", "version": "v1.51.0", From 1e3bbb04f45bb6621e9def5761b7327554319b6a Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 3 Feb 2026 18:58:17 +0100 Subject: [PATCH 4/4] Fix composer.json --- composer.json | 3 +-- demo/composer.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index c23d20709..88f784e35 100644 --- a/composer.json +++ b/composer.json @@ -64,8 +64,7 @@ "test": "vendor/bin/pest --parallel", "typescript:generate": "php demo/artisan sharp:typescript-generate", "post-autoload-dump": [ - "git config core.hooksPath scripts/hooks", - "@php artisan boost:update --ansi" + "git config core.hooksPath scripts/hooks" ] }, "extra": { diff --git a/demo/composer.json b/demo/composer.json index 847e2894f..063132031 100644 --- a/demo/composer.json +++ b/demo/composer.json @@ -53,7 +53,8 @@ "post-autoload-dump": [ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi", - "@php artisan vendor:publish --tag=sharp-assets --force" + "@php artisan vendor:publish --tag=sharp-assets --force", + "@php artisan boost:update --ansi" ], "post-update-cmd": [ "@php artisan vendor:publish --tag=laravel-assets --ansi --force"