fix: eliminate FOUC, Echo, modal duplicate, icons, test DB isolation

- FIX 1: Remove duplicate modal container (keep @livewire directive, drop <x-modal-container>)
- FIX 2: Add Laravel Echo + Pusher to bootstrap.js with Reverb config
- FIX 3: Cookie-based theme — exclude nimuli_theme from encryption, write cookie in Alpine store, read server-side in layout
- FIX 4: Generate missing PNG favicons (16/32/180/192/512px) via GD
- FIX 5: Create scripts/verify-modals.sh (27 checks, all green)
- BUG: Docker OS env DB_CONNECTION=mysql was overriding phpunit.xml sqlite config, causing RefreshDatabase to migrate:fresh production MySQL — fixed by forcing DB vars in tests/bootstrap.php alongside existing APP_ENV fix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
boban 2026-05-16 13:23:44 +02:00
parent ba67f67d60
commit 6c81dd1d48
11 changed files with 181 additions and 8 deletions

View File

@ -17,6 +17,8 @@ return Application::configure(basePath: dirname(__DIR__))
->withMiddleware(function (Middleware $middleware): void {
$middleware->trustProxies(at: '*');
$middleware->encryptCookies(except: ['nimuli_theme']);
$middleware->web(append: [
LocaleFromUser::class,
SecurityHeaders::class,

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 B

View File

@ -12,6 +12,7 @@ document.addEventListener('alpine:init', () => {
this.current = theme;
document.documentElement.dataset.theme = theme;
localStorage.setItem('nimuli_theme', theme);
document.cookie = 'nimuli_theme=' + theme + '; path=/; max-age=31536000; SameSite=Lax';
},
cycle() {
const order = ['dark', 'light', 'system'];

View File

@ -1,4 +1,17 @@
import axios from 'axios';
window.axios = axios;
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 443,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}" data-theme="{{ auth()->user()?->theme ?? 'dark' }}">
<html lang="{{ app()->getLocale() }}" data-theme="{{ request()->cookie('nimuli_theme') ?? auth()->user()?->theme ?? 'dark' }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@ -19,7 +19,7 @@
<script>
(function () {
try {
var saved = localStorage.getItem('nimuli_theme') || '{{ auth()->user()?->theme ?? 'dark' }}';
var saved = localStorage.getItem('nimuli_theme') || '{{ request()->cookie('nimuli_theme') ?? auth()->user()?->theme ?? 'dark' }}';
var theme = saved === 'system'
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
: saved;
@ -133,9 +133,8 @@
</div>
</div>
<x-modal-container />
@livewireScripts
@livewire('wire-elements-modal')
<script>
(function () {

150
scripts/verify-modals.sh Executable file
View File

@ -0,0 +1,150 @@
#!/usr/bin/env bash
set -euo pipefail
PASS=0; FAIL=0
ok() { echo "$*"; PASS=$((PASS+1)); }
fail(){ echo "$*"; FAIL=$((FAIL+1)); }
APP_URL="${APP_URL:-https://nimuli.com}"
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
echo ""
echo "═══════════════════════════════════════════"
echo " Nimuli — Modal & Layout Verify"
echo " $(date '+%Y-%m-%d %H:%M:%S')"
echo "═══════════════════════════════════════════"
# ── 1. PHP / Composer ─────────────────────────
echo ""
echo "[ 1 ] Dependencies"
cd "$PROJECT_DIR"
docker compose exec -T app php artisan --version &>/dev/null && ok "Laravel reachable" || fail "Laravel not reachable"
# ── 2. wire-elements-modal present once ───────
echo ""
echo "[ 2 ] Modal container in layout"
LAYOUT="resources/views/layouts/nimuli-app.blade.php"
MODAL_COUNT=$(grep -c "wire-elements-modal\|livewire-ui-modal" "$LAYOUT" || true)
if [ "$MODAL_COUNT" -eq 1 ]; then
ok "Exactly 1 modal container in layout"
elif [ "$MODAL_COUNT" -eq 0 ]; then
fail "No modal container found in layout"
else
fail "Duplicate modal containers ($MODAL_COUNT) — remove one"
fi
# ── 3. Echo in bootstrap.js ───────────────────
echo ""
echo "[ 3 ] Laravel Echo"
if grep -q "new Echo" "resources/js/bootstrap.js"; then
ok "Echo instantiated in bootstrap.js"
else
fail "Echo missing from bootstrap.js"
fi
# ── 4. Cookie theme ───────────────────────────
echo ""
echo "[ 4 ] Theme cookie"
if grep -q "nimuli_theme" "bootstrap/app.php"; then
ok "nimuli_theme excluded from cookie encryption"
else
fail "nimuli_theme not excluded from EncryptCookies"
fi
if grep -q "document.cookie.*nimuli_theme" "resources/js/app.js"; then
ok "Theme cookie written in Alpine store"
else
fail "Alpine theme store does not write cookie"
fi
if grep -q "request()->cookie('nimuli_theme')" "resources/views/layouts/nimuli-app.blade.php"; then
ok "Layout reads theme cookie server-side"
else
fail "Layout does not read theme cookie server-side"
fi
# ── 5. PNG icons ──────────────────────────────
echo ""
echo "[ 5 ] Manifest icons"
for f in favicon-16x16.png favicon-32x32.png apple-touch-icon.png android-chrome-192x192.png android-chrome-512x512.png; do
if [ -f "public/$f" ]; then
ok "$f present"
else
fail "$f missing from public/"
fi
done
# ── 6. Modal component files ──────────────────
echo ""
echo "[ 6 ] Modal Livewire components"
MODALS=(
"app/Livewire/Modals/CreateLink.php"
"app/Livewire/Modals/EditLink.php"
"app/Livewire/Modals/DeleteLink.php"
"app/Livewire/Modals/CreateWorkspace.php"
"app/Livewire/Modals/DeleteWorkspace.php"
"app/Livewire/Modals/InviteMember.php"
"app/Livewire/Modals/EditMemberRole.php"
"app/Livewire/Modals/RemoveMember.php"
"app/Livewire/Modals/CreateWebhook.php"
"app/Livewire/Modals/DeleteWebhook.php"
"app/Livewire/Modals/CreateApiToken.php"
"app/Livewire/Modals/RevokeApiToken.php"
"app/Livewire/Modals/ConfirmAction.php"
)
for modal in "${MODALS[@]}"; do
if [ -f "$modal" ]; then
ok "$(basename "$modal" .php)"
else
fail "MISSING: $modal"
fi
done
# ── 7. Tests ──────────────────────────────────
echo ""
echo "[ 7 ] Test suite"
if docker compose exec -T app php artisan test --filter "LinksModals|WorkspaceModals|DevUserSeeder" 2>&1 | grep -q "passed"; then
ok "Pest tests pass"
else
echo " Running full suite (this may take a moment)..."
docker compose exec -T app php artisan test 2>&1 | tail -5
fail "Check output above"
fi
# ── 8. Build compiles ─────────────────────────
echo ""
echo "[ 8 ] Vite build"
if npm run build 2>&1 | grep -q "built in"; then
ok "Vite build clean"
else
fail "Vite build errors — check output"
fi
# ── 9. Seeders ────────────────────────────────
echo ""
echo "[ 9 ] Dev user seeder"
DB_USER=$(sed -n 's/^DB_USERNAME=//p' "$PROJECT_DIR/.env")
DB_PASS=$(sed -n 's/^DB_PASSWORD=//p' "$PROJECT_DIR/.env")
DB_NAME=$(sed -n 's/^DB_DATABASE=//p' "$PROJECT_DIR/.env")
USER_CNT=$(docker compose exec -T mysql mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" \
-se "SELECT COUNT(*) FROM users WHERE email='nexxo@nimuli.com';" 2>/dev/null || echo "0")
if [ "${USER_CNT:-0}" -gt 0 ]; then
ok "nexxo@nimuli.com exists in DB"
else
fail "nexxo@nimuli.com not found — run: php artisan db:seed"
fi
# ── Summary ───────────────────────────────────
echo ""
echo "═══════════════════════════════════════════"
TOTAL=$((PASS + FAIL))
echo " Passed: $PASS / $TOTAL"
if [ "$FAIL" -gt 0 ]; then
echo " Failed: $FAIL"
echo "═══════════════════════════════════════════"
echo ""
exit 1
else
echo " All checks passed ✓"
echo "═══════════════════════════════════════════"
echo ""
fi

View File

@ -1,9 +1,17 @@
<?php
// Force APP_ENV=testing before anything else loads
// This overrides Docker container OS env (APP_ENV=local) so CSRF is disabled in tests
// Force test env vars before anything else loads.
// Docker OS env overrides phpunit.xml <env> directives unless we set them here first.
putenv('APP_ENV=testing');
$_ENV['APP_ENV'] = 'testing';
$_SERVER['APP_ENV'] = 'testing';
putenv('DB_CONNECTION=sqlite');
$_ENV['DB_CONNECTION'] = 'sqlite';
$_SERVER['DB_CONNECTION'] = 'sqlite';
putenv('DB_DATABASE=:memory:');
$_ENV['DB_DATABASE'] = ':memory:';
$_SERVER['DB_DATABASE'] = ':memory:';
require __DIR__.'/../vendor/autoload.php';