Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .ai/guidelines/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
- **Uploads have two paths.** Multipart uploads go through `UploadMediaAction` and land on `config('media.disk')`;
with `media.signed_uploads` enabled the action instead finalizes an already-uploaded temp key out of the `tmp/`
prefix. Accepted types come from `config('media.accepted_types')` (mime patterns, `image/*` wildcards included).
- **Version coupling.** The package requires `lattice-php/lattice` `>=0.29.0 <1.0.0` — pre-1.0 minors are treated as
- **Version coupling.** The package requires `lattice-php/lattice` `>=0.36.0 <1.0.0` — pre-1.0 minors are treated as
compatible, so a caret (which pins one 0.x minor) is deliberately avoided. Features that depend on newer core APIs
must raise the lower bound and wait for the corresponding core release.

Expand Down
25 changes: 6 additions & 19 deletions .ai/guidelines/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,27 @@
- This is a first-party companion package for [Lattice](https://github.com/lattice-php/lattice). It ships the media
library — the PHP models, component, field, actions, and table plus its React renderer as **source** (no separate npm
package); the consumer's build compiles it via Lattice's `lattice()` Vite plugin.
- The package is developed with Orchestra Testbench, not a full Laravel app. `artisan` at the repo root is a symlink to
`vendor/bin/testbench`, so `php artisan <command>` boots the Testbench skeleton with Lattice's and this package's
service providers.
- Run the PHP suite with `composer test` (the Feature suite; the Browser suite is excluded on purpose).
- Run the PHP suite with `composer test` (the Unit and Feature suites; the Browser suite is excluded on purpose).
- Run the JavaScript (renderer) suite with `npm test` (Vitest). The tests exercise the renderer against the **published**
`@lattice-php/lattice`, so `npm install` before running them.
- Run the browser suite with `composer test:browser`. It rebuilds the workbench bundle first (`npm run build`) so it
can never test stale assets — do not bypass that by invoking Pest's Browser suite directly after renderer changes.
- Serve the workbench demo app with `composer serve` (starts at `/media`; `/media-picker` exercises the field inside a
form — `testbench.yaml` holds the env/migrations/build wiring).
- The AI tooling overrides for Boost live in `workbench/app/Support/` and are wired in
`Workbench\App\Providers\WorkbenchServiceProvider`. They point Boost at the package root instead of the Testbench
skeleton.
- `bambamboole/extended-testbench` is what makes Boost work in a package repo: it rebases `base_path()` to the package
root for `boost:*` and `mcp:*` commands only, and ships the comments/git/Testbench guidelines composed into
`CLAUDE.md` and `AGENTS.md`. Do not reintroduce local Boost overrides.
- `CLAUDE.md` and `AGENTS.md` are generated (git-ignored). They regenerate automatically after `composer install`; run
`php artisan boost:update` (or `composer boost:refresh`) by hand after editing files in `.ai/guidelines/`.

## Verification

- Before finishing a change, run the gate that matches what you touched:
- PHP change → `composer check` (Pint, PHPStan, Pest).
- PHP change → `composer check` (Pint, PHPStan, Rector, Pest).
- Renderer change → `npm run typecheck` and `npm test`.
- Anything touching the workbench app, an endpoint, or interactive behavior → additionally `composer test:browser`.
- Never report green without having run the gate. CI runs all three.

## Comments

- Code must be self-explanatory: reach for clear names, small functions, and types before a comment.
- Do not add comments. A comment is a last resort and explains only *why* something is done, never *what* the code does.
- When you encounter an obsolete, redundant, or "what" comment, delete it.
- Keep PHPDoc/JSDoc only when it carries type information, public API intent, static-analysis value, or a non-obvious
constraint.
- Keep comments that explain framework quirks, ordering requirements, browser/test timing, or other constraints that are
hard to infer from the code alone.

## Testing

- Prefer feature tests for backend behavior — serialize a component and assert its wire shape, drive an action or form
Expand All @@ -45,6 +32,6 @@
`InteractsWithLatticeComponents` trait (`latticeRef()`, `callAction()`, `callBulkAction()`, `submitForm()`).
- For renderer behavior — grid rendering, selection, search, the detail slideout, the picker's hidden inputs — use the
Vitest suite in `resources/js`, with the local `test-support.ts` fixtures.
- For real-browser coverage (Playwright via Pest 4) use `tests/Browser`; `BrowserTestCase` guards against a missing or
- For real-browser coverage (Playwright via Pest) use `tests/Browser`; `BrowserTestCase` guards against a missing or
stale workbench build, and the `assert*Eventually` helpers in `tests/Support/Browser.php` absorb async UI settling.
- It is acceptable to add stable `data-test` attributes when they make assertions clearer or less brittle.
12 changes: 0 additions & 12 deletions .ai/guidelines/git.md

This file was deleted.

5 changes: 3 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ resources/js/**/*.test.* export-ignore
/.gitignore export-ignore
/artisan export-ignore
/boost.json export-ignore
/phpunit.xml export-ignore
/phpstan.neon export-ignore
/phpunit.xml.dist export-ignore
/phpstan.neon.dist export-ignore
/pint.json export-ignore
/rector.php export-ignore
/testbench.yaml export-ignore
/vite.config.ts export-ignore
/vite.lattice-deep-imports.ts export-ignore
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ env:

jobs:
php:
name: PHP ${{ matrix.php-version }} (${{ matrix.php-version == '8.4' && 'pint, phpstan, pest' || 'pest' }})
name: PHP ${{ matrix.php-version }} (${{ matrix.php-version == '8.4' && 'pint, phpstan, rector, pest' || 'pest' }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -53,7 +53,7 @@ jobs:

- run: composer install --prefer-dist --no-progress --no-interaction

- name: Pint, PHPStan, Pest
- name: Pint, PHPStan, Rector, Pest
if: ${{ matrix.php-version == '8.4' }}
run: composer check

Expand Down
10 changes: 6 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
/vendor/
/node_modules/
composer.lock
/composer.lock
.env
.env.*
.phpunit.cache/
/.phpunit.cache/
.phpunit.result.cache
/.idea/
/AGENTS.md
/CLAUDE.md
/.claude/
/AGENTS.md
/.mcp.json
/.claude/skills/
/.agents/
/.junie/
/docs/superpowers/
/.superpowers/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ them like any Laravel package translation).

```bash
composer install && npm install
composer check # pint --test, phpstan, pest (Feature)
composer check # pint --test, phpstan, rector --dry-run, pest (Unit + Feature)
npm run typecheck && npm test
composer test:browser # rebuilds the workbench bundle, then runs the Playwright suite
composer serve # workbench demo app: /media (library) and /media-picker (field in a form)
Expand Down
1 change: 0 additions & 1 deletion artisan

This file was deleted.

4 changes: 4 additions & 0 deletions artisan
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env php
<?php

require __DIR__.'/vendor/bin/testbench';
3 changes: 3 additions & 0 deletions boost.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"guidelines": true,
"mcp": true,
"nightwatch": false,
"packages": [
"bambamboole/extended-testbench"
],
"sail": false,
"skills": [
"laravel-best-practices",
Expand Down
29 changes: 15 additions & 14 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
"lattice-php/lattice": ">=0.36.0 <1.0.0"
},
"require-dev": {
"bambamboole/extended-testbench": "^0.3",
"bambamboole/laravel-i18next": "^0.6.0",
"larastan/larastan": "^3.0",
"laravel/boost": "^2.4",
"laravel/pint": "^1.16",
"league/flysystem-aws-s3-v3": "^3.34",
"mrpunyapal/peststan": "^0.2.12",
"orchestra/testbench": "^9.0 || ^10.0 || ^11.0",
"pestphp/pest": "^4.0",
"pestphp/pest-plugin-browser": "^4.3",
"pestphp/pest-plugin-laravel": "^4.0"
"pestphp/pest": "^5.0",
"pestphp/pest-plugin-browser": "^5.0",
"pestphp/pest-plugin-laravel": "^5.0",
"pestphp/pest-plugin-phpstan": "^5.0",
"rector/rector": "^2.0"
},
"autoload": {
"psr-4": {
Expand Down Expand Up @@ -54,20 +55,20 @@
"post-update-cmd": [
"@boost:refresh"
],
"boost:update": "@php vendor/bin/testbench boost:update --no-interaction",
"boost:refresh": "[ -n \"$CI\" ] || [ ! -f vendor/bin/testbench ] || vendor/bin/testbench boost:update --no-interaction",
"boost:refresh": "[ -n \"$CI\" ] || [ ! -f vendor/bin/testbench ] || [ ! -f boost.json ] || vendor/bin/testbench boost:update --no-interaction || true",
"serve": "@php vendor/bin/testbench serve",
"test": "@php vendor/bin/pest --testsuite Feature",
"test": "pest --testsuite=Unit,Feature",
"test:browser": [
"npm run build",
"@php vendor/bin/pest --testsuite Browser"
"pest --testsuite=Browser"
],
"test:lint": "@php vendor/bin/pint --test",
"lint": "@php vendor/bin/pint",
"analyse": "@php vendor/bin/phpstan analyse",
"lint": "pint --format agent",
"stan": "phpstan analyse",
"refactor": "rector",
"check": [
"@test:lint",
"@analyse",
"pint --test",
"phpstan analyse",
"rector --dry-run",
"@test"
]
},
Expand Down
1 change: 1 addition & 0 deletions database/factories/MediaFactory.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Database\Factories;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
Expand Down
7 changes: 4 additions & 3 deletions phpstan.neon → phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
includes:
- vendor/larastan/larastan/extension.neon
- vendor/mrpunyapal/peststan/extension.neon
- vendor/pestphp/pest/extension.neon
- vendor/pestphp/pest-plugin-phpstan/extension.neon

parameters:
level: 6
paths:
- src
- database
- tests
- workbench/app
level: 6
- database
13 changes: 13 additions & 0 deletions phpunit.xml → phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
colors="true"
cacheDirectory=".phpunit.cache">
<testsuites>
<testsuite name="Unit">
<directory>tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory>tests/Feature</directory>
</testsuite>
Expand All @@ -17,4 +20,14 @@
<directory>src</directory>
</include>
</source>
<php>
<env name="APP_DEBUG" value="true"/>
<env name="DB_CONNECTION" value="sqlite"/>
<env name="DB_DATABASE" value=":memory:"/>
<env name="CACHE_STORE" value="array"/>
<env name="SESSION_DRIVER" value="array"/>
<env name="MAIL_MAILER" value="array"/>
<env name="QUEUE_CONNECTION" value="sync"/>
<env name="BCRYPT_ROUNDS" value="4"/>
</php>
</phpunit>
3 changes: 1 addition & 2 deletions pint.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
{
"preset": "laravel",
"rules": {
"declare_strict_types": true,
"blank_line_after_opening_tag": false
"declare_strict_types": true
}
}
26 changes: 26 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

use Rector\CodeQuality\Rector\Identical\FlipTypeControlToUseExclusiveTypeRector;
use Rector\Config\RectorConfig;
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPublicMethodParameterRector;
use Rector\TypeDeclaration\Rector\Closure\AddClosureVoidReturnTypeWhereNoReturnRector;

return RectorConfig::configure()
->withPaths([__DIR__.'/src', __DIR__.'/tests', __DIR__.'/workbench/app'])
->withPhpSets()
->withPreparedSets(deadCode: true, codeQuality: true, typeDeclarations: true)
->withSkip([
// Untyped Pest closures are the house style in tests/; typing every test callback
// is churn that adds no safety. Scoped to tests/ so src/ keeps the rule.
AddClosureVoidReturnTypeWhereNoReturnRector::class => [__DIR__.'/tests'],

// Laravel resolves many signatures by reflection — policy methods, middleware handle(),
// listeners, authorize(). Stripping a parameter the body ignores breaks them at runtime.
RemoveUnusedPublicMethodParameterRector::class,

// Rewrites `$x !== null` to `$x instanceof <fully qualified class>`, which is neither
// shorter nor clearer than the null check it replaces.
FlipTypeControlToUseExclusiveTypeRector::class,
]);
1 change: 1 addition & 0 deletions src/Actions/DeleteMediaAction.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Actions;
Expand Down
1 change: 1 addition & 0 deletions src/Actions/DeleteSelectedMediaAction.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Actions;
Expand Down
1 change: 1 addition & 0 deletions src/Actions/UpdateMediaAction.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Actions;
Expand Down
1 change: 1 addition & 0 deletions src/Actions/UploadMediaAction.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Actions;
Expand Down
1 change: 1 addition & 0 deletions src/Components/MediaLibrary.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Components;
Expand Down
1 change: 1 addition & 0 deletions src/Console/Commands/GenerateConversionsCommand.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Console\Commands;
Expand Down
3 changes: 2 additions & 1 deletion src/Forms/Components/MediaPicker.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Forms\Components;
Expand Down Expand Up @@ -140,7 +141,7 @@ public function prefillRowFields(mixed $rows, ?FormData $form = null, ?Request $
*/
protected function rowsOf(mixed $value): array
{
if ($value === null || $value === '' || $value === []) {
if (in_array($value, [null, '', []], true)) {
return [];
}

Expand Down
1 change: 1 addition & 0 deletions src/Forms/RichEditor/MediaImage.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Forms\RichEditor;
Expand Down
2 changes: 2 additions & 0 deletions src/Forms/RichEditor/MediaImageNode.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Forms\RichEditor;
Expand All @@ -20,6 +21,7 @@ final class MediaImageNode extends Node
/**
* @return array<string, array{default: null}>
*/
#[\Override]
public function addAttributes(): array
{
return [
Expand Down
3 changes: 2 additions & 1 deletion src/Jobs/GenerateMediaConversions.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

declare(strict_types=1);

namespace Lattice\Media\Jobs;
Expand Down Expand Up @@ -46,7 +47,7 @@ public function __construct(
public function middleware(): array
{
return [
(new WithoutOverlapping((string) $this->media->getKey()))
new WithoutOverlapping((string) $this->media->getKey())
->releaseAfter(30)
->expireAfter(300),
];
Expand Down
1 change: 1 addition & 0 deletions src/MediaServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

final class MediaServiceProvider extends ServiceProvider
{
#[\Override]
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/../config/media.php', 'media');
Expand Down
Loading