nimuli/docs/superpowers/specs/2026-05-16-nimuli-handoff.md

14 KiB
Raw Permalink Blame History

Nimuli — Handoff für neue Claude-Session

Datum: 2026-05-16 Repo: https://git.nexlab.at/boban/nimuli.git VM: Debian 13, IP 10.10.90.142, User: nexxo (sudo) Spec: docs/superpowers/specs/2026-05-15-nimuli-design.md


Aktueller Stand

Was läuft

  • Docker-Compose hoch (nginx, app, queue, scheduler, reverb, mysql, redis, mailpit)
  • Laravel 12 + Livewire v3 + Octane + Tailwind v4 installiert
  • NPM-Reverse-Proxy mit SSL aktiv für:
    • app.nimuli.com → VM:80
    • nimu.li → VM:80
    • ws.nimuli.com → VM:8080 (Websockets ON)
    • mail.intern.nimuli.com → VM:8025
  • Vite über app.nimuli.com (kein vite.nimuli.com mehr)
  • Login funktioniert für Dev-User

Was kaputt ist (PRIO 1)

  1. Theme-Flash bei Hard-Reload — Page rendert kurz im White-Mode, springt dann auf Dark-Mode. Inakzeptabel.

  2. Modals funktionieren GAR NICHT@livewire('wire-elements-modal') möglicherweise nicht im Layout. Klick auf Modal-Trigger-Buttons macht nichts.

  3. Laravel Echo nicht definiert — Console-Warning: "Laravel Echo cannot be found". Reverb-Broadcasts greifen nicht.

  4. Manifest-Icon-FehlerError while trying to use the following icon from the Manifest. Favicon/Manifest-Pfade kaputt.

  5. Sidebar-Layout falsch — Header + Footer sollen fix sein, nur Mittelteil scrollable. Aktuell vermutlich anders.

  6. Alpine doppeltDetected multiple instances of Alpine running Console-Warning. Livewire v3 bringt Alpine mit, separater Import killt.

  7. Frontend fehlt — Nur Stock-Breeze-Blades sichtbar. Kein echtes Nimuli-UI mit Sidebar/Topbar/Dashboard/Modals.

Was schon getan wurde

  • Spec geschrieben (Master-Design 2026-05-15)
  • Phase 1 "Setup" laut Claude angeblich abgeschlossen, faktisch aber:
    • Default-Breeze-Auth-Views statt Custom-Nimuli-UI
    • Keine Modals gebaut
    • Layout nur rudimentär
    • Theme-Switch instabil
    • 25+ Tasks gemeldet als "complete" obwohl Browser-UI kaputt
  • Tailwind v4 + @tailwindcss/vite installiert
  • nginx-Config angepasst für Vite-Proxy via host.docker.internal:5173
  • DevUserSeeder existiert (sollte): nexxo@nimuli.com / NimuliDev2026!
  • Workspace-Routes /w/{ulid}/* definiert mit ResolveWorkspace-Middleware

Wichtige bisherige Entscheidungen (aus Brainstorm-Spec)

Bereich Entscheidung
Stack Laravel 12 + Livewire v3 + Octane (FrankenPHP) + Tailwind v4 + Alpine via Livewire + Wire Elements Modal + Chart.js 4.x
Frontend-Regel Alle Views via Livewire-Components, Controller + View getrennt. KEINE klassischen Controller außer RedirectController
Auth Breeze Livewire-Stack + 2FA via pragmarx/google2fa
Multi-Tenancy Single-DB, Workspace-ULID in URL /w/{ulid}/..., Plan-Limits enforced
DB MySQL Source-of-Truth, Redis für Cache+Queue+Sessions, 4×/Tag Backup 14d Retention
Storage Lokal NAS-Mount (dev), Hetzner Storage Box (prod)
Billing Stripe + Stripe Tax + PayPal-Method + SEPA, Cashier, Customer pro Workspace, billing_profiles separate Table
i18n DB-Table translations + Redis-Hash lang:{locale}, Admin-UI
Theme Dual-Mode Dark (A-Style, default) + Light (B-Style), Purple-Pink-Akzent (#7c3aed → #ec4899) in beiden Modi
Tailwind v4 @theme {} Syntax, KEIN tailwind.config.js, KEINE Inline-Styles außer Progressbar-Width
Mobile Mobile-First, Sidebar als Drawer <768px
Modals Wire Elements Modal — eigener Controller + View, alle via wire:click="$dispatch('openModal', {component: 'modals.xyz'})"
Tests Pest, TDD-Pflicht pro Modal-Action
Pricing Free 0€ / Pro 19€ / Business 79€ / Agency 199€
Roadmap Phase 1 Big-Bang Launch (12-16 Wochen): Core + AI + API + Webhooks. Phase 2 Integrationen. Phase 2.5 React-Native-App. Phase 3 Meta+Google Ads
Production Hetzner Cluster: 3 web + 1 worker + DB + Redis + LB + CDN
Dev Single-Host Docker Compose, alles auf VM 10.10.90.142

Sofort-Aufträge für neue Session

Phase 0 — Setup-State auditieren (vor jedem Fix)

cd ~/nimuli

# 1. Repo-State
git status
git log --oneline -10

# 2. Container
dc ps

# 3. Layout-Datei prüfen
cat resources/views/components/layouts/app.blade.php

# 4. Sidebar-Component
ls -la app/Livewire/Components/ resources/views/livewire/components/

# 5. Modals
ls -la app/Livewire/Modals/ resources/views/livewire/modals/

# 6. wire-elements-modal eingebunden?
grep -r "wire-elements-modal" resources/views/

# 7. Echo-Setup
cat resources/js/bootstrap.js
cat resources/js/app.js

# 8. Tailwind/CSS
cat resources/css/app.css

# 9. Vite-Config
cat vite.config.js

# 10. Dev-User
art tinker --execute="echo App\Models\User::firstWhere('email','nexxo@nimuli.com')?->email ?? 'MISSING';"

# 11. Routes
art route:list | grep -E 'ws\.|logout'

# 12. Tests
art test 2>&1 | tail -20

# 13. Browser-Console-Errors (User berichtet)

Phase 1 — Theme-Flash fixen (PRIO HOCH)

Layout-Header muss exakt so anfangen:

@php
    $theme = auth()->user()?->theme
          ?? request()->cookie('theme', 'dark');
    if ($theme === 'system') $theme = 'dark';
@endphp
<!DOCTYPE html>
<html lang="{{ str_replace('_','-', app()->getLocale()) }}" data-theme="{{ $theme }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>{{ $title ?? 'Nimuli' }}</title>

    <link rel="icon" type="image/svg+xml" href="/favicon.svg">
    <link rel="manifest" href="/site.webmanifest">

    <style>
        html { background: #0a0a0f; color: #f0f0f5; font-family: 'Inter', system-ui, sans-serif; }
        html[data-theme="light"] { background: #ffffff; color: #111827; }
        body { margin: 0; opacity: 0; transition: opacity 120ms; min-height: 100vh; }
        body.app-ready { opacity: 1; }
    </style>

    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    {{ $slot }}

    @livewire('wire-elements-modal')

    <script>
        function r() { document.body.classList.add('app-ready'); }
        if (document.readyState === 'complete') r();
        else { window.addEventListener('load', r); setTimeout(r, 500); }
    </script>

    @livewireScripts
</body>
</html>

Theme-Toggle MUSS Cookie + localStorage setzen, sonst flash beim Reload.

Phase 2 — Modals einbinden

@livewire('wire-elements-modal') MUSS im Layout vor </body> stehen. Sonst geht KEIN Modal.

Plus Wire-Elements-Modal-Package installiert:

art tinker --execute="echo class_exists('LivewireUI\\Modal\\ModalComponent') ? 'OK' : 'MISSING';"

Falls fehlt:

composer require wire-elements/modal

Phase 3 — Echo definieren

resources/js/bootstrap.js:

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: 443,
    wssPort: 443,
    forceTLS: true,
    enabledTransports: ['ws', 'wss'],
});

resources/js/app.js — KEIN Alpine-Import:

import './bootstrap';

Phase 4 — Sidebar mit flex-col

Pattern: flex flex-col mit flex-none Header + flex-1 overflow-y-auto Nav + flex-none Footer. Siehe vorigen Fix-Prompt.

Phase 5 — Modals bauen (PFLICHT mit TDD)

Reihenfolge:

  1. Shared: modals.confirm
  2. Links: create-link, edit-link, delete-link
  3. QR: create-qr, edit-qr, delete-qr
  4. Bio: create-bio-page, edit-bio-page, delete-bio-page, add-bio-block, edit-bio-block
  5. Domains: add-domain, verify-domain, delete-domain
  6. Team: invite-member, edit-member-role, remove-member
  7. Workspace: create-workspace, edit-workspace
  8. Settings: create-webhook, edit-webhook, create-api-token, revoke-api-token
  9. Profile: setup-2fa, delete-account

Pattern pro Modal:

  • Test ZUERST (TDD)
  • app/Livewire/Modals/<Name>.php — extends LivewireUI\Modal\ModalComponent
  • resources/views/livewire/modals/<name>.blade.php
  • Trigger-Button auf Page mit wire:click="$dispatch('openModal', {component: 'modals.<name>'})"
  • Test grün → Browser-Test → Commit

Phase 6 — Frontend-Build (umfangreich)

Layouts, Sidebar, Topbar, Theme-Toggle, Workspace-Switcher, Dashboard mit Hero-Metrics + Chart.js, alle Index-Pages für Links/QR/Bio/Domains/Analytics/Team/Billing/Settings/Profile/Admin.

Skills:

  • /frontend-design für JEDE View
  • /test-driven-development für jede Action
  • /using-superpowers immer

Skills-Pflicht in neuer Session

- /using-superpowers (BEFORE jeder Action)
- systematic-debugging (vor jedem Fix, Root-Cause finden)
- verification-before-completion (Beweis-Output bevor "fertig")
- /frontend-design (jede View, jedes Modal)
- /test-driven-development (jeder Action: Test ZUERST)
- security-guidance (Auth, Upload, Token, Webhook)
- caveman (Output knapp)

Selbst-Verify-Scripts

scripts/verify-modals.sh

#!/bin/bash
set -e
cd ~/nimuli

[ -d node_modules/tailwindcss ] || { echo "FAIL: tailwindcss missing"; exit 1; }
grep -q 'wire-elements-modal' resources/views/components/layouts/app.blade.php || { echo "FAIL: wire-elements-modal not in layout"; exit 1; }
grep -q 'window.Echo' resources/js/bootstrap.js || { echo "FAIL: Echo not defined"; exit 1; }
[ -f public/favicon.svg ] || { echo "FAIL: favicon missing"; exit 1; }
[ -f public/site.webmanifest ] || { echo "FAIL: manifest missing"; exit 1; }

USER=$(docker compose exec -T app php artisan tinker --execute="echo App\Models\User::where('email','nexxo@nimuli.com')->exists() ? '1' : '0';" 2>/dev/null | tr -d '[:space:]')
[ "$USER" = "1" ] || { echo "FAIL: nexxo user missing"; exit 1; }

ROUTES=$(docker compose exec -T app php artisan route:list 2>/dev/null | grep -E 'ws\.' | wc -l)
[ "$ROUTES" -ge 5 ] || { echo "FAIL: workspace routes ($ROUTES)"; exit 1; }

docker compose exec -T app php artisan test --parallel 2>&1 | tail -5

echo "✅ Verify GRÜN"

Alias:

alias verify='bash scripts/verify-modals.sh'

Workflow neue Session

1. claude-yolo  (VS Code: Permission-Mode "Bypass")
2. cd ~/nimuli
3. Diese handoff.md mitgeben oder Pfad referenzieren
4. Spec verfügbar: docs/superpowers/specs/2026-05-15-nimuli-design.md
5. /using-superpowers aktivieren
6. Phase 0 — Audit
7. Phase 1-6 nach Priorität
8. Pro Phase: Test → Build → Verify → Commit → Push
9. NICHT "fertig" ohne verify-Script GRÜN

Git-Push-Pattern

GITEA_TOKEN=$(grep ^GITEA_ACCESS_TOKEN .env | cut -d= -f2 | tr -d '"' | tr -d "'")
git remote set-url origin https://oauth2:${GITEA_TOKEN}@git.nexlab.at/boban/nimuli.git
git push origin main

Dev-User-Credentials

URL:       https://app.nimuli.com/login
Email:     nexxo@nimuli.com
Password:  NimuliDev2026!

Falls fehlt: art db:seed --class=DevUserSeeder (idempotent via updateOrCreate)


Lokale Aliase auf VM (~/.bashrc)

alias dc='docker compose'
alias dce='docker compose exec'
alias dcl='docker compose logs --tail=100 -f'
alias dcu='docker compose up -d'
alias dcd='docker compose down'
alias dcr='docker compose restart'
alias dcps='docker compose ps'

alias art='docker compose exec app php artisan'
alias tinker='docker compose exec app php artisan tinker'
alias migrate='docker compose exec app php artisan migrate'
alias migratef='docker compose exec app php artisan migrate:fresh --seed'
alias reset-db='docker compose exec app php artisan migrate:fresh --seed && echo "DB reset + Dev-User wieder da"'
alias seed-dev='docker compose exec app php artisan db:seed --class=DevUserSeeder'

alias composer='docker compose exec app composer'
alias pest='docker compose exec app vendor/bin/pest'
alias pint='docker compose exec app vendor/bin/pint'
alias phpstan='docker compose exec app vendor/bin/phpstan analyse'

alias dev='npm run dev'
alias build='npm run build'

alias vite-start='tmux new -d -s vite "cd ~/nimuli && npm run dev 2>&1 | tee /tmp/vite.log"'
alias vite-stop='tmux kill-session -t vite 2>/dev/null; pkill -f vite'
alias vite-log='tail -f /tmp/vite.log'

alias verify='bash scripts/verify-modals.sh'
alias smoke='bash scripts/smoke-test.sh'

alias clear-cache='docker compose exec app php artisan optimize:clear && rm -rf node_modules/.vite public/hot public/build'
alias octane-reload='docker compose exec app php artisan octane:reload'

alias nimuli='cd ~/nimuli'
alias claude-yolo='claude --dangerously-skip-permissions'  # falls CLI installiert

NPM-Konfiguration

Proxy-Hosts in NPM-UI:

  • app.nimuli.com → 10.10.90.142:80, SSL Let's-Encrypt, Websockets Support ON
  • nimu.li → 10.10.90.142:80, SSL Let's-Encrypt
  • ws.nimuli.com → 10.10.90.142:8080, SSL Let's-Encrypt, Websockets Support ON
  • mail.intern.nimuli.com → 10.10.90.142:8025

Falls vite.nimuli.com noch existiert: löschen (Vite läuft jetzt über app.nimuli.com).


Erfolgs-Kriterien (vor "Phase 1 fertig")

  • Hard-Reload 5× zeigt KEINEN Theme-Flash
  • Manifest-Icon-Error WEG aus Console
  • window.Echo definiert (Browser-Console-Check)
  • Alpine NUR EINMAL geladen (kein "multiple instances")
  • Sidebar Header+Footer fix, Mitte scrollable
  • Mindestens 25 Modals erstellt, alle funktional, alle getestet
  • Frontend hat echtes Nimuli-UI (Dashboard, Links, QR, Bio, Domains, Analytics, Team, Billing, Settings, Profile)
  • Login mit nexxo@nimuli.com funktioniert nach migrate:fresh
  • verify-Script grün
  • smoke-Script grün
  • art test grün
  • phpstan grün
  • alle Commits gepusht zu Gitea

Strikte Regeln

  1. KEINE "fertig"-Behauptung ohne verify-Output
  2. Bei Console-Error in Browser: NICHT abbrechen, Root-Cause finden
  3. JEDER Modal = TDD: Test zuerst, dann Build
  4. Wire-Elements-Modal in JEDEM Layout das Modals nutzt
  5. Theme-Init Server-Side via Cookie/User-Setting, nicht nur clientseitig
  6. DevUserSeeder NIE entfernen aus DatabaseSeeder
  7. @livewireScripts NACH @livewire('wire-elements-modal')
  8. Alpine NIE separat importieren
  9. Echo in bootstrap.js, NICHT in app.js direkt
  10. Pro Fix: Beweis durch curl + Browser + Test, nicht "ich glaube"