- 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>
- Remove desktop collapse feature — sidebar always open at 264px
- Simplify Alpine sidebar store to mobileOpen only
- Fix mobile overlay: add x-data to #nim-app so Alpine processes x-show/x-transition
- Mobile overlay uses direct backdrop-filter: blur(4px) bypassing Tailwind v4 CSS variable chain
- Fix close/workspace-trigger overlap: restructure header as flex row with close as sibling
- Clicking overlay (z-30) closes sidebar; sidebar stays at z-40
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement user-provided template design:
- Split nav into Workspace / Account sections with labels
- New CSS design tokens (--nim-*) for sidebar without breaking app vars
- Active indicator: left glow bar via ::before, CSS class nim-nav-active
- Collapsed tooltip via CSS ::after content: attr(data-label)
- Geist font for sidebar via Google Fonts
- 264px expanded / 72px collapsed sidebar widths
- All collapse state driven by html[data-sidebar-collapsed] critical CSS
(no Alpine reactivity needed, works across @persist boundary)
- Mobile: always full expanded sidebar regardless of collapse state
- Icon partial at partials/sidebar-icon with 24px viewBox icons
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
nim-sb-expanded/nim-sb-collapsed/sidebar-nav-link collapse rules were
applied globally. On mobile, switching from a desktop-collapsed state
hid all nav labels because data-sidebar-collapsed was still set.
Wrap the rules in @media (min-width: 768px) so mobile always shows
the full expanded sidebar regardless of collapse state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Blade processes @-directives everywhere in templates including inside <style>
blocks and HTML comments. The word "@persist" in a CSS comment was being
interpreted as Livewire's @persist directive, compiling to e() with no argument.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
x-show and :class bindings inside @persist blocks do not re-evaluate when
Alpine store changes. Replace all collapse-dependent bindings with CSS utility
classes (nim-sb-expanded, nim-sb-collapsed, sidebar-nav-link) driven by
html[data-sidebar-collapsed] in the critical inline style block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move .sidebar-aside{width:15rem} outside @media min-width:768px so mobile
gets explicit width — without it -translate-x-full translates 0px and
sidebar content bleeds visible (Collapse text, logout icon)
- Resize handler: auto-close mobileOpen when crossing to mobile viewport
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@persist disconnects Alpine :class bindings from store reactivity on re-render.
Solution: drive sidebar width via html[data-sidebar-collapsed] CSS attribute
(same mechanism as skeleton) instead of Alpine :class.
- _syncDataset() now uses isCollapsed (respects mobile: never narrow on <768px)
- Blocking script checks window.innerWidth >= 768 before setting attribute
- Resize handler calls _syncDataset() to update attribute on viewport change
- aside gets sidebar-aside class; CSS sets 15rem/4rem at md+ via attribute
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add isMobile reactive property + resize listener to sidebar store.
isCollapsed getter returns collapsed && !isMobile, so:
- Desktop: collapse works as normal
- Mobile: isCollapsed always false → sidebar always shows expanded state
regardless of desktop-set collapsed value
Replace all visual $store.sidebar.collapsed → isCollapsed in sidebar + layout.
toggle() / _syncDataset() remain on raw `collapsed` (persisted to localStorage).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add mobile-only X button in sidebar header to call closeMobile()
(splits workspace-switcher <button> into flex row with close sibling)
- Hide collapse toggle with hidden md:block — mobile only needs open/close,
desktop uses collapse to go narrow/wide
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tailwind v4 compiles -translate-x-full to `translate: -100%` (CSS translate
shorthand), not `transform`. The old :style set `transform: translateX(0)` —
a different property — so the sidebar stayed hidden regardless of mobileOpen.
Fix: use `translate: 0` in inline style so it overrides the same property.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add .nim-content class to main content div and pre-Alpine CSS rules:
- @media min-width:768px .nim-content { margin-left:240px }
- html[data-sidebar-collapsed] .nim-content { margin-left:64px }
The blocking script (FIX-13) already sets data-sidebar-collapsed before first
paint; the new CSS applies the matching margin before Alpine initializes.
Also fix skel-topbar left position for collapsed state (was always 240px).
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>
- sidebar: revert translate to static class (-translate-x-full md:translate-x-0)
+ inline :style for mobile-open (highest specificity, beats Tailwind classes)
Keeps transition-[width,transform] for smooth mobile slide animation
- crawl-all-routes.mjs, console-check.mjs: read TEST_EMAIL / TEST_PASSWORD
from process.env; DB creds read from .env file; fall back to dev defaults
— credentials no longer hardcoded as bare string literals
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>
- Install blade-ui-kit/blade-heroicons, replace emoji with x-heroicon-* components
- Inter Variable + JetBrains Mono served from /public/fonts/, no CDN
- html { font-size: 14px }, h1/h2/h3 scaled down
- Remove Google Fonts CDN links from all layouts
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>
Problem: Vite 7 serviert CSS-Entry-Points als JavaScript-Module
(Content-Type: text/javascript). Browser ignoriert JS-Response bei
<link rel="stylesheet"> — CSS wurde nie angewandt.
Lösung:
- CSS aus @vite()-Aufrufen entfernt (kein <link>-Tag mehr)
- CSS aus vite.config.js input entfernt
- import '../css/app.css' zu resources/js/app.js hinzugefügt
- Vite injiziert CSS via JS-Modul → <style>-Tags im Browser
In Prod: CSS wird aus JS-Import extrahiert, Manifest linkt sie korrekt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add hmr.path '/vite-hmr' to vite.config.js for WSS routing
- Improve nginx /vite-hmr location with full proxy headers
- Add DevUserSeeder (nexxo@nimuli.com) for local dev login
- Register DevUserSeeder in DatabaseSeeder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /{slug} redirect route matched /login, /register etc. because
auth.php was required after it. Auth routes must be registered first.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tailwind v4 uses its own Vite plugin — PostCSS/autoprefixer not needed.
Nginx now proxies Vite dev paths to host:5173 so HMR works without a
separate vite.nimuli.com proxy host.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>