php artisan boost:add-skill agenticmorf/fluxui-avatar
Save this content to: .claude/skills/skills-skills/SKILL.md
---
package: agenticmorf/fluxui-avatar
source_path: SKILLS.md
repo: https://github.com/AgenticMorf/fluxui-avatars
---
# SKILLS.md — Required Technical Context for AI Agents
This file documents the prerequisite skills and architectural context an AI agent needs to contribute to this repository effectively.
---
## 1. Livewire 4 Lifecycle & Key Differences from v3
| Topic | v3 | v4 (Required) |
|-------|-----|---------------|
| Component registration | `Livewire::component(...)` | Same, but v4 supports auto-discovery |
| Property attributes | `#[LivewireProperty]` | Remove annotation — public props are reactive by default |
| Validation | `protected $rules = []` | `#[Validate]` attribute **or** `protected function rules(): array` |
| File uploads | `WithFileUploads` trait | Same, but `TemporaryUploadedFile` is namespaced under `Livewire\Features\SupportFileUploads` |
| Dependency injection | Not supported in `mount()` args | `boot(MyService $service)` for DI; `mount()` for URL params |
| `wire:model.lazy` | Available | Use `wire:model.live` or `wire:model.blur` in v4 |
**Important for this package**: Always inject `AvatarStorageInterface` via `boot(AvatarStorageInterface $storage)` in the Livewire component, NOT via `mount()`.
---
## 2. Flux UI Component Structures
Flux UI provides Blade components under the `flux::` namespace. Key components used:
```blade
{{-- Avatar display --}}
<flux:avatar src="..." initials="JD" size="lg" shape="circle" />
{{-- File upload --}}
<flux:file-upload wire:model="photo" accept="image/*" />
{{-- Button --}}
<flux:button variant="danger" wire:click="removePhoto">Remove</flux:button>
{{-- Badge for status --}}
<flux:badge color="blue" size="sm">Uploading…</flux:badge>
```
### View Namespace Shadowing
Flux UI registers its views under the `flux` namespace. Laravel resolves namespaced views by searching through all registered paths for that namespace **in registration order**. By calling `$viewFactory->prependNamespace('flux', ...)` in our `ServiceProvider::boot()`, our version of `avatar.blade.php` is checked first.
**Critical:** The shadow view must **not** render `<flux:avatar>` internally — that resolves to the same prepended file and causes infinite recursion. Delegate with `@include('flux::avatar.index', ...)` (Flux’s real template under `stubs/resources/views/flux/avatar/index.blade.php`).
---
## 3. Laravel Service Provider Boot Order
```
Register phase (all providers)
→ ServiceProvider::register() ← bind things in IoC here
Boot phase (all providers, in dependency order)
→ ServiceProvider::boot() ← register views, routes, components here
```
The `FluxuiAvatarServiceProvider::boot()` must run **after** Flux UI's provider has registered its namespace. Using `prependNamespace` ensures our path is inserted at the front of the existing namespace paths array, regardless of provider registration order.
---
## 4. Spatie Media Library Patterns
When using `SpatieMediaLibraryAdapter`:
```php
// Model must implement HasMedia and use InteractsWithMedia
class User extends Authenticatable implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('avatars')
->singleFile(); // ensures old avatar is replaced
}
}
```
The `singleFile()` call on the collection registration in the **User model** (not in the adapter) ensures Spatie automatically removes old media when a new one is added to the same collection.
---
## 5. Laravel Service Container Binding
The `AvatarStorageInterface` is bound in `register()`:
```php
$this->app->bind(AvatarStorageInterface::class, function (): AvatarStorageInterface {
return match (config('fluxui-avatar.driver')) {
'spatie' => new SpatieMediaLibraryAdapter(...),
default => new LaravelDiskAdapter(...),
};
});
```
Livewire resolves `boot()` method dependencies via the container automatically. No manual resolution required in the component.
---
## 6. PestPHP Conventions
```php
// Test file structure
test('it does something', function (): void {
// Arrange
// Act
// Assert
});
// Arch tests
arch('no debugging functions')->expect(['dd', 'dump'])->not->toBeUsed();
// Livewire tests
Livewire::test(AvatarManager::class)
->set('photo', $file)
->assertHasErrors(['photo']);
```
Always use function-based Pest tests. Never use class-based PHPUnit test cases.
---
## 7. Package Discovery
This package uses Laravel's auto-discovery via `extra.laravel.providers` in `composer.json`. No manual registration is required in the host application's `config/app.php`.
This file documents the prerequisite skills and architectural context an AI agent needs to contribute to this repository effectively.
| Topic | v3 | v4 (Required) |
|---|---|---|
| Component registration | Livewire::component(...) |
Same, but v4 supports auto-discovery |
| Property attributes | #[LivewireProperty] |
Remove annotation — public props are reactive by default |
| Validation | protected $rules = [] |
#[Validate] attribute or protected function rules(): array |
| File uploads | WithFileUploads trait |
Same, but TemporaryUploadedFile is namespaced under Livewire\Features\SupportFileUploads |
| Dependency injection | Not supported in mount() args |
boot(MyService $service) for DI; mount() for URL params |
wire:model.lazy |
Available | Use wire:model.live or wire:model.blur in v4 |
Important for this package: Always inject AvatarStorageInterface via boot(AvatarStorageInterface $storage) in the Livewire component, NOT via mount().
Flux UI provides Blade components under the flux:: namespace. Key components used:
{{-- Avatar display --}}
<flux:avatar src="..." initials="JD" size="lg" shape="circle" />
{{-- File upload --}}
<flux:file-upload wire:model="photo" accept="image/*" />
{{-- Button --}}
<flux:button variant="danger" wire:click="removePhoto">Remove</flux:button>
{{-- Badge for status --}}
<flux:badge color="blue" size="sm">Uploading…</flux:badge>
Flux UI registers its views under the flux namespace. Laravel resolves namespaced views by searching through all registered paths for that namespace in registration order. By calling $viewFactory->prependNamespace('flux', ...) in our ServiceProvider::boot(), our version of avatar.blade.php is checked first.
Critical: The shadow view must not render <flux:avatar> internally — that resolves to the same prepended file and causes infinite recursion. Delegate with @include('flux::avatar.index', ...) (Flux’s real template under stubs/resources/views/flux/avatar/index.blade.php).
Register phase (all providers)
→ ServiceProvider::register() ← bind things in IoC here
Boot phase (all providers, in dependency order)
→ ServiceProvider::boot() ← register views, routes, components here
The FluxuiAvatarServiceProvider::boot() must run after Flux UI's provider has registered its namespace. Using prependNamespace ensures our path is inserted at the front of the existing namespace paths array, regardless of provider registration order.
When using SpatieMediaLibraryAdapter:
// Model must implement HasMedia and use InteractsWithMedia
class User extends Authenticatable implements HasMedia
{
use InteractsWithMedia;
public function registerMediaCollections(): void
{
$this->addMediaCollection('avatars')
->singleFile(); // ensures old avatar is replaced
}
}
The singleFile() call on the collection registration in the User model (not in the adapter) ensures Spatie automatically removes old media when a new one is added to the same collection.
The AvatarStorageInterface is bound in register():
$this->app->bind(AvatarStorageInterface::class, function (): AvatarStorageInterface {
return match (config('fluxui-avatar.driver')) {
'spatie' => new SpatieMediaLibraryAdapter(...),
default => new LaravelDiskAdapter(...),
};
});
Livewire resolves boot() method dependencies via the container automatically. No manual resolution required in the component.
// Test file structure
test('it does something', function (): void {
// Arrange
// Act
// Assert
});
// Arch tests
arch('no debugging functions')->expect(['dd', 'dump'])->not->toBeUsed();
// Livewire tests
Livewire::test(AvatarManager::class)
->set('photo', $file)
->assertHasErrors(['photo']);
Always use function-based Pest tests. Never use class-based PHPUnit test cases.
This package uses Laravel's auto-discovery via extra.laravel.providers in composer.json. No manual registration is required in the host application's config/app.php.
How can I help you explore Laravel packages today?