agentsclimarketplace

Ux

Skill aronpc/ai/skills/ux

Otimiza UX com Laravel Precognition, Prompts e Turbo para interações fluidas. Use quando precisar melhorar experiência do usuário, adicionar validação em tempo real, ou implementar Turbo/HTMX.From its SKILL.md

Install
npx -y skills add aronpc/ai --skill ux

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

14.9 KB, as published. Nobody here has run it

ux

Resumo

Melhora UX com Precognition, Prompts interativos e HMR para Inertia.js.

Skills Relacionadas

SkillQuando usar junto
ui-uxPara identificar melhorias de UX
i18nPara UX com traduções
mcpPara validar UX visualmente
testingPara testar flows de UX

Quando usar

Use esta skill sempre que:

  • Implementar validação preemptiva com Precognition
  • Criar Artisan commands interativos com Prompts
  • Configurar HMR para Inertia.js
  • Criar CLI user experience otimizada
  • Melhorar feedback visual em forms
  • Configurar Vite para desenvolvimento

Laravel Precognition

Instalação

composer require laravel/precognition
# React puro (não-Inertia)
npm install laravel-precognition-react
# React + Inertia (use este pacote no lugar do anterior ao usar Inertia)
npm install laravel-precognition-react-inertia

Configuração Backend

O Laravel Precognition já fornece o middleware nativo Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests. Não é necessário criar um middleware custom — basta aplicá-lo à rota que deve suportar validação preemptiva (veja a próxima seção).

Configurar Route

// routes/web.php
use App\Http\Controllers\ProductController;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/products', [ProductController::class, 'store'])
    ->middleware([HandlePrecognitiveRequests::class]);

No FormRequest ou controller, é possível checar se a requisição é preemptiva com $request->isPrecognitive().

Frontend React

// resources/js/components/ProductForm.tsx
import { useForm } from 'laravel-precognition-react';

interface ProductForm {
    name: string;
    price: number;
    category_id: number;
}

export function ProductForm() {
    // Ordem dos argumentos: MÉTODO, URL, dados iniciais
    const form = useForm<ProductForm>(
        'post',
        '/products',
        {
            name: '',
            price: 0,
            category_id: 0,
        }
    );

    return (
        <form onSubmit={form.submit}>
            <input
                type="text"
                value={form.data.name}
                onChange={(e) => form.setData('name', e.target.value)}
                onBlur={() => form.validate('name')}
            />
            {form.invalid('name') && (
                <span>{form.errors.name}</span>
            )}

            <input
                type="number"
                value={form.data.price}
                onChange={(e) => form.setData('price', parseFloat(e.target.value))}
                onBlur={() => form.validate('price')}
            />
            {form.invalid('price') && (
                <span>{form.errors.price}</span>
            )}

            <button type="submit" disabled={form.processing}>
                Save
            </button>
        </form>
    );
}

Integração com Inertia

// resources/js/pages/Products/Create.tsx
// No modo Inertia, o useForm do Precognition já integra o Inertia
// (não use o useForm do @inertiajs/react junto).
import { useForm } from 'laravel-precognition-react-inertia';

export default function CreateProduct() {
    // Ordem dos argumentos: MÉTODO, URL, dados iniciais
    const form = useForm('post', '/products', {
        name: '',
        price: 0,
        category_id: 0,
    });

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        // No modo Inertia, submit() recebe as visit options do Inertia
        form.submit({
            preserveScroll: true,
            onSuccess: () => form.reset(),
        });
    };

    return (
        <form onSubmit={handleSubmit}>
            <input
                type="text"
                value={form.data.name}
                onChange={(e) => form.setData('name', e.target.value)}
                onBlur={() => form.validate('name')}
            />
            {form.invalid('name') && <span className="error">{form.errors.name}</span>}

            <button type="submit" disabled={form.processing}>
                Save
            </button>
        </form>
    );
}

Laravel Prompts

Instalação

composer require laravel/prompts

Command Básico

php artisan make:command ImportProducts
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use App\Actions\Product\ImportProductsAction;
use Illuminate\Console\Command;
use Laravel\Prompts\Prompt;

final class ImportProducts extends Command
{
    protected $signature = 'products:import';

    protected $description = 'Import products from CSV';

    public function handle(): int
    {
        // Confirm
        if (! $this->confirm('Do you want to import products?')) {
            return Command::SUCCESS;
        }

        // Select
        $source = $this->choice(
            'Select import source',
            ['csv', 'api', 'xml'],
            'csv'
        );

        // Text input
        $file = $this->ask('Enter file path', 'products.csv');

        // Password/Secret
        $apiKey = $this->secret('Enter API key');

        // Search/Select
        $category = $this->anticipate(
            'Select category',
            ['Electronics', 'Clothing', 'Food'],
            'Electronics'
        );

        // Multi-select
        $fields = $this->choice(
            'Select fields to import',
            ['name', 'price', 'description', 'category'],
            multiple: true
        );

        // Progress bar
        $this->info('Importing products...');

        $products = $this->importFromFile($file);

        $progress = $this->output->createProgressBar($products->count());
        $progress->start();

        foreach ($products as $product) {
            ImportProductsAction::dispatch($product);
            $progress->advance();
        }

        $progress->finish();
        $this->newLine(2);

        $this->info("Successfully imported {$products->count()} products!");

        return Command::SUCCESS;
    }
}

Prompts Avançados

<?php

use Laravel\Prompts\{
    confirm,
    info,
    note,
    alert,
    warning,
    error,
    spin,
    table,
    search,
    multisearch,
    suggest,
    text,
    textarea,
    password,
    select,
    multiselect,
};

// Info/Warning/Error
info('Process started successfully');
note('Remember to backup your data');
warning('This action cannot be undone');
alert('Something went wrong');
error('Failed to process');

// Table
table(
    ['ID', 'Name', 'Price', 'Status'],
    [
        [1, 'Product 1', 'R$ 100,00', 'Active'],
        [2, 'Product 2', 'R$ 200,00', 'Inactive'],
        [3, 'Product 3', 'R$ 150,00', 'Active'],
    ]
);

// Search
$search = search(
    label: 'Search for a product',
    options: fn (string $value) => $this->searchProducts($value),
    placeholder: 'Type to search...',
);

// Multi-search
$categories = multisearch(
    label: 'Select categories',
    options: fn (string $value) => Category::where('name', 'like', "%{$value}%")
        ->pluck('name', 'id')
        ->toArray(),
);

// Suggest
$name = suggest(
    label: 'Product name',
    options: ['Product A', 'Product B', 'Product C'],
    placeholder: 'Start typing...',
);

// Spinner
$result = spin(
    fn () => $this->longRunningTask(),
    'Processing...'
);

// Textarea
$description = textarea(
    label: 'Product description',
    placeholder: 'Enter detailed description...',
    required: true,
    rows: 5,
);

Laravel Turbo (HMR)

Configurar Vite + Inertia HMR

// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.ts'],
            refresh: true,
        }),
    ],
    server: {
        hmr: {
            host: 'localhost',
        },
    },
});

Configuração React Fast Refresh

npm install @vitejs/plugin-react
// vite.config.js
import react from '@vitejs/plugin-react';

export default defineConfig({
    plugins: [
        react(),
        laravel({
            input: ['resources/css/app.css', 'resources/js/app.tsx'],
            refresh: true,
        }),
    ],
});

Hot Module Replacement

// resources/js/app.tsx
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
    resolve: (name) =>
        resolvePageComponent(
            `./Pages/${name}.tsx`,
            import.meta.glob('./Pages/**/*.tsx')
        ),
    setup({ el, App }) {
        // HMR aceito por padrão
        createRoot(el).render(<App />);
    },
});

Performance Tuning

Vite Configurações

// vite.config.js
export default defineConfig({
    build: {
        cssCodeSplit: true,
        sourcemap: true,
        rollupOptions: {
            output: {
                manualChunks: {
                    'vendor': ['react', 'react-dom', '@inertiajs/react'],
                    'filament': ['@filament/support'],
                },
            },
        },
    },
    optimizeDeps: {
        include: ['react', 'react-dom', '@inertiajs/react'],
    },
});

Lazy Loading de Componentes

// resources/js/components/LazyComponent.tsx
import { lazy } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

export function Page() {
    return (
        <Suspense fallback={<div>Loading...</div>}>
            <HeavyComponent />
        </Suspense>
    );
}

Precognition com Throttle

// Reduzir requests de validação
// Ordem dos argumentos: MÉTODO, URL, dados iniciais
const form = useForm('post', '/products', initialData);

// Configura o debounce da validação preemptiva (em milissegundos)
form.setValidationTimeout(500); // esperar 500ms

Casos de Uso

Form Multi-step com Precognition

interface Step1Data { name: string; }
interface Step2Data { price: number; }
interface Step3Data { description: string; }

export function MultiStepForm() {
    const [step, setStep] = useState(1);
    // Ordem dos argumentos: MÉTODO, URL, dados iniciais
    const step1 = useForm<Step1Data>('post', '/products/step1', {});
    const step2 = useForm<Step2Data>('post', '/products/step2', {});
    const step3 = useForm<Step3Data>('post', '/products/step3', {});

    const nextStep = () => {
        // Valida os campos do passo atual (mesmo os que o usuário não tocou)
        // e só avança no callback de sucesso.
        const steps = [
            { form: step1, only: ['name'] },
            { form: step2, only: ['price'] },
            { form: step3, only: ['description'] },
        ];
        const { form, only } = steps[step - 1];
        form.validate({
            only,
            onSuccess: () => setStep(step + 1),
        });
    };

    return (
        <form>
            {step === 1 && <Step1Form form={step1} onNext={nextStep} />}
            {step === 2 && <Step2Form form={step2} onNext={nextStep} />}
            {step === 3 && <Step3Form form={step3} onNext={handleSubmit} />}
        </form>
    );
}

Command Interativo com Prompts

<?php

final class SetupTenant extends Command
{
    protected $signature = 'tenant:setup';

    public function handle(): int
    {
        $this->info('Welcome to Tenant Setup!');

        // Step 1: Basic info
        $name = text('Tenant name', required: true);
        $email = text('Admin email', required: true)
            ->validate('email');

        // Step 2: Plan selection
        $plan = select(
            'Select plan',
            [
                'basic' => 'Basic - R$ 99/mês',
                'pro' => 'Pro - R$ 199/mês',
                'enterprise' => 'Enterprise - Custom',
            ]
        );

        // Step 3: Features
        $features = multiselect(
            'Select features',
            ['inventory', 'pos', 'reports', 'api'],
        );

        // Step 4: Confirm
        if (! $this->confirm("Create tenant '{$name}' with {$plan} plan?", true)) {
            return Command::FAILURE;
        }

        // Create tenant
        $progress = $this->output->createProgressBar(4);
        $progress->start();

        $progress->advance();
        $tenant = CreateTenantAction::run($name, $email, $plan, $features);

        $progress->advance();
        $this->run('db:seed', ['--class' => 'TenantSeeder']);

        $progress->advance();
        $this->run('storage:link');

        $progress->finish();
        $this->newLine();

        $this->info("Tenant {$name} created successfully!");
        table(
            ['Setting', 'Value'],
            [
                ['Tenant ID', $tenant->id],
                ['Name', $tenant->name],
                ['Plan', $tenant->plan->name],
                ['Login URL', route('tenant.login', $tenant->id)],
            ]
        );

        return Command::SUCCESS;
    }
}

Melhores Práticas

✅ FAÇA

  • Use Precognition para forms complexos
  • Throttle requests de validação
    • Forneça feedback visual imediato
    • Use Prompts para commands interativos
    • Implemente progress bars para tarefas longas
    • Configure HMR para desenvolvimento
    • Lazy load componentes pesados
    • Teste UX em múltiplos dispositivos
    • Use table() para dados tabulares

❌ NÃO FAÇA

- Não implemente validação duplicada
- Não ignore feedback visual
- Não crie commands sem feedback
- Não use Prompts para automação (sem interação)
- Não esqueça de tratar erros
- Não sobrecarregue o usuário com opções
- Não use HMR em produção

Checklist de UX

Antes de finalizar feature com UX:

  • Precognition configurado para forms
  • Throttle de validação implementado
  • Feedback visual para todos estados
  • Commands com Prompts criados
  • Progress bars para tarefas longas
  • HMR configurado e funcionando
  • Lazy loading de componentes
  • Tratamento de erros amigável
  • Testes de UX realizados
  • Acessibilidade verificada

Referências Cruzadas

  • Validation: Integrado com Form Requests de architecture
  • i18n: Mensagens de i18n em Prompts e forms
  • Actions: Commands usam Actions de actions
  • Testing: Testar UX com testing

Referências

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.