- Button: wire:loading.remove on label (DOM removal, no z-index fight)
spinner span is normal flex child — button's own flex centers it
SVG: track circle opacity 25% + 3/4 arc via stroke-dasharray 42.4/56.6,
stroke-linecap round, rotated -90deg so arc starts at top
- AI prompt: explicitly flags sparse data (<3 links or <10 clicks),
requires every claim reference a specific slug, forbids generic padding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Prompt respects user locale (de/en/fr/es/it/pt) — responds in user's language
- Rewrite prompt: domain pattern analysis, low-click diagnosis, no generic advice
- Include total clicks + link count for context
- max_tokens 512 → 800 for richer output
- Button spinner: h-5 w-5 (was h-4 w-4), bg-inherit to cover label text cleanly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- BUG 1: blocking script in <head> reads localStorage before first paint; CSS rule html[data-sidebar-collapsed] .skel-sidebar{width:64px} eliminates width flash
- BUG 2: sidebar store gains mobileOpen/toggleMobile()/closeMobile(); hamburger calls toggleMobile(); backdrop and sidebar :style use mobileOpen; SPA navigated handler calls closeMobile()
- BUG 3: text-purple → text-accent in AI Insights view (text-purple is undefined)
- BUG 4: ui/button.blade.php — spinner is now absolute inset-0 overlay; content fades to opacity-40 via wire:loading.class instead of hiding
- BUG 5: insights generate button and QR modal save/cancel migrated to x-ui.button component
- BUG 6: QR modal Styling/Frame/Advanced sections replaced from <details> (destroyed by Livewire re-render) to Alpine x-data{open:false} divs with x-show — state survives wire:model.live updates
- BUG 7: updateActiveNav() now checks el.dataset.navExact; Dashboard <a> gets data-nav-exact="1" so it only activates on exact pathname match
- BUG 8: bio_pages.theme JSON applied in public show.blade.php via inline CSS vars (4 presets: dark/light/purple/blue); EditBioPage gains theme property with preset selector in modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BUG 1: Insights/Anomalies called require_workspace() in action methods — add mount() cache
BUG 2: Analytics clear-filter used multiple $set() calls — replace with clearFilters() action
BUG 3: Static md:ml-60 on main div blocked Alpine :class md:ml-16 override — remove static
BUG 4: updateActiveNav missed icon <span> colors on @persist sidebar — update icon span too
BUG 5: Topbar <header> lacked x-data — Alpine x-show on theme SVGs not reactive
BUG 6: --color-t2 #8888a0 too dark in dark mode — raise to #b8b8d0
BUG 7: UtmBuilder::createLink() used current_workspace() in AJAX — pass workspaceUlid via mount
BUG 8: Clicks badge in separate column, not rightmost — merge into actions cell
BUG 9: Sidebar nav icons w-4 h-4 — increase to w-5 h-5, justify-center when collapsed
BUG 10: suggestSlugs() no try-catch; text-purple undefined — add guard + use text-accent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Workspace.getRouteKeyName()='ulid' so route() generates ULID URLs not ID
- QR modal 4xl→5xl; import QRCode in @script→window.QRCode (no ESM in @script)
- Bio slug unique rule scoped to (workspace_id, domain_id) not global
- updateActiveNav uses URL.pathname not raw getAttribute('href') (absolute URL fix)
- Add resources/views/components/ui/copy-button.blade.php Alpine component
- Analytics Index.render/baseQuery use $workspaceId not require_workspace() — prevents RuntimeException on Livewire action calls without middleware
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- QR Code: split-pane wizard with type tabs, live preview canvas, style/frame/advanced options
- UTM Builder modal: generate URL with UTM params, copy + create short link
- A/B Test: LinkVariants modal with auto-balance weights (min 2, max 5 variants)
- AI: SlugSuggester service (3 suggestions via claude-haiku), AI-slug button in CreateLink
- AI pages: /ai/insights, /ai/anomalies, /ai/ab-generator with sidebar entry
- Analytics: #[Url] attributes for range/country/device/linkId, 24h/7d/30d/90d buttons
- QR enum migration (MySQL-only guard) to add email/phone/sms/location types
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All create modals now validate→create→dispatch(kebab-case event)→toast→close
- All edit/delete modals dispatch kebab-case events to Index classes
- Index components listen via #[On] and call resetPage() to refresh
- QR Code save() validates url field conditionally per type
- Tests updated to assert kebab-case event names
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add POST /logout route (was missing from auth.php)
- Add workspaceId property to Analytics component (fixes #[On] binding)
- tests/bootstrap.php: force APP_ENV=testing before Docker OS env takes over
- phpunit.xml: use tests/bootstrap.php + force=true on APP_ENV
- Update AuthenticationTest, ProfileTest, LoginRateLimitTest to match current app structure
- Add tests/Feature/Livewire/WorkspacePagesTest.php: 11 workspace route tests
- scripts/smoke-test.sh: HTTP smoke test for all routes
- Result: 75/75 tests passing, all smoke routes 302→login
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DemoDataSeeder now creates workspace_members record for owner + sets default_workspace_id
- ResolveWorkspace middleware accepts workspace owners even without explicit member record
- Applied membership fix for nexxo@nimuli.com via PHP (existing installs)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement AI domain with Anthropic API integration for slug generation.
Adds GenerateSlug action for URL-to-slug transformation and GenerateAbVariants
for A/B test variants. Includes test coverage for GenerateSlug action with
mocked AiService.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Overwrites the Analytics Index stub with a full Livewire component that
loads today's click count from the clicks table (workspace_id is a direct
column) and listens for live updates via Echo/Reverb using the
#[On('echo:workspace.{workspaceId}.analytics,click.recorded')] attribute.
Creates the corresponding blade view with a today-counter card and a
live recent-clicks feed (last 10 entries).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add Links Livewire page (Index component + blade view) with live search,
status filter, paginated table, and delete action using DeleteLink for
cache invalidation. Register workspace-scoped route group under
w/{workspace} with ResolveWorkspace middleware. Add stub Livewire pages
for QrCodes, Bio, Analytics, Domains, Settings, and Billing with a shared
coming-soon view.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement Nimuli-specific authenticated layout with:
- Sidebar navigation with 7 main menu items (dashboard, links, qr codes, link-in-bio, analytics, domains, settings)
- Topbar header with user email and logout button
- Safe route checking using Route::has() for undefined routes
- Workspace display in sidebar when available
- Livewire AppLayout base class for future Livewire components
Adds /api/v1/workspaces/{ulid}/links endpoints (index, store, destroy)
protected by Sanctum token auth; adds HasApiTokens to User model and
AuthorizesRequests to base Controller; wires routes/api.php into bootstrap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Laravel\Cashier\Billable trait to Workspace model
- Register Workspace as Cashier customer model in AppServiceProvider
- Publish cashier config with EUR currency and de_DE locale defaults
- Add CSRF exclusion for stripe/* in bootstrap/app.php
- Add StripeWebhookTest verifying endpoint exists and rejects bad signatures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements QrRenderer using chillerlan/php-qrcode v6 API (outputInterface
instead of deprecated outputType constants), QrCode Eloquent model with
ULID auto-generation and soft deletes, and GenerateQrCode action.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>