The topbar online count + server-switcher poll, but the sidebar nav
badges (firing-alert "1", update chip) were static Blade — they only
updated on a page navigate/refresh, out of step with the live shell.
The sidebar can't itself be a Livewire poll (that would break its
request()->is() active-nav states). Instead: a tiny `navBadges` Alpine
island on the <nav> polls GET /shell/badges.json every 15s and updates
the [data-nav-badge] chips in place. The badge chips are now always in
the DOM (hidden at zero) so the poller can reveal/hide them without a
re-render. Counts come from the same cached+busted model helpers the
render uses (AlertIncident::firingCount / AuditEvent::threatLoginCount24h
/ ReleaseChecker::updateAvailable — all cache-only, no per-poll query
storm); the endpoint zeroes alerts/threats for non-manage-panel roles.
Verified live in a browser: with an incident fired mid-session, the alert
chip flips to "1" within one poll cycle with NO page refresh, zero
console errors. New ShellBadgesTest (auth + gating + count); the release
badge test updated for the always-present-hidden chip. 753 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Surgical consistency/polish tightening within the existing "Tactical Terminal"
system — no re-theme, no new tokens/fonts/colors, every change uses an existing
@theme utility. From a design review (subagent-cataloged, false-positives rejected).
- nav-item: badge tooltip is now a contextual `badge-title` prop instead of a hardcoded
"update available" — the Threats count badge showed the wrong tooltip. Sidebar passes a
real "N login attempts (24h)" title for Threats and "Update available" for Versions.
- empty-state messages (sessions/users/webauthn-keys/login-protection) bumped from the
faintest ink-4 to ink-3 — when a list is empty that line is the focal point, not meta.
- modal actions: fail2ban-config / hardening-action / system-update submit buttons go from
the softer `accent` to solid `primary` so the real action reads as primary (cancel stays
secondary; the done-state close is unchanged).
- servers list: CPU/RAM mini-bar labels ink-4 -> ink-3 to match their values.
- edit-credential: top-level error now uses the standard bordered offline error box
(border-offline/25 bg-offline/10 + alert icon), matching firewall-rule.
- file-editor: title + path get a native `title=` tooltip so a truncated long path is legible.
- add-ssh-key label rhythm (mb-1.5 -> mb-1); login-protection textarea vertical padding
(py-2 -> py-2.5, the textarea norm); webauthn-keys add button w-full sm:w-auto so it isn't
a mobile orphan; app.css font comment de-staled (fonts are self-hosted).
Deliberately NOT changed (would harm the design, not help): mail from_name kept sans vs
from_address mono (name = prose, address = technical token — the convention); the Memory
metric tone kept dynamic/threshold-based (health signal) rather than forced cyan.
Verified: vite build compiles all utilities, 617 tests green, de/en lang parity, DOM render
check of the badge + button variants.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A guided tour for new operators: a dimmed+blurred overlay with an enterprise description card
(mono eyebrow "01 / 06", display title, body, progress bar, back/skip/next). A step with a
target spotlights the matching sidebar nav item (a box-shadow "hole" lights it while everything
else dims); welcome + finish are centred cards. On narrow viewports (sidebar hidden) it falls
back to centred cards.
- auto-opens once per account (users.onboarding_tour_completed_at is null → autostart); skip or
finish calls Tour::markSeen() which stamps it, so it never auto-opens again.
- relaunchable any time from Settings (a client-side 'onboarding:start' window event — no DB change).
- @persist'd in the layout + a sessionStorage guard so a wire:navigate or fast reload right after
dismissal can't re-open it before markSeen persists.
- nav-item now merges its attribute bag (data-tour passes through without duplicating class/href).
Browser-verified: auto-open → spotlight walks Dashboard/Servers/Terminal/Settings → skip stamps +
closes → navigation doesn't reopen → Settings relaunch works; zero console errors. 4 feature tests;
full suite 473 pass; Codex review CLEAN.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two fixes the operator asked for:
- The "what's new" preview was empty. availableChangelog fetched CHANGELOG.md at
ref="0.9.50", but release tags are "v0.9.50" — the raw fetch 404'd and the
panel silently stayed empty. Use the v-prefixed tag as the ref. The existing
test masked it with a wildcard fake; tightened with Http::assertSent.
- The sidebar now shows a "1" badge on the Version item when an update is
available, so an update is visible on every page, not only the Versions page.
Driven by a new ReleaseChecker service: updateAvailable() is a pure cache read
(no network in the sidebar); a scheduled clusev:check-update (every 30 min,
anonymous/read-only) keeps the cache warm so the badge is accurate even before
the operator opens the Versions page.
Refactor: the remote tag lookup (fetch + newest-version) moved out of the
Versions component into ReleaseChecker; the component delegates. nav-item gains
an optional badge prop.
Tests: ReleaseChecker (cache-read update-available, refresh caches, command warms
cache, sidebar badge renders only when available) + the v-prefixed-ref assertion.
371 pass, Pint clean, /versions loads 200 with no console errors; badge + the
"What's new in v0.9.50" panel verified in the browser. Lang parity kept.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
From clusev-template/reference: void background with a fine dot-grid + orange top
vignette, panel elevation (shadow-panel token), tonal scrollbars, a left accent
bar + accent icon on the active nav item, status-dot + brandmark glows, and a
motion-safe ping. @theme/tokens only — no inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>